Skip to content

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/sign
Authorization: Bearer vb_live_...
Idempotency-Key: <UUIDv7>
Content-Type: multipart/form-data

Request fields

FieldTypeRequiredDescription
assetbinaryYesThe asset to sign. Max 100 MB direct upload; use presigned PUT for larger files.
recipestringYesRecipe ID (e.g. image-genai-v1). Must exist and be active.
metadataJSON stringNoAdditional 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"
}
}
FieldTypeDescription
asset_idstringUnique asset ID (ULIDv7 prefixed with a_)
watermark_idstringWatermark ID embedded in the asset. Present when recipe enables watermarking.
manifest_uristringPermanent URL to retrieve the C2PA manifest
verifier_urlstringPublic URL for human-readable verification result
anchor.batch_idstringAnchor batch ID. Present when anchoring is enabled.
anchor.statusstringqueuedpendinganchored

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":

Terminal window
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

Terminal window
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

HTTPCodeMeaning
400verbitas.sign.invalid_recipeRecipe ID not found or validation failed
400verbitas.sign.unsupported_mimeFile type not allowed by this recipe
400verbitas.sign.missing_idempotency_keyIdempotency-Key header missing
401verbitas.auth.invalid_keyAPI key invalid or revoked
402verbitas.billing.quota_exceededMonthly plan limit reached
409verbitas.sign.idempotency_conflictSame Idempotency-Key with different payload
413verbitas.sign.file_too_largeFile exceeds 100 MB direct upload limit
429verbitas.ratelimit.exceededPer-tenant rate limit exceeded
503verbitas.signer.unavailableKMS 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 .jpg containing PNG data is treated as PNG.
  • Watermarked assets are stored in Hetzner Object Storage with TTL controlled by retention.derived_asset_days in the recipe.
  • The verifier_url is publicly accessible without authentication at https://v.verbitas.io.
  • Large files (> 100 MB) require a presigned PUT. Contact support for the presigned upload endpoint.