API reference
Generated from the endpoints this server really answers and from the contract that declares which fields they accept. Nothing here is written by hand except the prose, and a description that drifts from the API fails the build.
Keys
What the presented key is
The smallest endpoint there is: it answers with the organization, the environment and the key's public prefix. Call it once from a new deployment to be sure the key you shipped is the key you meant — above all, that a key you believe is a test key really names the sandbox environment.
It does no metered work, so it counts against no limit of your organization's; the per-key and per-address rate limits still apply.
Answers
| 200 | The key's organization, environment ( The verdict is the public specification's, verbatim (§7 outcome, §8 labels, |
| 401 |
|
| 429 |
|
Example — Check a key before shipping it
curl -X GET "$VCAP_API_BASE/api/v1/me" \
-H "Authorization: Bearer $VCAP_API_KEY"
Verification
Verify a sealed file
Send the media, and the sidecar when the proof lives beside the file rather than in it. Both base64. A proof carried in C2PA Content Credentials is read from the file itself; a manifest store kept beside the file (.c2pa) goes in c2pa_store_b64, and is read only when the file embeds none.
The answer is the verdict of the same isomorphic core the web page and the command-line verifier run, plus the trust set it was reached under. This endpoint is a convenience, never a dependency: the same file verifies with no server at all. What a server adds can only make a verdict stronger — it can ask the transparency log whether a key was revoked, and it can compare an anchored root with what the chain recorded — and both of those are questions a file cannot carry an answer to.
Read the verdict in the specification's own words: outcome, labels and not_evaluated. A missing piece of evidence is a weaker verdict, not an error. proof_source says where the proof was read — the file's trailer, the sidecar, or its Content Credentials — and is never a label: where a proof sits is not evidence.
Parameters
| Idempotency-Key | header | Any string of yours, on a request you may retry. The same key with the same body replays the stored response for 24 hours; with a different body it is refused (422 VCAP_IDEMPOTENCY_KEY_REUSED). Only a success is remembered. |
Request body
Exactly the fields this endpoint accepts, as the server itself validates them; anything else is refused. metering_ref is added by this service and is not yours to send.
| media_b64 | string · required | base64 (standard alphabet, padded). A blob, not text: the pattern is what a validator can check without modelling the proof format. max 32,000,000 chars |
| sidecar_b64 | string | base64 (standard alphabet, padded). A blob, not text: the pattern is what a validator can check without modelling the proof format. max 200,000 chars |
| c2pa_store_b64 | string | base64 (standard alphabet, padded). A C2PA manifest store kept beside the file (.c2pa), read by the core only when the file embeds none, and only for the vcap proof it may carry. A blob, not text, bounded like the media because a store can hold thumbnails, within the same cap on the request as a whole. max 32,000,000 chars |
| recompute_segments | boolean | |
| watermark | string | The detector, as an optional upgrade (C10). off: not run, and the response carries no watermark block. sync: run inside the request. async: queued, and the response carries an opaque job id. auto: sync for a still, async for a clip, decided from the proof's own media.mime. off · auto · sync · async |
Answers
| 200 |
The verdict is the public specification's, verbatim (§7 outcome, §8 labels, |
| 401 |
|
| 403 |
|
| 409 |
|
| 422 |
|
| 429 |
|
| 502 |
|
Example — A file with no proof is a verdict, not an error
curl -X POST "$VCAP_API_BASE/api/v1/verify" \
-H "Authorization: Bearer $VCAP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"media_b64": "BASE64_OF_A_FILE_WITH_NO_PROOF"
}'
Poll an asynchronous verification
watermark: "async" — and "auto" on a clip — queues the detector and hands back an opaque job id. Poll this, or subscribe to the verification.completed webhook and be told.
Once state is done the answer carries both the raw detector evidence and a verdict recomputed with that evidence folded in, so a contradiction between the pixels and the signed core arrives as verdict.outcome === "tampered" rather than as a footnote beside a positive verdict.
The job expires within the hour. A job id that belongs to another environment and one that never existed answer the same 404: an answer that told them apart would say whether a capture had been verified here.
Parameters
| job_id | path · required | Opaque job id, a UUID v4. It carries nothing: not the capture, not the tenant, not the caller. |
Answers
| 200 |
The verdict is the public specification's, verbatim (§7 outcome, §8 labels, |
| 401 |
|
| 404 |
|
| 429 |
|
| 502 |
|
Example — An unknown job id is a 404, not a hint
curl -X GET "$VCAP_API_BASE/api/v1/verify/watermark/00000000-0000-4000-8000-000000000000" \
-H "Authorization: Bearer $VCAP_API_KEY"
Sandbox
List the conformance vectors this sandbox serves
The sandbox is not a pretend world. It serves the conformance corpus of the public specification — real sealed files, whose expected verdict is known because the specification says what it is, reviewed and written down before the file was generated.
The answer names the corpus: its version, the SHA-256 of the manifest as committed, how many vectors the manifest declares, and how many of them this API serves. Every vector carries the outcome the specification expects.
Only a key of the sandbox environment may call this. A dev or production key is refused with 403 VCAP_SANDBOX_ONLY — the environment boundary that already separates your keys is the one that separates test traffic from real data, and there is no second one.
Answers
| 200 |
The verdict is the public specification's, verbatim (§7 outcome, §8 labels, |
| 401 |
|
| 403 |
|
| 429 |
|
| 503 |
|
Example — What the sandbox holds
curl -X GET "$VCAP_API_BASE/api/v1/sandbox/vectors" \
-H "Authorization: Bearer $VCAP_API_KEY"
Fetch one vector and the verdict the specification expects
The file, base64, in exactly the shape POST /api/v1/verify takes — media_b64 and, where the vector has one, sidecar_b64 or c2pa_store_b64 — next to expected, copied from the vector's own expected.json in the specification repository.
expected is not our answer and not a promise about ours: it is what the specification says any conformant verifier must produce for these bytes. Feed the same bytes to the offline page, to the command-line verifier, or to your own implementation, and compare against the same file. That is the whole point of shipping the corpus instead of a mock.
verify is the request the vector has to be sent with. For a container vector it carries recompute_segments: true, because that is what the kind means in the corpus — the expectation is the one reached with every present segment's content hash recomputed from the received container. Send a container vector without it and you get segment content not recomputed, and a replaced frame reads frames_not_compared instead of tampered, as does a genuine clip: a proof's own segment hashes prove that somebody signed some hashes, not that those are the frames you received.
Served vectors are the subset whose expected verdict depends on the file alone. The corpus also holds vectors that pin an injected input — a verifier clock, an online revocation answer, a chain read — and those cannot be reproduced by sending bytes to a live service, because the service has its own clock and its own trust set. They are declared in the listing rather than quietly dropped, and the place to run them is the corpus runner in the specification.
Parameters
| name | path · required | The vector's directory name in the corpus, e.g. `01-jpeg-sealed`. |
Answers
| 200 |
The verdict is the public specification's, verbatim (§7 outcome, §8 labels, |
| 401 |
|
| 403 |
|
| 404 |
|
| 429 |
|
| 503 |
|
Example — A sealed photo, with the verdict the specification expects
curl -X GET "$VCAP_API_BASE/api/v1/sandbox/vectors/01-jpeg-sealed" \
-H "Authorization: Bearer $VCAP_API_KEY"
What changed in this contract
One entry per change to the wire contract above — not the release history of the capture apps or of this console, which are versioned where they ship. Each entry pins a digest of the operations described here, so a change to the API that nobody wrote down fails the build instead of shipping quietly, and a long quiet stretch here means the contract has not moved.
1.3.2 · 2026-09-27
- No field changed. The verdict now follows corpus 3.0.0 of the public specification: green requires proven device integrity, a registry-signed Google Play Integrity verdict of hardware. Without one — no verdict, basic, failed, or any Apple App Attest verdict — the ceiling is amber and the labels carry integrity not proven. A capture that read green without such a verdict now reads amber, and every iOS capture is amber at best.
Operations digest: 7cecb9cbcabd213c16cce677c654b9d9ba0596003855c023f6fa2dc60d30e132
1.3.1 · 2026-09-25
- GET /api/v1/sandbox/vectors/{name} returns c2pa_store_b64 where the vector keeps a C2PA store beside the file (vector 132), and the listing says has_c2pa_store. Send it as c2pa_store_b64 to POST /api/v1/verify.
Operations digest: 7cecb9cbcabd213c16cce677c654b9d9ba0596003855c023f6fa2dc60d30e132
1.3.0 · 2026-09-25
- POST /api/v1/verify takes an optional c2pa_store_b64: a C2PA manifest store kept beside the file (.c2pa), read only when the file embeds none. A proof carried in Content Credentials embedded in the file is read without it.
- The verdict says where the proof was read, in proof_source (trailer, sidecar, or c2pa with the manifest and how far up the parentOf chain), and what a C2PA store held in content_credentials. Neither is a label. A proof read from the capture a file was made from makes the file no_proof_found, never tampered; a trailer whose proof differs from the active manifest's copy carries the label manifest copy differs.
Operations digest: 971b228bd0627a9ccf060e39c3224d5fe8508313dc80255957e212a9c6c88ff9
1.2.1 · 2026-09-24
- No field changed. The verdict now follows corpus 2.0.0 of the public specification, which adds the outcome frames_not_compared: a video whose signatures hold and whose frames were not tied to them. A container sent without recompute_segments reads it where it used to read verified_clip, so a replaced frame no longer reads as a clip.
- The sandbox serves vectors numbered past 99, and does not serve the corpus's empty-file vector: a request carries at least one byte.
Operations digest: 5ca3917a637786d69ae0aeee0753f824a31010237c78ee0e7be3be971723ff8d
1.2.0 · 2026-09-24
- POST /api/v1/verify no longer answers 402 VCAP_QUOTA_EXCEEDED: there are no plans or quotas any more. A monthly limit agreed for your organization answers 403 VCAP_LIMIT_REACHED, as described since 1.1.0.
- The webhook events quota.threshold_reached and quota.exceeded are withdrawn with the quotas they reported on.
Operations digest: 59f2e2df336d2410e975a25f8d3775256bcd59605eaabac0098f6eb69b4d7f23
1.1.0 · 2026-09-24
- POST /api/v1/verify may answer 403 VCAP_LIMIT_REACHED: a monthly limit agreed for your organization is reached, or the endpoint is switched off for it. The body names the limit and when it rolls over.
Operations digest: 24261d03dd221a22f74127edd42f1e7994ca8e687c345c460eaccc377aabc292
1.0.1 · 2026-09-22
- No wire change: every endpoint, field and answer is what 1.0.0 described.
- Wording only. This document is public, so it no longer describes how the service is built — which component mints a job id, which file a field list is read from, how many environments are deployed. What it promises is unchanged: the description is generated from what the server serves and a disagreement fails the build.
Operations digest: 23701ac77a2c40a36f6cca14792b30e87ea648a45e2750151c6cdb33b0666f22
1.0.0 · 2026-09-12
- First published description of the tenant API: GET /api/v1/me, POST /api/v1/verify, GET /api/v1/verify/watermark/{job_id}.
- Sandbox added: GET /api/v1/sandbox/vectors and GET /api/v1/sandbox/vectors/{name} serve the vcap-spec conformance corpus to keys of the sandbox environment.
Operations digest: 1126e79a68a6cebdd72b485340c559e69f92a9fc8ff0942961f106a39dd3a5e6
Error codes
| VCAP_UNAUTHENTICATED | The key is unknown, malformed or revoked. All three answer the same, so a probe learns nothing about which prefixes exist. |
| VCAP_RATE_LIMITED | Too many requests, too fast. Retry in a second — this is not a limit of your organization's. |
| VCAP_LIMIT_REACHED | A limit of your organization's agreement is reached: `limit` names which, `resets_at` when a monthly one rolls over. A limit of 0 is the endpoint switched off for your organization. Retrying before `resets_at` gets the same answer. |
| VCAP_IDEMPOTENCY_KEY_REUSED | That idempotency key was used with a different body. Refused, never served. |
| VCAP_IDEMPOTENCY_IN_PROGRESS | The first request with that key is still running. |
| VCAP_IDEMPOTENCY_KEY_INVALID | The `Idempotency-Key` header is longer than 255 characters. An error this API could answer and this document did not name misleads in the same direction as one it names and cannot. |
| VCAP_NOT_FOUND | No such job, or not this environment's. |
| VCAP_SANDBOX_ONLY | This endpoint answers keys of the `sandbox` environment only. |
| VCAP_SANDBOX_CORPUS_UNAVAILABLE | This deployment has no conformance corpus on disk. The endpoint refuses rather than serve something it made up. |
| VCAP_DATA_UNREACHABLE | The verification plane did not answer. Nothing was counted. Retry. |
| VCAP_UNDECLARED_FIELD | The request carries a field the plane boundary does not declare. The answer names a JSON pointer and the rule, never the value. |