POST /v1/sign
POST /v1/sign
Sign an asset. Runs all layers configured in the recipe: watermark embedding (Layer 2), C2PA manifest construction and signing (Layer 1), soft-binding index write (Layer 3), and anchor queue (anchor batch).
POST https://api.verbitas.io/v1/signAuthorization: Bearer vb_live_...Idempotency-Key: <UUIDv7>Content-Type: multipart/form-dataRequest fields
| Field | Type | Required | Description |
|---|---|---|---|
asset | binary | Yes | The asset to sign. Max 100 MB direct upload; use presigned PUT for larger files. |
recipe | string | Yes | Recipe ID (e.g. image-genai-v1). Must exist and be active. |
metadata | JSON string | No | Additional assertions to include in the C2PA manifest. |
metadata fields
All metadata fields are optional. Include only what you have.
{ "generator": "stable-diffusion-xl", "model": "sdxl-1.0", "prompt_hash": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "created_at": "2026-05-09T10:00:00Z", "creator": "Jane Smith", "legal_entity": "ACME Corp", "legal_case_ref": "CASE-2026-001"}Never include the prompt text itself. Only the SHA-256 hash of the prompt should appear in the manifest.
Response
200 OK (synchronous — file ≤ ~5 MB)
{ "asset_id": "a_01j...", "watermark_id": "w_01j...", "manifest_uri": "https://m.verbitas.io/manifests/a_01j.../manifest.c2pa", "verifier_url": "https://v.verbitas.io/v/a_01j...", "anchor": { "batch_id": "b_01j...", "status": "queued" }}| Field | Type | Description |
|---|---|---|
asset_id | string | Unique asset ID (ULIDv7 prefixed with a_) |
watermark_id | string | Watermark ID embedded in the asset. Present when recipe enables watermarking. |
manifest_uri | string | Permanent URL to retrieve the C2PA manifest |
verifier_url | string | Public URL for human-readable verification result |
anchor.batch_id | string | Anchor batch ID. Present when anchoring is enabled. |
anchor.status | string | queued → pending → anchored |
202 Accepted (asynchronous — file > ~5 MB)
{ "job_id": "j_01j...", "poll_url": "https://api.verbitas.io/v1/jobs/j_01j..."}Poll GET /v1/jobs/{job_id} until status == "done":
curl -H "Authorization: Bearer vb_live_..." \ https://api.verbitas.io/v1/jobs/j_01j... | jq '{status,asset_id}'Job response when done:
{ "job_id": "j_01j...", "status": "done", "asset_id": "a_01j...", "watermark_id": "w_01j...", "manifest_uri": "https://m.verbitas.io/manifests/a_01j.../manifest.c2pa", "verifier_url": "https://v.verbitas.io/v/a_01j...", "anchor": { "batch_id": "b_01j...", "status": "queued" }}curl example
curl -X POST https://api.verbitas.io/v1/sign \ -H "Authorization: Bearer $VERBITAS_API_KEY" \ -H "Idempotency-Key: $(python3 -c 'import uuid; print(uuid.uuid4())')" \ -F "recipe=image-genai-v1" \ -F 'metadata={"generator":"stable-diffusion-xl","model":"sdxl-1.0"}' \ | jq .Python SDK example
import verbitas
client = verbitas.Client() # reads VERBITAS_API_KEY from env
result = client.sign( "output.png", recipe="image-genai-v1", metadata={"generator": "stable-diffusion-xl", "model": "sdxl-1.0"})
# For async (large file), .sign() returns a Job; call .wait() to block:if result.is_async: result = result.wait(timeout=120)
print(result.asset_id)print(result.verifier_url)TypeScript SDK example
import { VerbitasClient } from "@verbitas/sdk";
const client = new VerbitasClient(); // reads VERBITAS_API_KEY from env
const result = await client.sign("output.png", { recipe: "image-genai-v1", metadata: { generator: "stable-diffusion-xl", model: "sdxl-1.0" },});
console.log(result.assetId);console.log(result.verifierUrl);Error codes
| HTTP | Code | Meaning |
|---|---|---|
| 400 | verbitas.sign.invalid_recipe | Recipe ID not found or validation failed |
| 400 | verbitas.sign.unsupported_mime | File type not allowed by this recipe |
| 400 | verbitas.sign.missing_idempotency_key | Idempotency-Key header missing |
| 401 | verbitas.auth.invalid_key | API key invalid or revoked |
| 402 | verbitas.billing.quota_exceeded | Monthly plan limit reached |
| 409 | verbitas.sign.idempotency_conflict | Same Idempotency-Key with different payload |
| 413 | verbitas.sign.file_too_large | File exceeds 100 MB direct upload limit |
| 429 | verbitas.ratelimit.exceeded | Per-tenant rate limit exceeded |
| 503 | verbitas.signer.unavailable | KMS signer temporarily unavailable; retry with backoff |
All errors use RFC 7807 Problem+JSON:
{ "type": "https://docs.verbitas.io/api/errors#verbitas.sign.invalid_recipe", "title": "Invalid recipe", "status": 400, "detail": "Recipe 'my-recipe-v99' not found for tenant t_01j...", "request_id": "req_01j..."}Notes
- File extensions are not trusted. Magic-byte detection determines the actual media type. A
.jpgcontaining PNG data is treated as PNG. - Watermarked assets are stored in Hetzner Object Storage with TTL controlled by
retention.derived_asset_daysin the recipe. - The
verifier_urlis publicly accessible without authentication athttps://v.verbitas.io. - Large files (> 100 MB) require a presigned PUT. Contact support for the presigned upload endpoint.