APIR agent record specification
The APIR agent record specification is the exact, byte-level description of the evidence record APIR keeps for each AI agent, published so anyone can write their own verifier. It covers the signed papers (an Ed25519-signed Trust Passport under did:web:apir.ai), the signed mandate, and the log book: a tamper-evident, hash-chained work history whose table refuses edits, in which each entry's chain_hash is the SHA-256 of a fixed canonical text, and the chain heads are timestamped every night by an outside RFC 3161 authority, with the tokens held by APIR. Today a public passport's signature and a seal check without trusting APIR; the parts that still rest on APIR's word, among them the log book recompute and the timestamp tokens over agents' chains, are named. Every rule here was checked against the running system on 1 November 2026, with real test vectors and the commands that reproduce them.
By APIR · updated
APIR wrote this specification about its own system. It describes what runs, measured on 1 November 2026, including what a stranger cannot yet check from public data (see Known gaps). It is not an open standard. It is APIR's own specification of what APIR runs, published so anyone can check the record. There is no standards body or IETF draft behind it, and the specification itself carries no licence. The offline seal checkers are published at apir.ai/proof/verify-seal and apir.ai/proof/apir-seal.mjs. The checker for exports, stamps and seals is MIT-licensed at github.com/apir-ai/apir-record. Lines about standards and FreeTSA come from their own pages, read on 1 November 2026, with the link beside them.
The record, its three objects, and who signs what
APIR gives every AI agent signed papers and a tamper-evident work history that buyers, auditors and insurers can check without trusting you.
For each agent the record holds three objects. Each one is checkable on its own, and each has a different signer.
- The papers. The Trust Passport: a credential that names the agent (agent_id), the organisation that owns it (org_id), its grade and its expiry, signed with APIR's Ed25519 passport key. It is the agent's signed ID. A passport is issued from the agent's Trust Genome when its score is 70 or more; below that the agent reads Unrated, never a made-up score. The signer itself refuses only a score below 0.01, one more than 0.5 from the genome, or an agent with no genome.
- The mandate. What the agent may do: actions, places, a value cap and an expiry. A signed-in member of the agent's organisation writes it on apir.ai, and APIR signs it with the same passport key. The owner holds no key of their own, so the signature shows APIR signed this mandate as written; its issued_at is APIR's clock.
- The log book (the Agent Black Box). One hash chain per agent. Each entry's chain_hash is the SHA-256 of a canonical text that includes the chain_hash of the entry before it. Entries are not signed one by one: the chain and the outside timestamp carry the integrity. APIR operates the writer. Every writer in APIR's code goes through one database function, bb_record; APIR's service role can also insert rows directly.
Two more things hang off the log book. Every night the heads of the agents' chains are combined into a Merkle root that an outside RFC 3161 authority (FreeTSA) timestamps, so a later rewrite of anything before a stamp would no longer match it. For an agent with a public passport, anyone can fetch the token from the verify-black-box endpoint and check the stamp themselves, and an owner's free export carries the tokens too. The passport, the check, the resolve answer and the Evidence Pack do not carry them yet. And a seal, a declaration over files made at apir.ai/seal, is itself one log book entry, with a portable proof that carries its token and checks offline.
- APIR's passport key (Ed25519) signs: Trust Passports, mandates, Evidence Packs (over the hex text of their SHA-256 content hash) and agent ID photos. One key, apir-pp-a8G-EOhBbTIO, sealed in Supabase Vault and used by one signing service, passport-issuer.
- FreeTSA, with its own certificate, signs: the nightly timestamp tokens. APIR does not run it and cannot change what it signed.
- Nothing signs: a single log book entry. Its integrity is the hash chain and the timestamp over it.
The Evidence Pack (AUD 499, one-time) seals one agent's record for a deal, a review or a claim behind a public link; its contents format is not part of version 1. The agent ID photo is defined in the passport section for completeness: none has been issued as of 1 November 2026.
Identifiers: did:web:apir.ai, key ids and record ids
Every signature names a key, and every key resolves from one web address.
- Issuer. did:web:apir.ai. Under the did:web method it resolves to apir.ai/.well-known/did.json, which lists each published key as a JsonWebKey2020 verification method with the id did:web:apir.ai#<kid>, referenced from assertionMethod and authentication. The same keys are a JWK Set at apir.ai/.well-known/jwks.json.
- Keys. Ed25519, published as an OKP JWK (RFC 8037): kty OKP, crv Ed25519, alg EdDSA, use sig, and x, the 32-byte public key in base64url. A kid is "apir-pp-" followed by the first 12 characters of x.
- The key in use. apir-pp-a8G-EOhBbTIO, x = a8G-EOhBbTIOAxqClk7hqFTthIGXehWTFDwMOOC_B9M (hex 6bc1be10e8416d320e031a82964ee1a854ed8481977a1593143c0c38e0bf07d3), active since 28 May 2026. No other key is published, and none has been retired, as of 1 November 2026.
- Agents. agent_id is a UUID, printed lowercase with hyphens. It is the subject of the passport, the grantee of the mandate and the owner of the log book chain.
- Passports. passport_id is text, today beginning TP- (early passports begin APIR-TP-), for example TP-SPOEBUILDER-MVGFML6R. A renewal gets a new passport_id.
- Mandates. mandate_id is a UUID and is the id inside the signed payload. mandate_ref (MND-, ending -C for a mandate written on the connect page; some early mandates have none) is the label people quote and is not signed.
- Log book entries and seals. An entry is addressed by agent_id and sequence_number. Its row id is not hashed. A seal's id is the row id of its log book entry.
A log book entry, field by field
Nine fields go into the hash. Nothing else stored beside an entry does.
- agent_id (uuid). The agent whose chain this is.
- sequence_number (bigint). The entry's place in the agent's chain: 1 for the opening entry, then up by one. The pair agent_id and sequence_number is unique.
- event_type (text). What kind of entry. The table accepts a fixed list (39 values on 1 November 2026, among them action, decision, tool_call, input, output, reasoning, data_access, error, registration, passport, audit and kill_switch). An outside agent sending through POST https://apir.ai/api/v1/events may use action, decision, tool_call, input, output, reasoning, data_access and error.
- input_hash, output_hash (text or null). For an outside agent, what it sends as the SHA-256 of its prompt and of its output, in lowercase hex, made on its own side: APIR checks only that each is 64 hex characters and cannot tell what was hashed. The summary and data fields arrive as written. APIR's own writers put other hashes here, for example a seal's declaration hash.
- reasoning_summary (text or null). One line on what happened. Up to 1,000 characters from the events API.
- data_accessed (jsonb or null). Structured detail. Up to 8,192 bytes from the events API. Stored as PostgreSQL jsonb, which decides how it prints in the canonical text.
- timestamp (timestamptz). Set by the writer from the database clock at the moment of insert, to the microsecond. It is APIR's clock, not the agent's.
- prev_chain_hash (text or null). The chain_hash of the entry before. Null on sequence 1.
- chain_hash (text). The lowercase hex SHA-256 of the canonical text of the nine fields above. It is not part of that text itself.
Stored beside each entry but not hashed: the row id, org_id, metadata and created_at. The agent is covered by an entry's hash; the organisation that owns the agent is not.
The canonical text whose SHA-256 is chain_hash
chain_hash is the lowercase hex SHA-256 of the UTF-8 bytes of the text PostgreSQL prints for jsonb_build_object(the nine fields)::text in UTC (the database default; the canonical function pins it). Version 1 freezes that text.
If you hold the canonical text, you need none of the rules below: hash it. A seal's portable proof hands you the text of every entry it covers. The rules are for rebuilding the text from the fields.
- Key order. Shorter keys first, measured in UTF-8 bytes; keys of equal length in bytewise order. So the nine fields always print as agent_id, timestamp, event_type, input_hash, output_hash, data_accessed, prev_chain_hash, sequence_number, reasoning_summary. The same rule holds at every depth inside data_accessed: "B" sorts before "a", and "é" (two bytes) sorts after "ab". A repeated key keeps its last value.
- Separators. A comma and a space between members and between array elements, a colon and a space between a key and its value, no other whitespace, and no newline at the end. Empty containers print as {} and []. Arrays keep their order.
- Strings. In double quotes. Only the double quote, the backslash and characters below U+0020 are escaped: \b \f \n \r \t, and any other character below U+0020 as \u00XX in lowercase hex. Everything else, U+007F, the forward slash and non-ASCII included, is literal UTF-8.
- Numbers. PostgreSQL numeric text. Exponents are written out and decimal places are kept: 0.00 stays 0.00, 1.50 stays 1.50, 1e3 prints as 1000, 1.5e-3 as 0.0015, 2.50e1 as 25.0 and -0 as 0. A verifier that parses JSON into floating point and prints it again loses this. sequence_number prints as a plain integer.
- Literals and nulls. true, false and null. A null column prints as null; no field is ever left out.
- Identifiers. agent_id prints as a lowercase, hyphenated UUID string.
- Timestamps. In UTC as YYYY-MM-DDTHH:MM:SS, then a dot and the fraction of a second to the microsecond with trailing zeros dropped (no dot at all when the fraction is zero), then +00:00. 07:30:09.120000 prints as 07:30:09.12+00:00. A JavaScript Date holds milliseconds only, so rebuild the text from the stored string, not from a Date.
PostgreSQL documents the behaviour this rests on. jsonb "does not preserve the order of object keys", "shorter keys are stored before longer keys", and "jsonb will preserve trailing fractional zeroes" (PostgreSQL 17, JSON Types). Every example output on this page was printed by APIR's PostgreSQL 17 database on 1 November 2026.
Reference rebuild in JavaScript
Rebuilds the canonical text from the nine fields and hashes it. Pass data_accessed as JSON text, so each number keeps its digits, and the timestamp as the stored UTC string. Save it as rebuild.js and run node rebuild.js; it prints the chain_hash of test vector 1.
// Rebuild a log book entry's canonical text and its chain_hash. // APIR agent record specification, version 1. Node, no dependencies. // Pass data_accessed as JSON text, so every number keeps the digits it was stored with. const { createHash } = require('node:crypto'); function parseJson(text) { let i = 0; const ws = () => { while (i < text.length && /\s/.test(text[i])) i++; }; const str = () => { const s = i++; while (text[i] !== '"') { if (text[i] === '\\') i++; i++; } i++; return JSON.parse(text.slice(s, i)); }; const val = () => { ws(); const c = text[i]; if (c === '{' || c === '[') { const isObj = c === '{', out = []; i++; ws(); if (text[i] === (isObj ? '}' : ']')) { i++; return isObj ? { obj: out } : { arr: out }; } for (;;) { if (isObj) { ws(); const k = str(); ws(); i++; out.push([k, val()]); } else out.push(val()); ws(); if (text[i++] !== ',') return isObj ? { obj: out } : { arr: out }; } } if (c === '"') return { str: str() }; for (const lit of ['true', 'false', 'null']) if (text.startsWith(lit, i)) { i += lit.length; return { lit }; } const n = /^-?\d+(\.\d+)?([eE][+-]?\d+)?/.exec(text.slice(i))[0]; i += n.length; return { num: n }; }; return val(); } // PostgreSQL numeric text: exponent written out, decimal places kept, no minus zero. function numText(lex) { const m = /^(-?)(\d+)(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/.exec(lex); const frac = m[3] || '', exp = Number(m[4] || 0), scale = Math.max(0, frac.length - exp); const d = BigInt(m[2] + frac) * 10n ** BigInt(exp - frac.length + scale); let s = d.toString().padStart(scale + 1, '0'); if (scale) s = s.slice(0, -scale) + '.' + s.slice(-scale); return (m[1] && d !== 0n ? '-' : '') + s; } // Only the quote, the backslash and characters below U+0020 are escaped; the rest, U+007F included, is literal UTF-8. function strText(s) { const esc = { '"': '\\"', '\\': '\\\\', '\b': '\\b', '\f': '\\f', '\n': '\\n', '\r': '\\r', '\t': '\\t' }; let out = '"'; for (const ch of s) { const c = ch.codePointAt(0); out += esc[ch] || (c < 0x20 ? '\\u' + c.toString(16).padStart(4, '0') : ch); } return out + '"'; } // Keys: shorter UTF-8 byte length first, then bytewise. A repeated key keeps its last value. const keyCmp = (a, b) => Buffer.byteLength(a) - Buffer.byteLength(b) || Buffer.compare(Buffer.from(a), Buffer.from(b)); function render(v) { if (v.lit) return v.lit; if (v.num !== undefined) return numText(v.num); if (v.str !== undefined) return strText(v.str); if (v.arr) return '[' + v.arr.map(render).join(', ') + ']'; const last = new Map(v.obj); return '{' + [...last.keys()].sort(keyCmp).map((k) => strText(k) + ': ' + render(last.get(k))).join(', ') + '}'; } // UTC, to the microsecond, trailing zeros of the fraction dropped, then +00:00. function tsText(t) { const m = /^(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2}:\d{2})(?:\.(\d{1,6}))?(?:Z|\+00(?::?00)?)$/.exec(t); if (!m) throw new Error('timestamp must be UTC: ' + t); const f = (m[3] || '').replace(/0+$/, ''); return m[1] + 'T' + m[2] + (f ? '.' + f : '') + '+00:00'; } function canonicalEvent(e) { const s = (x) => (x == null ? { lit: 'null' } : { str: String(x) }); return render({ obj: [ ['agent_id', s(String(e.agent_id).toLowerCase())], ['sequence_number', { num: String(e.sequence_number) }], ['event_type', s(e.event_type)], ['input_hash', s(e.input_hash)], ['output_hash', s(e.output_hash)], ['reasoning_summary', s(e.reasoning_summary)], ['data_accessed', e.data_accessed == null ? { lit: 'null' } : parseJson(e.data_accessed)], ['timestamp', { str: tsText(e.timestamp) }], ['prev_chain_hash', s(e.prev_chain_hash)], ] }); } const chainHash = (e) => createHash('sha256').update(canonicalEvent(e), 'utf8').digest('hex'); // Test vector 1. Prints 41f5f81b43055fa07c53bf95f3c087fc96212b1316a921a317c32448db6acf0f console.log(chainHash({ agent_id: 'd25e0c5b-2861-4bc3-a330-c5c9427a86e5', sequence_number: 1, event_type: 'registration', input_hash: null, output_hash: null, reasoning_summary: 'Agent registered: SPOE Builder', data_accessed: '{"risk_tier": "moderate", "created_by": null, "eu_ai_act_class": null}', timestamp: '2026-10-19 03:29:04.922129+00', prev_chain_hash: null, }));
How the chain links, and what a change looks like
One chain per agent, read in sequence order, each entry naming the one before it.
- Sequence 1 has prev_chain_hash null. Every later entry's prev_chain_hash equals the stored chain_hash of the entry before it, and its sequence_number is one more.
- A verifier walks the entries in sequence order and reports three findings: content (tampered_sequences, where the stored chain_hash does not recompute from the fields), links (broken_link_sequences, where prev_chain_hash is not the previous chain_hash) and sequence (sequence_gap_at, where a number was skipped). The chain verifies when all three are clean.
- An edited field: that entry fails content.
- An edited field with its chain_hash rewritten to match: the next entry's link breaks, unless every later entry is rewritten too, which only a head stamped or saved outside APIR before the change shows.
- A deleted entry in the middle: a sequence gap and a broken link at the entry after it.
- Newest entries deleted or replaced, or a deleted chain: nothing shows in what remains. A head that was already timestamped, or that someone outside APIR saved, shows it.
The table refuses UPDATE, DELETE and TRUNCATE: triggers raise an error on any attempt, from APIR's service role too. The refusal of DELETE and TRUNCATE dates from 4 November 2026; before that the service role could delete rows. Someone with owner rights on the database can switch a trigger off, and the service role can insert rows directly. A chain that verifies is internally consistent, which on its own does not rule out the newest entries being removed or replaced; only a head stamped or saved outside APIR before the change shows that. The outside timestamp is signed by FreeTSA, so APIR cannot alter what a token says, and a later rewrite of anything before a stamp would no longer match it; the token for an agent with a public passport is public, so an outsider can check that stamp themselves.
APIR runs the same recompute on a schedule (daily by default; an organisation can choose every six hours or weekly) and on demand. For outsiders it runs when a public passport's page is opened: /verify-passport recomputes on every open, and /v/<agent-id> is refreshed at least every five minutes. A private passport gets no chain check for outsiders.
Retired hash schemes. A chain_hash written under an older canonical text can never recompute under this one. Such an entry is registered in bb_legacy_hash_events (event_id, agent_id, sequence_number, scheme, legacy_canonical, recorded_at, note) with the exact text that produced its hash. It is cleared only when both hold: the SHA-256 of legacy_canonical equals the stored chain_hash, and every hashed field of the live row equals the same field in that text. That can excuse a scheme, never a changed field, because blessing a change would need a SHA-256 preimage. A stored chain_hash is never rewritten to fix a mismatch: that would break the next entry's link.
On 1 November 2026 bb_legacy_hash_events is empty. Its one member, an entry written on 13 July 2026 under a JavaScript JSON.stringify scheme, was removed when its agent was deleted. The public recompute does not read bb_legacy_hash_events, so it would report a listed entry as content not intact.
The RFC 3161 timestamp: what is sent, when, and what it bounds
Every night, the heads of the agents' chains go under a timestamp from an authority APIR does not run.
- When. Every night at 00:00 UTC, one request for each organisation that has a chain.
- Leaves. The chain_hash of the newest entry of every agent in the organisation, as 64-character hex text.
- Merkle root. Sort the leaves as text. Take them in pairs; each pair becomes the SHA-256 of the two hex strings joined (128 characters, as UTF-8), written as hex. An odd leaf at the end is paired with itself. Repeat until one remains. With a single leaf, the root is that leaf.
- Sent. An RFC 3161 TimeStampReq, version 1, whose messageImprint is SHA-256 (OID 2.16.840.1.101.3.4.2.1) of the merkle_root hex text as UTF-8 with no newline, with certReq true, no nonce and no policy, sent by POST as application/timestamp-query to https://freetsa.org/tsr.
- Kept. One row for each organisation each night (an organisation admin can also ask for one on demand): merkle_root, chain_heads (the agent_id and chain_hash of each leaf), tsa_url, the whole DER TimeStampResp in base64, tsa_status (anchored or failed as written by the anchor) and anchored_at, APIR's own clock at the time. A failed request leaves a row with no token for that night.
FreeTSA says on its own page that it "provides a free Time Stamp Authority", and it publishes the certificates its tokens check against (FreeTSA).
What a valid token bounds. It proves the root, and so every head under it, existed no later than the authority's time (genTime). Each head hashes every entry before it, so every entry up to a stamped head existed, with exactly that content, by that time. It does not date an entry any earlier, it does not check the entry's own timestamp field, which APIR's clock wrote, and it says nothing about entries after the last stamp. Between stamps APIR holds the writer and could add or remove entries unseen. After a stamp, a later rewrite of anything before it would no longer match the token.
Check a token with OpenSSL against FreeTSA's certificates, using the commands in the steps below. Where to get one: for an agent with a public passport, the anchor field of GET https://podncdlwtknujadrtsxz.supabase.co/functions/v1/verify-black-box?agent_id=<id> carries head_chain_hash, the night's heads as bare hashes, merkle_root, anchored_at and the token as tsa_response_der_b64. An owner's free export carries the same for each chain in anchors.json. A private agent's stamp is readable only by its organisation until its owner exports it, and the passport, the check, the resolve answer and the Evidence Pack do not carry tokens yet. A seal's portable proof carries its token, with the heads under it, and checks offline.
An owner can export an agent's log book, free, as a zip, and hand it to anyone (added 3 November 2026). It holds events.json (every entry, with data_accessed exactly as the database prints it), anchors.json (for each chain, the newest stamped head that is an entry in the file, with that night's heads as bare hashes, merkle_root and the token), report.pdf, custody.json and manifest.json. To check one: hash each file against manifest.files. Take manifest.json without manifest_hash, signature, signature_alg and verify_hint; its compact JSON, keys in the order written, must hash to manifest_hash. Group the entries by agent_id, sort by sequence_number, recompute each chain_hash by the rule in this specification, and check that each prev_chain_hash names the entry before it. Then check each stamp as this section describes, and that its head is an entry in the file. Parse numbers exactly: 88.00 must stay 88.00, or the hash will not recompute. The manifest's HMAC uses a key APIR holds, so only APIR can check it. Only the stamp ties the file to anyone but APIR: the hashes alone show a file consistent with itself. The open-source checker apir-record (MIT, Python, no dependencies) runs these steps: apir-record verify-bundle for an export, apir-record verify-anchor for a public agent's stamp. It does not check the authority's signature itself; it prints the OpenSSL commands that do.
The Trust Passport: signed bytes, signature, status and keys
The signature covers eleven fields, in a fixed order, as compact JSON.
- passport_id: string.
- agent_id: string, the agent's UUID.
- org_id: string, the owning organisation's UUID.
- trust_score: number or null, as a JavaScript number: 81.00 is written 81, and 77.80 is written 77.8.
- trust_grade: string or null: A, B, C, D, F or NR.
- risk_tier: string or null: minimal, low, moderate, elevated or critical.
- frameworks_passed: array of strings, sorted; [] when there are none.
- genome_hash: string or null, the hash the Trust Genome scorer records for the assessment. Version 1 does not define how it is computed.
- issued_at, expires_at: strings or null, ISO 8601 in UTC with milliseconds and a Z, as JavaScript's Date.toISOString() prints them. Microseconds in the stored value are cut, not rounded.
- methodology_version: string or null, for example 2.2.0.
The signed message is the UTF-8 of JSON.stringify of an object with exactly those keys in exactly that order: no spaces, JavaScript string escaping. It is not RFC 8785 (JCS). The signature is Ed25519 as RFC 8032 defines it (pure Ed25519, no pre-hash), 64 bytes, stored and published as base64url without padding.
- Issue. A passport issued or renewed from the Trust Genome runs 90 days. The signer refuses a score below 0.01, one more than 0.5 from the agent's Trust Genome, or an agent with no genome, and pulls such a row from public view. A passport is signed when it is issued and is not re-signed automatically when its row changes, so a later change to a signed field makes the signature fail. APIR holds the key and the signer has a re-sign path (for key rotation), so a valid signature shows what APIR signed, not that APIR never signed another version.
- Status. active, suspended, revoked, expired or superseded. A renewal in the last 14 days replaces the old passport, which becomes superseded, not expired.
- Revocation. The status becomes revoked and revoked_at is normally set. There is no signed revocation list: status is read live from APIR. The free check answers REVOKED. GET /api/verify-passport answers 404, Passport not found, for a revoked passport, because revoked rows are not publicly readable, so a 404 there is not proof a passport never existed.
- Rotation. Registering a new key retires the active one. Retired keys stay published in jwks.json and did.json, so older passports still verify; a revoked key is withdrawn. No key has been rotated as of 1 November 2026.
The credential envelope. With the header Accept: application/vc+ld+json, GET https://apir.ai/api/verify-passport?id=<passport_id> returns the passport as a credential: @context [https://www.w3.org/ns/credentials/v2, https://schema.org], type [VerifiableCredential, APIRTrustPassport], id, issuer {@type Organization, name, url}, issuanceDate, expirationDate, credentialSubject (passport_id, agent_id, agent_name, trust_score, trust_grade, risk_class, insurance_score, frameworks_compliant, methodology_version, chain_verified: despite its name, the result of APIR's signature check, not of the log book), proof, and credentialStatus {type apir/PassportStatus, status}.
The proof is type ApirPassportProof2026, cryptosuite apir-eddsa-fixed-order-2026, proofPurpose assertionMethod, verificationMethod did:web:apir.ai#<kid>, and proofValue the base64url signature above. The proof also carries created, canonicalization, verify and reVerified (APIR's own signature check), none of them signed. It is not a DataIntegrityProof and not eddsa-jcs-2022: that W3C suite canonicalises the document with JCS (RFC 8785) before it hashes and signs (Data Integrity EdDSA Cryptosuites). Nor is the envelope a conformant VC 2.0 credential today: the VC 2.0 data model requires the issuer to be a URL or an object with an id, and renamed issuanceDate and expirationDate to validFrom and validUntil (Verifiable Credentials Data Model v2.0). A general VC library will not verify it. The steps below will.
Defined, not yet used: the same key can sign an agent ID photo, over JSON.stringify of {artifact_type: "agent_id_photo", agent_id, org_id, sha256, issued_at} in that order, with issued_at as Date.toISOString() prints it.
The mandate: what is signed, and by whom
The owner writes it. APIR signs what the owner wrote.
- mandate_id: string, the mandate's UUID.
- org_id: string.
- principal_type: string or null; human for a mandate written on the connect page.
- principal_label: string or null; the writer's name and organisation, in the form "<name> for <organisation>".
- grantee_agent_id: string or null, the agent it covers.
- parent_mandate_id: string or null; null for a root mandate, set when authority is delegated from another mandate.
- scope: an object with keys in this order: actions (sorted strings), targets (sorted strings; * means anywhere), value_cap (number or null; 0 means it may not move money) and currency (string or null).
- issued_at, expires_at: ISO 8601 in UTC with milliseconds and a Z, as for the passport.
The signed message is the UTF-8 of JSON.stringify of that object in that order, signed with the same Ed25519 key and published as base64url. A mandate written on the connect page runs 3, 6 or 12 months; writing a new one there revokes the last one, and the old row is kept. Where authority is delegated from one mandate to another it may only narrow, and the root must be a human.
A mandate lists what is allowed; anything not on it, the agent may not do. A valid signature proves APIR recorded this mandate, as written, under the key published for did:web:apir.ai. It does not prove the mandate was wise, and it does not prove a permission still held when an action ran: check at the point of action.
A stranger reads the mandate on file through the free check: its reference, allowed actions, value cap, expiry and whether it is signed. The signature, the full signed fields and the mandate's earlier versions are not on a public endpoint yet, and an owner can replace a mandate, so a stranger cannot check a mandate signature offline today. APIR confirmed the form on 1 November 2026 by recomputing one of its own mandates against the published key.
Seals and the portable proof
A seal is one log book entry, and its proof carries everything needed to check it without APIR.
- A seal is an action entry on the log book of APIR's Registrar agent. Its seal_id is that entry's row id.
- input_hash is the SHA-256 of the declaration's canonical text: compact JSON.stringify of {declarant: {name}, declared_at, files, identifiers, kind, method, statement, title, version} in that key order, with files as {name, sha256, size} sorted by sha256, and identifiers with sorted keys and empty values dropped.
- output_hash is the SHA-256 of the files' SHA-256 hashes, lowercase, sorted and joined with a newline, with no newline at the end.
- A later act on the seal (a statement, process files, an attestation, a contest, an answer, a review or an Evidence Pack) is its own entry, whose input_hash is the SHA-256 of the act as compact JSON with object keys sorted at every depth.
The portable proof is GET https://apir.ai/api/seal/<seal_id>/proof, format apir-seal-proof/1. It carries format, issued_by, seal_id, seal_url, attestation, recorded_at, declaration_canonical, chain (from the seal's entry to the first stamped head at or after it, each entry with sequence_number, chain_hash and canonical), anchor (anchored_at, head_chain_hash, head_sequence_number, merkle_root, chain_heads, tsa_url, tsa_response_der_b64, tsa_certificates and both rules in words), not_yet_anchored, how_to_check, what_it_proves and what_it_does_not. APIR refuses to issue a proof whose entries do not recompute.
Two checkers read it: verify-seal, a page you can save once and run offline, and apir-seal.mjs, a Node command with no dependencies. Each checks that the declaration and its file list match the sealed entry; that every entry from the seal to the stamped head recomputes and links in sequence; that the head is among that day's heads and the heads rebuild the root; that the token was granted and names the SHA-256 of the root text; that the token's time agrees with anchored_at to within 15 minutes and comes after the declaration and the recording; that later acts on the seal inside the stamped segment recompute; and that each file you give is a sealed or process file, byte for byte. The token's own signature is checked with OpenSSL.
Anyone holding a file can ask whether it is sealed, free and without uploading it, at apir.ai/seal/check or with GET https://apir.ai/api/seal/check?sha256=<hex>. The answer is on_record, near_match or not_on_record. APIR sealed the declaration and the files. APIR did not determine how the work was made.
How to verify each object, step by step
Every command here was run on 1 November 2026 and gave the result shown.
1. Fetch the keys
The JWK Set and the DID document. Both list the same key.
curl -s https://apir.ai/.well-known/jwks.json # {"keys":[{"x":"a8G-EOhBbTIOAxqClk7hqFTthIGXehWTFDwMOOC_B9M","alg":"EdDSA","crv":"Ed25519","kid":"apir-pp-a8G-EOhBbTIO","kty":"OKP","use":"sig"}]} curl -s https://apir.ai/.well-known/did.json2. Ask APIR about a passport
APIR recomputes the signature against the published key and says so. The second form returns the credential envelope; the third is the free check, with the mandate on file.
curl -s 'https://apir.ai/api/verify-passport?id=TP-SPOEBUILDER-MVGFML6R' # {"valid":true, ... "signature_verified":true, ...} curl -s -H 'Accept: application/vc+ld+json' 'https://apir.ai/api/verify-passport?id=TP-SPOEBUILDER-MVGFML6R' curl -s 'https://apir.ai/api/check-agent?q=TP-SPOEBUILDER-MVGFML6R' # {"verdict":"VERIFIED", ...}3. Check the passport signature yourself, with OpenSSL
OpenSSL 3 and xxd. The payload is test vector 4 below. The public key is x with the fixed 12-byte Ed25519 key prefix in front.
printf '%s' '{"passport_id":"TP-SPOEBUILDER-MVGFML6R","agent_id":"d25e0c5b-2861-4bc3-a330-c5c9427a86e5","org_id":"f3e11999-4e51-4b2e-8747-648e14d1de45","trust_score":81,"trust_grade":"B","risk_tier":"moderate","frameworks_passed":[],"genome_hash":"82ead53ea906169971be0c9d3d75621807d76fc5f87d54e92c690ebad1681112","issued_at":"2026-10-20T08:39:29.881Z","expires_at":"2027-01-18T08:39:29.881Z","methodology_version":"2.2.0"}' > payload.txt printf '302a300506032b6570032100%s' 6bc1be10e8416d320e031a82964ee1a854ed8481977a1593143c0c38e0bf07d3 | xxd -r -p > pub.der openssl pkey -pubin -inform DER -in pub.der -out pub.pem printf '%s==' '-GX_aK4Gsp7TGUf6bRxH-Jr21KPQ7YUS4lJwa6aR7xFCFCCLhdcsRGmM5M1x4IBnXMWMcDuYgbTUYXsZhy1uDg' | tr '_-' '/+' | base64 -d > sig.bin openssl pkeyutl -verify -pubin -inkey pub.pem -rawin -in payload.txt -sigfile sig.bin # Signature Verified Successfully4. Or with Node
The same check with Node's own crypto module. Change one character of the payload and it prints false.
const c = require('node:crypto'); const payload = '{"passport_id":"TP-SPOEBUILDER-MVGFML6R","agent_id":"d25e0c5b-2861-4bc3-a330-c5c9427a86e5","org_id":"f3e11999-4e51-4b2e-8747-648e14d1de45","trust_score":81,"trust_grade":"B","risk_tier":"moderate","frameworks_passed":[],"genome_hash":"82ead53ea906169971be0c9d3d75621807d76fc5f87d54e92c690ebad1681112","issued_at":"2026-10-20T08:39:29.881Z","expires_at":"2027-01-18T08:39:29.881Z","methodology_version":"2.2.0"}'; const key = c.createPublicKey({ key: { kty: 'OKP', crv: 'Ed25519', x: 'a8G-EOhBbTIOAxqClk7hqFTthIGXehWTFDwMOOC_B9M' }, format: 'jwk' }); const sig = Buffer.from('-GX_aK4Gsp7TGUf6bRxH-Jr21KPQ7YUS4lJwa6aR7xFCFCCLhdcsRGmM5M1x4IBnXMWMcDuYgbTUYXsZhy1uDg', 'base64url'); console.log(c.verify(null, Buffer.from(payload, 'utf8'), key, sig)); // true5. Read the log book check
APIR recomputes the log book on its own side when a public passport's page is opened: apir.ai/verify-passport recomputes on every open, and apir.ai/v/<agent-id> is refreshed at least every five minutes. It is APIR checking its own record and telling you. A private passport gets no chain check for outsiders, and an agent's events are not public: recomputing them offline takes the export from its owner. To recompute entries yourself, use a seal's portable proof, as in the next step.
6. Check a seal offline
Download the proof once; the check needs no APIR server.
curl -s -o seal.proof.json https://apir.ai/api/seal/9218f8fd-d732-4666-8c76-94d1571a1b74/proof curl -sO https://apir.ai/proof/apir-seal.mjs node apir-seal.mjs verify seal.proof.json --require-anchor --tsr-out tsr # seven PASS lines, then: # CHECKS without APIR. Dated by the authority 2026-09-19 00:00:07 UTC.7. Check the timestamp token with OpenSSL
root.txt holds the merkle_root as text with no newline, so -data hashes exactly what was sent. FreeTSA documents the same form of the command on its own page.
cd tsr curl -sO https://freetsa.org/files/tsa.crt curl -sO https://freetsa.org/files/cacert.pem openssl ts -verify -in seal.tsr -data root.txt -CAfile cacert.pem -untrusted tsa.crt # Verification: OK # (OpenSSL 3.5 first warns that tsa.crt is not a CA certificate; that is expected.) openssl ts -reply -in seal.tsr -text # Status: Granted. Hash Algorithm: sha256 Time stamp: Sep 19 00:00:07 2026 GMT8. Check a file against the seals
The fingerprint of the specimen Triage Verdict PDF, sealed in the proof above.
curl -s 'https://apir.ai/api/seal/check?sha256=1cc5be7e5f2271bc2f46c1a2dd4231978164910c7251a0a00c1ee0f67cba098f' # {"ok":true, ... "result":"on_record", ...}
Test vectors, recomputed
Real records of APIR's own agents. Vectors 2 and 3 come from a proof anyone can download; vector 1 is published here, because no public endpoint returns it yet. Each was recomputed in Node and with sha256sum on 1 November 2026 and matched.
Vector 1: the opening entry of a chain
Sequence 1 of the log book of SPOE Builder, an APIR agent with a public passport (open it): its registration. prev_chain_hash is null. The canonical text, then its SHA-256, then the command.
{"agent_id": "d25e0c5b-2861-4bc3-a330-c5c9427a86e5", "timestamp": "2026-10-19T03:29:04.922129+00:00", "event_type": "registration", "input_hash": null, "output_hash": null, "data_accessed": {"risk_tier": "moderate", "created_by": null, "eu_ai_act_class": null}, "prev_chain_hash": null, "sequence_number": 1, "reasoning_summary": "Agent registered: SPOE Builder"} # chain_hash 41f5f81b43055fa07c53bf95f3c087fc96212b1316a921a317c32448db6acf0f printf '%s' '{"agent_id": "d25e0c5b-2861-4bc3-a330-c5c9427a86e5", "timestamp": "2026-10-19T03:29:04.922129+00:00", "event_type": "registration", "input_hash": null, "output_hash": null, "data_accessed": {"risk_tier": "moderate", "created_by": null, "eu_ai_act_class": null}, "prev_chain_hash": null, "sequence_number": 1, "reasoning_summary": "Agent registered: SPOE Builder"}' | sha256sumVector 2: a sealed entry, from a proof anyone can download
Sequence 2 of the Registrar agent's chain: the seal over APIR's specimen Triage Verdict at apir.ai/seal/9218f8fd-d732-4666-8c76-94d1571a1b74. Its canonical text is 1,822 bytes with nested data_accessed, so take it from the proof rather than retyping it. Its input_hash is the SHA-256 of declaration_canonical, and its output_hash is the SHA-256 of the one file's hash as text.
curl -s -o seal.proof.json https://apir.ai/api/seal/9218f8fd-d732-4666-8c76-94d1571a1b74/proof node -e "process.stdout.write(require('./seal.proof.json').chain[0].canonical)" | sha256sum # 26ebcf3da8bfdf05b029fd5be2541e8602688eda1c0a96ab453ef9f9040d12f5 # The text begins: # {"agent_id": "88f94b9c-47da-4bd5-89b1-97218d8f6c4f", "timestamp": "2026-09-18T07:30:09.596951+00:00", "event_type": "action", # "input_hash": "ad52d7180e45f8e526901ec6c322dce8893023a3076054d43188a38211f39bc5", # "output_hash": "8ebf47935542ab5a90a7efd5c973846c75d6ca7a8e4287aad2b930ae42d47b60", "data_accessed": {"attestation": ... # and ends: # "prev_chain_hash": "fedbbb9cd3fca4362046949650a6fa1ccad8473cf04a352b06b2ad4930e2dcc9", "sequence_number": 2, "reasoning_summary": "Seal: ..."}Vector 3: the Merkle root and the timestamp
The same proof carries the heads stamped on 19 September 2026. Rebuilt by the Merkle rule they give this root. The SHA-256 of the root text is the imprint FreeTSA signed.
# merkle_root fa143627a9664ddd76b5d900c2d026ae61f4dd0a36f6836ff7379b46ba11ef8b printf '%s' fa143627a9664ddd76b5d900c2d026ae61f4dd0a36f6836ff7379b46ba11ef8b | sha256sum # 593e4bdae6d86fc77a8cbe70221d41f00f21edf9a5b7ccf0749125cb3de5b974 (the messageImprint in the token) # genTime in the token: 2026-09-19 00:00:07 UTC # anchored_at in the proof: 2026-09-19T00:00:07.246282+00:00Vector 4: a passport signature
The live passport TP-SPOEBUILDER-MVGFML6R. The payload rebuilt from its fields, the signature, and the key. Changing trust_score from 81 to 82 makes the check fail.
# payload (410 bytes, UTF-8) {"passport_id":"TP-SPOEBUILDER-MVGFML6R","agent_id":"d25e0c5b-2861-4bc3-a330-c5c9427a86e5","org_id":"f3e11999-4e51-4b2e-8747-648e14d1de45","trust_score":81,"trust_grade":"B","risk_tier":"moderate","frameworks_passed":[],"genome_hash":"82ead53ea906169971be0c9d3d75621807d76fc5f87d54e92c690ebad1681112","issued_at":"2026-10-20T08:39:29.881Z","expires_at":"2027-01-18T08:39:29.881Z","methodology_version":"2.2.0"} # passport_signature (base64url, 64 bytes) -GX_aK4Gsp7TGUf6bRxH-Jr21KPQ7YUS4lJwa6aR7xFCFCCLhdcsRGmM5M1x4IBnXMWMcDuYgbTUYXsZhy1uDg # key apir-pp-a8G-EOhBbTIO # x a8G-EOhBbTIOAxqClk7hqFTthIGXehWTFDwMOOC_B9M # hex 6bc1be10e8416d320e031a82964ee1a854ed8481977a1593143c0c38e0bf07d3 # Ed25519 verify: trueRendering examples
What PostgreSQL 17 printed on 1 November 2026, for checking a rebuild. Input on the left of each pair, jsonb text below it.
{"a":-0.00,"b":1.5e-3,"c":2.50e1,"z":2,"ab":3,"B":4,"a1":5,"é":1} {"B": 4, "a": 0.00, "b": 0.0015, "c": 25.0, "z": 2, "a1": 5, "ab": 3, "é": 1} {"x":1.50,"y":1e3,"z":-0,"w":0.1e1,"e":{},"arr":[3,1,{"bb":1,"a":2}],"nul":null,"t":true} {"e": {}, "t": true, "w": 1, "x": 1.50, "y": 1000, "z": 0, "arr": [3, 1, {"a": 2, "bb": 1}], "nul": null} 2026-09-18 07:30:09+00 -> "2026-09-18T07:30:09+00:00" 2026-09-18 07:30:09.120000+00 -> "2026-09-18T07:30:09.12+00:00" 2026-01-01 00:00:00.000001+00 -> "2026-01-01T00:00:00.000001+00:00"
What the record proves, and what it never proves
It proves what APIR signed and that the record is internally consistent. It never proves the agent was right.
- It proves that a passport or mandate which verifies was signed by the key published for did:web:apir.ai, and that no signed field has changed since APIR signed it.
- It proves that a log book chain which verifies is internally consistent: no entry was changed or removed from the middle without its own hash or the next link failing. On its own it does not rule out the newest entries being removed or replaced (someone with owner rights on the database could switch the triggers off); only a head stamped or saved outside APIR before the change shows that.
- It proves to whoever holds the token, that every entry up to a stamped head existed, with that exact content, by the time FreeTSA signed; a later rewrite of anything before the stamp would no longer match it. An outsider can make this check on a seal's portable proof, on the stamp of an agent with a public passport, and on an owner's export.
- It proves that a sealed declaration covered exactly those files, byte for byte, by the stamp that followed it.
- It never proves the record is complete. The entries are what the agent and its owner put on the record, whether the agent runs on APIR or anywhere else (the owning organisation can record events for its own agents). The log book proves what was recorded and shows any later change, not that the record is complete; an action never recorded leaves no entry.
- It never proves the agent was right, safe, honest or competent, and it claims no skill or experience for any agent.
- It never proves the mandate was wise, or that a permission still held when an action ran. The mandate is written by the agent's owner; APIR signs what was written.
- It never proves an entry's own timestamp beyond APIR's clock. APIR operates the writer, the database and the signing key. A later rewrite of anything before the last stamp would no longer match that stamp, which covers nothing written since.
- It never proves the newest entries were not removed or replaced after the last stamp, unless someone outside APIR holds a later head.
- It never decides whether a court, a regulator or an insurer accepts the record. That is their call. APIR gives no legal advice, is not an insurer or a broker, and certifies no one.
- It says nothing about APIR's own security controls. APIR holds no SOC 2, no ISO 27001 and no penetration test report.
We don't promise results. We prove whatever happens.
What a stranger cannot check from public data yet
Version 1 describes what runs, including the parts not yet open to an outside check.
- No public endpoint returns an agent's log book entries. A stranger recomputes them independently only from the export the agent's owner hands them, or where a seal's portable proof carries their canonical text. Otherwise the recompute runs on APIR's side when a public passport's page is opened.
- Timestamp tokens are public only for agents with a public passport (the anchor field of verify-black-box) and inside an owner's export. An agent's Evidence Pack issued since 4 November 2026 carries its token too. The passport, the check, the resolve answer and the fleet pack do not, and a private agent's stamp is otherwise readable only by its organisation. A seal's proof carries its token, with the heads under it.
- An export holds what its owner chose to export. A window or a filter can leave entries out, and no file shows what an agent did not report.
- Audit entries written up to 18:00 UTC on 2 November 2026 call the hourly monitor's reading a trust score: trust_score in the summary, trust_score_after in data_accessed. It is 100 minus the monitor's penalties, not a rating. A rating comes only from the Trust Genome, and an agent without one is Unrated. Later entries call it monitor_reading. The older entries keep their words, because rewriting them would break their hashes.
- A mandate's signature, its full signed fields and its earlier versions are not on any public endpoint.
- The 70 floor is enforced where a passport is first issued from the Trust Genome; the renewal sweep and the SDK issue route do not enforce it, and some signed passports were issued below 70.
- The writer (bb_record) and the public check do not pin the time zone; they rely on the database default, UTC. The canonical function and the seal proof pin it.
- GET /api/verify-passport answers 404 for a revoked passport instead of saying revoked; the free check says REVOKED.
- The credential envelope is not a conformant W3C VC 2.0 credential, and its proof is APIR's own suite, not eddsa-jcs-2022.
- There is no signed revocation list. Status is read live from APIR.
- The table refuses edits and deletes through triggers, so someone with owner rights on the database can switch them off. APIR's service role can still insert rows directly.
- passport_hash in the public envelope is a random value set at issue, not a hash of the passport, and it is not signed. Ignore it when verifying.
Each line is a fact about version 1, not a promise about the next.
Version 1, 1 November 2026
What is frozen, and how a change would be made.
- The canonical text of a log book entry is frozen: the nine fields, PostgreSQL jsonb text in UTC, SHA-256. It is never changed. A retired form goes in bb_legacy_hash_events with its exact text, and a stored chain_hash is never rewritten.
- The passport and mandate payloads are fixed-order JSON. Changing either would break every signature already issued, so a change would be a new, named payload beside the old one, never an edit of it.
- The seal proof is format apir-seal-proof/1. A change to it gets a new format name; the checkers read apir-seal-proof/1.
- A later version of this page will say what changed, and when.
For the plain-language version, read what an AI agent evidence record is and who gives AI agents a tamper-evident work history.
Questions people ask
What is the APIR agent record specification?
The exact, byte-level description of the record APIR keeps for each AI agent, published so anyone can build their own verifier: the Ed25519-signed Trust Passport under did:web:apir.ai, the signed mandate, and the hash-chained log book whose chain heads are timestamped every night by an outside RFC 3161 authority, with the tokens held by APIR. Version 1 is dated 1 November 2026 and names what a stranger cannot yet check from public data.
How is chain_hash computed for an APIR log book entry?
chain_hash is the lowercase hex SHA-256 of the UTF-8 text PostgreSQL prints for a jsonb object of nine fields: agent_id, sequence_number, event_type, input_hash, output_hash, reasoning_summary, data_accessed, timestamp and prev_chain_hash, in UTC. jsonb prints shorter keys first, then bytewise, with a comma and a space between members and a colon and a space after each key, numbers as PostgreSQL numeric text, and timestamps to the microsecond with trailing zeros dropped.
Can I verify an APIR Trust Passport without calling APIR?
Yes, once you hold its public answer from /api/verify-passport, which prints all eleven signed fields. The signature is Ed25519 over a fixed-order JSON payload of those fields, checkable with OpenSSL 3 or any Ed25519 library against the key at apir.ai/.well-known/jwks.json. Whether it is still current is read live from APIR.
Is the APIR passport an eddsa-jcs-2022 Data Integrity proof?
No. Its proof is APIR's own suite, ApirPassportProof2026: Ed25519 over a fixed-order JSON payload, with the signature in base64url. eddsa-jcs-2022 canonicalises the document with JCS (RFC 8785) first, and the envelope does not yet meet the VC 2.0 data model, so a general VC library will not verify it. Use the steps in the specification.
What does APIR's RFC 3161 timestamp prove?
That every log book entry up to a stamped chain head existed, with exactly that content, no later than the time FreeTSA signed, so a later rewrite of anything before a stamp would no longer match it. Every night at 00:00 UTC, APIR sends FreeTSA the SHA-256 of a Merkle root over the heads of the agents' chains. The token does not date an entry earlier, does not check the entry's own timestamp, and covers nothing written after the last stamp. An outsider can check a stamp from a seal's portable proof, from the anchor field of a public agent's verify-black-box answer, or from an owner's export. All three check offline.
Can APIR change or delete a log book entry?
The table refuses both, from APIR's service role too: UPDATE, DELETE and TRUNCATE raise an error (deletes since 4 November 2026). Someone with owner rights on the database could switch those triggers off, so the check does not rest on them: a changed field stops its hash recomputing, a deleted entry in the middle shows as a gap and a broken link, and newest entries removed or replaced show only against a head that was already timestamped or saved outside APIR, and APIR holds the timestamp tokens.
Is the APIR agent record an open standard?
No. It is APIR's own specification of what APIR runs, published so anyone can check the record. There is no standards body or IETF draft behind it, and the specification itself carries no licence. The offline seal checkers are published at apir.ai/proof/verify-seal and apir.ai/proof/apir-seal.mjs. The checker for exports, stamps and seals is MIT-licensed at github.com/apir-ai/apir-record.
Does APIR hold SOC 2 or ISO 27001?
No. APIR holds no SOC 2, no ISO 27001 and no penetration test report. The record is built so its integrity can be checked without trusting APIR's controls, and the specification says where that holds today and where it does not yet.
Sources
- RFC 3161, Time-Stamp Protocol (TSP) https://www.rfc-editor.org/rfc/rfc3161
- RFC 8032, Edwards-Curve Digital Signature Algorithm (EdDSA) https://www.rfc-editor.org/rfc/rfc8032
- RFC 8037, CFRG curves in JOSE (the OKP key type) https://www.rfc-editor.org/rfc/rfc8037
- RFC 8785, JSON Canonicalization Scheme (not used by the passport signature) https://www.rfc-editor.org/rfc/rfc8785
- W3C, Verifiable Credentials Data Model v2.0 https://www.w3.org/TR/vc-data-model-2.0/
- W3C, Data Integrity EdDSA Cryptosuites v1.0 https://www.w3.org/TR/vc-di-eddsa/
- did:web Method Specification https://w3c-ccg.github.io/did-method-web/
- PostgreSQL 17 documentation, 8.14 JSON Types https://www.postgresql.org/docs/17/datatype-json.html
- FreeTSA, the time-stamping authority https://freetsa.org/index_en.php
- APIR public signing keys (JWKS) https://apir.ai/.well-known/jwks.json
- APIR DID document (did:web:apir.ai) https://apir.ai/.well-known/did.json
- APIR passport verification API https://apir.ai/api/verify-passport
- Check an AI agent, free https://apir.ai/check-agent
- A seal's portable proof (the specimen seal) https://apir.ai/api/seal/9218f8fd-d732-4666-8c76-94d1571a1b74/proof
- verify-seal, the offline seal checker https://apir.ai/proof/verify-seal
- apir-seal.mjs, the command-line seal checker https://apir.ai/proof/apir-seal.mjs
- apir-record, the open-source checker (MIT) https://github.com/apir-ai/apir-record
Check a live agent's papers and log book yourself, free and with no account.