Sandbox receipts are signed test JWS objects. COSE serialization and embedded-manifest verification
below are target contract features. Use the quickstart for the supported JSON/JWS
flow; test receipts do not authorize real talent content.
GET /v1/receipts/{receipt_id}.
A receipt comes in two forms with the same claims: a compact JWS (jws), and a COSE_Sign1
structure (cose) for tools that work in CBOR.
Header
typ is always tlp-receipt+jwt. A receipt has no exp and no aud: it is a record anyone may
check, not a credential.
Claims
Example payload
The receipt for a line of speech in Maya Okafor’s voice, generated by Northlight under its own voice license for branded content on audio and the web in the US, and allowed after its generation check:platform and licensee are the same account.
A check receipt has kind: "check", a chk_ subject and no artifact_sha256. A deny has a
receipt too, so a platform can prove it refused.
This example is unbound. Had Northlight declared its C2PA claim-signing certificate, tlp would
also carry, and the receipt object would repeat as c2pa_signer:
COSE form
cose on the receipt object is a base64
COSE_Sign1 structure (RFC 9052, CBOR tag 18) carrying the same claims:
In CBOR diagnostic notation:
{"iat":1792260363,"iss":"https://app.reflections.ai",.
To verify it, build the Sig_structure ["Signature1", protected, h'', payload], encode it as
CBOR, and verify the signature over those bytes with the JWKS key whose kid matches. The payload
then parses as the same JSON claims as the JWS.
Verify a receipt
1
Check the header
alg is ES256 and typ is tlp-receipt+jwt. A receipt with a kid that starts with
rfl-test- was made in test mode and proves nothing about live content.2
Check the signature
Verify it against the key with the same
kid in the
JWKS. Retired keys stay published, so an old receipt
keeps verifying. Do not reject a receipt for its age.3
Check the claims
iss is https://app.reflections.ai. Read tlp.decision, tlp.assurance, tlp.talent and
tlp.use to see what was decided and on what evidence. Each entry in tlp.licenses names the
signed revision behind one talent’s license. A party that can read the license, such as the
licensee, compares each entry’s terms_digest with the terms_digest of the revision it names,
from GET /v1/licenses/{license_id}/revisions/{revision_id}.4
Match it to the content
tlp.artifact_sha256 is the digest of the bytes the platform submitted, before it embedded the
C2PA manifest, so a delivered file has a different digest. To match a file to its receipt, send
the file to POST /v1/verifications, which
reads an embedded manifest first, then matches by exact digest, then by fingerprint and
watermark. For a receipt read from a manifest, compare tlp.c2pa_signer.thumbprint with the
SHA-256 of the manifest’s claim signer certificate (DER). A receipt without tlp.c2pa_signer
proves the decision, not that this file carries it
(TLP-RCP-16).5
Read the live status
The signature proves a past decision. Whether the output may still be used is in
use_status
from GET /v1/receipts/{receipt_id}.Live status
tlp.licenses, with one difference in
spelling: each entry names its revision license_revision, where the claim names it revision.
Anyone can read a receipt, with any key or none, and see it as a page at
https://app.reflections.ai/r/{receipt_id}. Discovery publishes that template as receipt_page;
receipt_endpoint remains the JSON route at /v1/receipts/{receipt_id}. A test page is explicitly
fictional and grants no production rights. An unavailable human page contains no receipt facts;
protocol clients use the JSON endpoint’s HTTP 503 response.
Related
C2PA assertion
Where the receipt goes in the output’s manifest.
Verifying content
Verification by receipt, manifest or file.
Provenance
Receipts, manifests, fingerprints and verification.
Receipts in the Standard
Receipt format, signing keys and key rotation.