The act log · the format
The entry, the COSE receipt and the switch pack, as formats
- An agent asks
- Give me everything GAIP holds for my handle, in a form I can check and carry.
- GAIP answers
- The switch pack: every entry, GAIP's public keys and the verifier steps, signed as one pack. Facts as recorded, not a finding.
- The call
GET /v1/free/acts/switch-packwith headerX-GAIP-Continuity, or the MCP toolgaip_switch_pack
One entry per act an agent did, promised, checked or was told, kept as hashes bound to the person's continuity handle, signed and chained. This page is the format: the entry schema, the COSE receipt, the switch pack and how to check each offline. Free. No affiliate links. Ever.
- Read the entry schema. Every field, what it holds and what it never holds.
- Fetch a COSE receipt or a switch pack. One key, one digest, a COSE signature beside the JSON one; one pack for the whole record.
- Check it offline. The published keys, the hash rule and the standalone verifier.
BOUGHT
Submitted 2026-10-10 22:38 UTC (synthetic example)
- Kind
- BOUGHT
- Submitted by
- the agent, as recorded
- Counterparty
- shop.gaipagents.com
- subject_sha256
- c8193e04f8caea93ebf1f9eb57c87ab6a20317201faf9f102a4383e20d61898a
- content_sha256
- b809c719c5156aebd53890c1a219aef810690c85977a33c55833c1d12fbffcae
- Amount declared
- 24.00 GBP (as declared, not checked)
- Actor's handle
- bound by keyed hash (actor_handle_hash)
- entry_sha256
- 4b25514bbf9b48c756b74e6d321551bc3a9e0ac33564493ebce94126ba0d93fc
Worked example: unsigned, synthetic
GAIP records that an agent submitted this entry at the stated time, and, where shown, what a public page showed when GAIP read it. GAIP did not witness the act and does not say it happened as described.A worked example of one entry as the receipt page shows it (synthetic, unsigned).
The formats everything else about this product
The entry: gaip.act.v1
GAIP's signed, chained record that an agent submitted this entry, bound to its continuity handle by keyed hash, with the hashes it gave and, where asked, GAIP's own reading of a public page. Not a statement that the act happened as described. GAIP does not say the act happened, that any party did what the entry describes, or what the hashes stand for; an entry is the submission as recorded, never a finding about anyone.
| Field | What it holds |
|---|---|
schema_version | gaip.act.v1 |
service / evidence_class / brand | ACT_LOG / ACT / GAIP |
kind | one of BOUGHT, BOOKED, CANCELLED, SUBSCRIBED, PAID_DECLARED, AGREED, DELIVERED, REFUSED, TOLD, ASKED, READ, CHECKED, REVOKED, NOTICE_MATCHED |
actor_handle_hash | the keyed hash (HMAC-SHA256 under GAIP's key, label gaip.act.handle_hash.hmac.v1) of the continuity handle that bound the entry; never the handle |
recorded_at_utc / observed_at_utc | when GAIP recorded the submission (ISO 8601, UTC) |
submitted_by | AGENT (the agent's submission) or GAIP (GAIP's own entry, such as a recall-watch match) |
subject_sha256 / content_sha256 | SHA-256 digests the agent computed; GAIP never receives the text |
counterparty | {kind: host | handle_hash | ref_hash, value}: a public host, a handle's hash, or the keyed hash of the agent's opaque reference (label gaip.act.counterparty.hmac.v1) |
mandate_sha256 | optional: the keyed hash (HMAC-SHA256 under GAIP's key, label gaip.act.mandate_ref.hmac.v1) of the SHA-256 of the mandate the agent said it acted under; never the agent's hash itself |
amount_declared / currency | optional: a decimal string and an ISO 4217 code, recorded as declared |
page_read | optional: {url, seller_domain, read_at_utc, read_status, values {name, price, currency, availability, variant}, sha256, sources_read}: what a public page showed when GAIP read it once; the page is not kept |
product_ids | optional, BOUGHT only: {count, kept_as}; the identifiers live as keyed hashes in the recall watch |
cosigner | optional: {key_id, signature over entry_sha256, host, key_state KEY_KNOWN | KEY_NOT_KNOWN}; as presented |
sentence / what_this_is | the fixed sentence and what an entry is |
published / data_classification / content_retained | false / PSEUDONYMOUS / false |
receipt_id / observation_id | ac-<12 base32 characters of entry_sha256> and act:<receipt_id> |
entry_sha256 | SHA-256 over the canonical JSON: keys sorted, no spaces (separators "," and ":"), every non-ASCII character escaped as \uXXXX (ASCII output), then UTF-8 bytes of the entry without receipt_id, observation_id, entry_sha256, signature, signature_status, cosigner, handle_hash_retention, content_retention, erasure (a view also adds in_chain, record_sha256, source_kind, source_receipt, source_url, re_emitted, source_schema, removed before hashing) |
signature | {alg Ed25519, kid, sig (base64url), purpose gaip.act.entry_sha256.v1, signed_message} |
A worked example (synthetic, unsigned, GAIP's own test range; no real handle or act):
{
"actor_handle_hash": "c3e729c02c14850bfff168df8057a4b1087c68b11c8f15f0decb9df83025ee7e",
"affiliate_links": false,
"amount_declared": "24.00",
"brand": "GAIP",
"content_retained": false,
"content_sha256": "b809c719c5156aebd53890c1a219aef810690c85977a33c55833c1d12fbffcae",
"correction_route": "https://www.gaipagents.com/corrections",
"counterparty": {
"kind": "host",
"value": "shop.gaipagents.com"
},
"currency": "GBP",
"data_classification": "PSEUDONYMOUS",
"entry_sha256": "4b25514bbf9b48c756b74e6d321551bc3a9e0ac33564493ebce94126ba0d93fc",
"evidence_class": "ACT",
"external_effect": false,
"kind": "BOUGHT",
"mandate_sha256": null,
"observation_id": "act:ac-jmsvcs57tnem",
"observed_at_utc": "2026-10-10T22:38:00Z",
"outside_write": false,
"page_content_retained": false,
"page_read": null,
"price_gbp": 0,
"product_ids": null,
"published": false,
"receipt_id": "ac-jmsvcs57tnem",
"recorded_at_utc": "2026-10-10T22:38:00Z",
"schema_version": "gaip.act.v1",
"sentence": "GAIP records that an agent submitted this entry at the stated time, and, where shown, what a public page showed when GAIP read it. GAIP did not witness the act and does not say it happened as described.",
"service": "ACT_LOG",
"signature": null,
"signature_status": "UNSIGNED",
"subject_sha256": "c8193e04f8caea93ebf1f9eb57c87ab6a20317201faf9f102a4383e20d61898a",
"submitted_by": "AGENT",
"what_this_is": "GAIP's signed, chained record that an agent submitted this entry, bound to its continuity handle by keyed hash, with the hashes it gave and, where asked, GAIP's own reading of a public page. Not a statement that the act happened as described."
}
How an agent writes one: the MCP tool gaip_log_act or POST /v1/free/acts with
its continuity handle; the holder reads them back at GET /v1/free/acts/mine; one entry is public at
GET /v1/free/acts/<code> and /r/<code>. A page_url is read once, on your request, through Second Look's reader: public product pages only, never a cart, checkout or account page; Amazon, eBay and Etsy never; robots.txt honoured. Values and hashes are kept, never the page.
The COSE receipt: GET /v1/free/acts/<code>/receipt.cose
GAIP's Ed25519 signature over this entry's entry_sha256 as a COSE_Sign1 (RFC 9052), so a COSE verifier can check it without GAIP's JSON: the same key signs the same digest, as a separate signature over the COSE Sig_structure, not the JSON signature re-encoded. The words of the entry stay at /v1/free/acts/<code>. Served as application/cose; cose-type="cose-sign1".
- COSE_Sign1, tagged 18 (RFC 9052):
18([h'<protected header>', {}, h'<the 64 ASCII hex characters of entry_sha256>', h'<64-byte Ed25519 signature>']) - Protected header (CBOR diagnostic notation):
{1: -8, 3: "text/plain; charset=utf-8", 4: h'3c6b69643e'}: alg 1 = -8 (EdDSA, RFC 9053), content type 3, kid 4 = the key id as UTF-8 bytes (the first 16 hex characters of SHA-256 over the raw public key, as the key set names it). - Unprotected header: an empty map. Payload: the 64 ASCII hex characters of
entry_sha256. - Signature: Ed25519 over the CBOR
Sig_structure["Signature1", h'<protected header>', h'', h'<payload>'](RFC 9052 section 4.4), with no external data. - Check it with the key in /.well-known/gaip-receipt-keys.json whose
kidequals the header's kid. The JSON entry carries the same key's signature over the ASCII messagegaip.act.entry_sha256.v1:<entry_sha256>; the two are one key and one digest, each a separate Ed25519 signature over its own message. - When GAIP's signing key is not available, the route answers JSON
NOT_SIGNEDwith the sentence instead of bytes.
CBOR is encoded in the core deterministic form (RFC 8949 section 4.2.1): shortest integers, definite lengths, map keys ordered by their encoded bytes.
The switch pack: gaip.switch-pack.v1
GAIP's own export of the entries bound to this handle, as open, signed JSON: a format any agent can read and check offline against GAIP's published keys. It is GAIP's holdings only: it binds no other provider, and GAIP does not say what any other service does with it. GET /v1/free/acts/switch-pack with the X-GAIP-Continuity header, or the MCP tool
gaip_switch_pack with continuity_handle; served only to that handle, rate-limited like every read, JSON only, a format any agent can read.
| Field | What it holds |
|---|---|
schema_version | gaip.switch-pack.v1 |
continuity_ref | a one-way reference to the handle (never the handle or its token) |
entries[] | every entry bound to the handle, newest first: in_chain entries (the full gaip.act.v1 entry with record_sha256) and re_emitted rows (kind, recorded_at_utc, source_kind, source_receipt, source_url, counterparty, amount_declared, currency, mandate_sha256) |
entries_total / in_chain / re_emitted / by_kind | counts |
keys | GAIP's public keys as /.well-known/gaip-receipt-keys.json publishes them (kid, kty OKP, crv Ed25519, x, alg EdDSA, status), the kid rule and the message rule |
verifier | the steps to check the pack, each entry, the COSE receipt and the chain |
sentence / what_this_is / portability / format | the fixed sentence; what a pack is; what it binds (GAIP's holdings only); the format's address and licence |
pack_sha256 / signature | SHA-256 over the canonical JSON of the pack without the fields hash_rule names; GAIP's Ed25519 signature, purpose gaip.switch_pack.pack_sha256.v1 |
pack_sha256 is SHA-256 over the canonical JSON (sorted keys, no spaces, UTF-8) of the pack without its signature fields (pack_sha256, signature, signature_valid, signature_unavailable_reason, signature_purpose, signature_keys_url) and without the per-call fields a runtime adds after signing (status, continuity, continuity_handle, continuity_handle_authority, continuity_source_receipt_id, since_last_call, next_tool, continuity_purpose, revoked_since, receipt, receipt_id, receipt_url, report_receipt_id, next_steps, keep_watching, record_history, record_line, gaip_next_agents, gaip_outcome_follow_up, gaip_defaults_applied, checked_at_utc, notice, act_log).
Checking a pack or an entry offline
Each check shows that GAIP's key signed that digest. It shows nothing about the act itself: GAIP records that an agent submitted this entry at the stated time, and, where shown, what a public page showed when GAIP read it. GAIP did not witness the act and does not say it happened as described.
The pack:
- 1. Remove the signature and per-call fields named in hash_rule; pack_sha256 is SHA-256 over the canonical JSON (sorted keys, no spaces, UTF-8) of the pack without its signature fields (pack_sha256, signature, signature_valid, signature_unavailable_reason, signature_purpose, signature_keys_url) and without the per-call fields a runtime adds after signing (status, continuity, continuity_handle, continuity_handle_authority, continuity_source_receipt_id, since_last_call, next_tool, continuity_purpose, revoked_since, receipt, receipt_id, receipt_url, report_receipt_id, next_steps, keep_watching, record_history, record_line, gaip_next_agents, gaip_outcome_follow_up, gaip_defaults_applied, checked_at_utc, notice, act_log).
- 2. Compare the result with pack_sha256.
- 3. Rebuild the ASCII message 'gaip.switch_pack.pack_sha256.v1:<pack_sha256>' and check signature.sig (base64url, Ed25519) with the key in keys.keys whose kid equals signature.kid.
An entry:
- 1. For an entry with in_chain true: remove the unsigned fields (receipt_id, observation_id, entry_sha256, signature, signature_status, cosigner, handle_hash_retention, content_retention, erasure) and the view fields this pack adds (in_chain, record_sha256, source_kind, source_receipt, source_url, re_emitted, source_schema); SHA-256 the canonical JSON: keys sorted, no spaces (separators "," and ":"), every non-ASCII character escaped as \uXXXX (ASCII output), then UTF-8 bytes of what is left; compare with entry_sha256.
- 2. Rebuild the ASCII message 'gaip.act.entry_sha256.v1:<entry_sha256>' and check signature.sig (base64url, Ed25519) with the key whose kid equals signature.kid.
- 3. A COSE_Sign1 receipt over the same entry_sha256 is at https://www.gaipagents.com/v1/free/acts/<code>/receipt.cose (alg EdDSA, kid in the protected header, payload the 64 hex characters of entry_sha256): a separate Ed25519 signature by the same key over the COSE Sig_structure, not the JSON signature re-encoded.
- 4. An entry with re_emitted true is a view of an earlier GAIP receipt: source_receipt names it and source_url serves it; check that receipt by its own rule at https://www.gaipagents.com/receipt-verifier.
Every in-chain entry is a row of GAIP's receipts chain: https://www.gaipagents.com/v1/free/receipts/<code> gives its place and the daily anchors at /v1/free/observatory/anchors cover it. The free standalone verifier (Python and JavaScript, no dependencies, with test vectors) is at /receipt-verifier.
Licence
The formats on this page (the entry schema gaip.act.v1, the COSE receipt and the switch pack gaip.switch-pack.v1) are published under CC BY 4.0; a pack is for the handle that took it. CC BY 4.0. Counts about the log are on the
record card (record.acts). Privacy ·
Terms · Corrections · Free. No affiliate links. Ever.
GAIP records that an agent submitted this entry at the stated time, and, where shown, what a public page showed when GAIP read it. GAIP did not witness the act and does not say it happened as described.