Sign
Signing takes three steps: create an upload, accept a job with POST /v1/sign, and poll the job.
The API never accepts media bytes in a JSON request.
1. Create an upload
Section titled “1. Create an upload”POST /v1/uploads declares the exact bytes you will send:
{ "byte_length": 204800, "sha256": "<64 lowercase hex characters>", "declared_media_type": "image/jpeg", "mode": "single", "filename": "photo.jpg"}declared_media_type must be one of image/jpeg, image/png, image/webp,
image/tiff, image/avif, audio/wav, audio/mpeg, audio/mp4, or video/mp4 for the
embedded lane, one of the sign-only types image/gif,
image/jxl, image/svg+xml, image/x-adobe-dng, image/heic, image/heif,
image/heic-sequence, image/heif-sequence, video/quicktime, video/x-msvideo or
audio/flac, which are signed only with rcp_system_structural_sign_only, or one of the
sidecar media types, among them
image/x-tiff-based, image/x-riff-based, video/x-bmff-based, video/x-riff-based,
audio/aac, audio/MPA and audio/x-riff-based, whose manifest is delivered beside the file.
Check
GET /v1/capabilities for current limits
and account availability. TIFF and AVIF accept only the image scopes listed in the
introduction. Use "mode": "multipart" with a parts list for large files.
MP4 (video/mp4) accepts one H.264 video track (8-bit 4:2:0) and at most one AAC-LC audio track, in a non-fragmented file with no other tracks. HEVC, 10-bit/HDR, HE-AAC and fragmented MP4 are refused with MEDIA_CODEC_UNSUPPORTED; phone recordings are often HEVC, so re-export them as H.264/AAC first.
The 201 response contains upload_id, the parent asset_id (ast_…), expires_at, and, for
single-part uploads, an upload object with method (PUT), url, and headers.
2. Transfer the bytes
Section titled “2. Transfer the bytes”PUT the file to upload.url and send exactly the returned headers. Do not send your
Authorization header to this URL: it is a storage URL, not an API URL.
3. Complete the upload
Section titled “3. Complete the upload”POST /v1/uploads/{upload_id}/complete needs an
Idempotency-Key header. The API checks the stored bytes against the declared length and SHA-256.
It then returns the immutable asset_object_id (aob_…). A mismatch fails with
UPLOAD_CHECKSUM_MISMATCH or UPLOAD_SIZE_MISMATCH.
4. Accept a signing job
Section titled “4. Accept a signing job”curl -sS -X POST "$OP_API_BASE_URL/v1/sign" \ -H "Authorization: Bearer $OP_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d "{\"asset_id\": \"$ASSET_ID\", \"recipe_id\": \"rcp_system_sign_only\"}"| Field | Required | Description |
|---|---|---|
asset_id |
yes | The ast_… asset whose upload you completed. |
recipe_id |
yes* | A recipe from GET /v1/recipes; see Choose a mode. |
creator_id |
no | An enrolled creator identity to attribute in the content credentials, if you use them. |
watermark_algorithm |
no | The invisible watermark to embed. Only valid with a watermark recipe; see below. |
watermark_strength |
no | subtle, balanced or robust. Only valid with a watermark recipe; see below. |
fingerprint_algorithms |
no | The fingerprints kept as server-side recovery signals. Omit for the default; see below. |
ai_declaration |
no | Declares that a generative model produced the asset. Only valid with a sign-only recipe. |
mode |
no | express waits for the result; see Express. Omit for the asynchronous job. |
* recipe_id may be omitted only when the API key’s
signing profile names a default recipe.
The 202 response is a job snapshot with job_id (job_…) and state.
Many assets at once
Section titled “Many assets at once”POST /v1/sign/batch takes up to 100 of the same
request bodies as {"items": [...]} and needs the same sign:write scope. Each item is admitted
exactly as its own POST /v1/sign would be, with the same quota and errors. One item’s refusal
does not affect the others, so a 202 answer can still contain refused items.
- The response lists one result per item, in request order:
index,status(202or the error status),jobfor an admitted item anderrorfor a refused one. - Item
iis admitted under the idempotency key<Idempotency-Key>.b<i>. Replaying the same batch returns the same jobs. The batch key can be at most 196 characters, which leaves room for that suffix. - A key with a per-minute rate limit spends one request per item, all or nothing. If the limit
cannot cover the whole batch, the batch gets
429withRetry-Afterand no item is admitted. A batch larger than the key’s burst allowance can never fit, so it gets422with the largest allowed size infield_details.items. At 60 requests per minute that size is 11.
Choose a mode
Section titled “Choose a mode”Three signing modes cover the decoded media types (images, audio and video):
| Mode | recipe_id |
What the job does |
|---|---|---|
| Sign | rcp_system_sign |
Writes and signs the C2PA manifest. No watermark and no fingerprint. |
| Sign + watermark | rcp_system_watermark_then_sign_unfingerprinted |
Embeds an invisible watermark, then the manifest. No fingerprint. |
| Sign + watermark + fingerprint | rcp_system_watermark_then_sign |
A watermark, server-side recovery fingerprints, then the manifest. |
rcp_system_sign_only still signs with fingerprints and no watermark, and
rcp_system_watermark_only still watermarks without a manifest; existing integrations keep working
unchanged.
What a mode without fingerprints gives up: nothing is kept that could find your published original
from a copy whose manifest was removed, except the watermark in “Sign + watermark”. An image signed
with “Sign” carries no ISCC soft binding in its manifest; an image watermarked with a registered
TrustMark variant keeps that watermark’s soft binding. Both modes refuse fingerprint_algorithms
with HTTP 422 JOB_RECIPE_INVALID, and “Sign” refuses watermark_algorithm and
watermark_strength the same way.
Every field in this request can also be defaulted, and some restricted, per API key; see
Signing profile. A field the request names always wins
over the key’s default. A request the key’s profile does not allow is refused with HTTP 403
API_KEY_PROFILE_FORBIDS, naming the field.
Express
Section titled “Express”For JPEG, PNG, WebP, TIFF and AVIF images, "mode": "express" runs Sign + watermark +
fingerprint on the express lane (rcp_system_express_watermark_then_sign): the same watermark,
fingerprints and C2PA manifest, with the job’s steps run back to back instead of queued one by one.
The request waits for the result:
200: the job finished within the wait. The body is the job resource plusdownload, the same short-lived deliveryPOST /v1/assets/{asset_id}/downloadmints.download.urlis a bearer URL; keep it out of logs.202: the job had not finished yet. The body is the usual job snapshot; poll it or wait for the webhook, exactly as for any job.
mode applies to rcp_system_watermark_then_sign and to the express recipe itself. Any other recipe
or media type runs the ordinary pipeline and answers 202. Webhooks are delivered either way, and
the job, receipt, verification and Library entry are the same as for any Sign job. Naming
rcp_system_express_watermark_then_sign without mode runs the lane and answers 202; a key’s
signing profile can make it the default recipe.
Choose a watermark
Section titled “Choose a watermark”The watermark recipes are rcp_system_watermark_then_sign, which embeds an invisible watermark and
then writes the C2PA manifest, and rcp_system_watermark_only, which embeds the watermark without
a manifest. Both accept an optional watermark_algorithm:
- Omit it and the job uses the default for the asset’s media type, listed in the table below. A later change of default never changes a job that already exists.
- Name the recommended algorithm to opt in to the one that ranks best in our watermark benchmark. Where it is already the default, naming it runs the same algorithm as omitting the field.
- Any other value must be one of the algorithms listed for that media type in
GET /v1/capabilities, undermedia[].watermark_algorithms. A sign-only recipe refuses the field.
{ "asset_id": "ast_…", "recipe_id": "rcp_system_watermark_then_sign", "watermark_algorithm": "pixelseal"}The defaults are summarised below. Every algorithm, which media types take it, its C2PA
soft-binding status and its strength levels are on Watermarks. The table is
generated from the capability snapshot the API is built with; a deployment can enable fewer, so
GET /v1/capabilities stays the authority at run time.
| Modality | Default (field omitted) | Recommended | Selectable |
|---|---|---|---|
| audio | audioseal-mm02-4 |
audioseal-mm02-4 |
audioseal, audioseal-mm02-4 |
| image | pixelseal |
pixelseal |
pixelseal, trustmark-b, trustmark-c, trustmark-p, trustmark-q, videoseal-image |
| video | pixelseal-video |
pixelseal-video |
pixelseal-video, videoseal |
Sign-only and sidecar media carry no watermark.
Choose a watermark strength
Section titled “Choose a watermark strength”Each watermark algorithm is qualified at one strength or a short ladder of strengths. By default a
job embeds at the lowest rung and climbs only when the watermark would not be detected in the
output. watermark_strength narrows that ladder for one request:
subtleembeds at the lowest qualified strength only. If the watermark would not be detected there, the job fails rather than embedding more strongly.balancedis the default: exactly what omitting the field does.robustallows the full qualified ladder, up to the strongest rung.
No level ever uses a strength outside the algorithm’s qualified ladder. For every algorithm today
the default already allows the full ladder, so balanced and robust run the same rungs; they
differ only for an algorithm whose default is capped below its qualified maximum. For a
single-strength algorithm all three levels are the same.
{ "asset_id": "ast_…", "recipe_id": "rcp_system_watermark_then_sign_unfingerprinted", "watermark_strength": "subtle"}GET /v1/capabilities lists the exact strengths each
level allows for each algorithm, under media[].watermark_algorithms[].strength_levels. The level
applies to the algorithm the job runs: the one you name, or the media type’s default. A job keeps
the strengths it was admitted with. A sign-only recipe refuses the field with HTTP 422
JOB_RECIPE_INVALID; a media type without a watermark (for example audio/mp4) refuses it with
JOB_POLICY_REJECTED.
Choose fingerprints
Section titled “Choose fingerprints”Every recipe that decodes the asset computes perceptual fingerprints of the published bytes. They
are kept on our servers so verification can find your published original from a
copy whose manifest was removed. The optional fingerprint_algorithms list chooses which:
- Omit it and the job computes the default set for the asset’s media type, listed below. A later change of default never changes a job that already exists.
- A list replaces the default set. Name each fingerprint once, at least one, and only those
GET /v1/capabilitieslists for that media type undermedia[].fingerprint_algorithms. A refusal is HTTP 422 withFINGERPRINT_ALGORITHM_UNSUPPORTED. - ISCC Image-Code is always added for images, because it is the soft binding the manifest carries. No other fingerprint enters the manifest or any public response.
- A set that differs from the default adds a
fingerprintline to the job’s price, on the watermark schedule; see Retention and pricing. - The “Sign” and “Sign + watermark” modes compute no fingerprint and refuse the field.
{ "asset_id": "ast_…", "recipe_id": "rcp_system_sign_only", "fingerprint_algorithms": ["dinov2", "pdq"]}The defaults are summarised below. Every fingerprint, which media types keep it and what each
survives are on Fingerprints. The table is generated from the same
capability snapshot as the watermark table; GET /v1/capabilities stays the authority at run
time.
| Modality | Default (field omitted) | Always added | Selectable |
|---|---|---|---|
| audio | chromaprint |
none | chromaprint, audfprint, iscc-audio |
| image | iscc-image, dinov2 |
iscc-image |
iscc-image, dinov2, pdq, dinov2-lsh256 |
| video | tmk-pdqf |
none | tmk-pdqf, vpdq |
5. Poll the job
Section titled “5. Poll the job”Poll GET /v1/jobs/{job_id} until state is terminal:
completed, failed, rejected, canceled, or expired. Non-terminal states are accepted,
queued, running, and cancel_requested. Wait at least 2 seconds between polls, backing off
for longer jobs.
A completed job has a result with asset_id, publication_id, receipt_id, and
verification_id. That last value is ready verification evidence (vfy_…), not a job ID. A
failed job carries an error_code from the reason codes.
6. Download and inspect
Section titled “6. Download and inspect”POST /v1/assets/{asset_id}/downloadreturns a short-livedGETURL with the exactbyte_lengthand lowercasesha256of the published bytes. Check both after downloading. For a sidecar media type the response also carriessidecar_manifest, the.c2pafile that must travel beside the asset. See Downloads and sidecar manifests.GET /v1/receipts/{receipt_id}returns the immutable signing receipt.
To stop a job, send a cancellation request with
POST /v1/jobs/{job_id}/cancel. It never publishes partial
output.
