API Reference
Complete reference for the Labeler REST API. Automate image labeling, retrieve audit records, and manage certificates programmatically.
Overview
Base URL
https://labeler.eu//api/v1Versioning
The API uses path-based versioning. The current version is v1. Breaking changes will be introduced under a new path prefix (e.g. /api/v2).
Request format
Endpoints that accept a body use multipart/form-data (for file uploads) or application/json. The Accept header controls the response format where applicable.
Standard response headers
| Header | Description |
|---|---|
X-Request-Id | Unique request identifier. Include this in support requests. |
X-Content-Type-Options | nosniff |
Cache-Control | no-store |
RateLimit-Limit | Maximum requests allowed in the window. |
RateLimit-Remaining | Requests remaining in the current window. |
RateLimit-Reset | Unix timestamp when the window resets. |
Request ID
Every response includes an X-Request-Id header. Include this ID when contacting support — it lets us trace the exact request.
Authentication
Creating an API key
Create and manage keys from your dashboard. Navigate to Settings → API Keys → New key. Assign only the scopes the key needs.
Bearer token format
Authorization: Bearer lbl_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxScopes
Each key is issued with one or more scopes. Requests to endpoints outside the key's scopes return 403 Forbidden.
| Scope | Grants access to |
|---|---|
label | Sign images and create C2PA manifests (label, batch label, label-from-URL). |
assets | Read the labeling event audit log (list events, fetch a single event). |
certificates | Read compliance certificates for labeling events. |
check | Run compliance checks against files (multi-region scoring against EU AI Act, California SB 942, China CAC). |
verify | Verify a file’s C2PA manifest and cross-check against the labeling audit log. |
Test vs. live keys
Keys prefixed lbl_test_ operate in a sandbox and do not consume quota or write permanent data. Keys prefixed lbl_live_ operate against production.
lbl_test_…Sandbox environment. No quota consumed. Safe for integration testing.
lbl_live_…Production environment. Quota applies. Use only in server-side code.
Security best practices
- Rotate keys regularly and revoke unused ones.
- Issue keys with the minimum required scopes.
- Never embed keys in client-side code, mobile apps, or public repositories.
- Store keys in environment variables or a secrets manager.
cURL example
curl https://labeler.eu/api/v1/assets \
-H "Authorization: Bearer lbl_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"Rate Limits
| Endpoint | Method | Limit | Auth |
|---|---|---|---|
/api/v1/label | POST | 30 / 5 min | API key or session |
/api/v1/label/batch | POST | 5 / 5 min | API key |
/api/v1/label/url | POST | 10 / 5 min | API key |
/api/v1/assets | GET | 60 / min | API key |
/api/v1/assets/{id} | GET | 60 / min | API key |
/api/v1/certificate/{id} | GET | 60 / min | API key |
/api/v1/check | POST | 120 / min | API key |
/api/v1/verify | POST | 120 / min | API key |
/api/v1/verify/{hash} | GET | 600 / min | API key |
Rate-limit response headers
All responses include rate-limit headers so you can track your remaining budget.
RateLimit-Limit: 30
RateLimit-Remaining: 12
RateLimit-Reset: 1748000400429 response example
HTTP/1.1 429 Too Many Requests
RateLimit-Limit: 30
RateLimit-Remaining: 0
RateLimit-Reset: 1748000400
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Try again after the reset timestamp."
},
"requestId": "b3d9f12c-4a7e-4f1b-8e0c-1a2b3c4d5e6f"
}Backoff guidance
When you receive a 429, read the RateLimit-Reset header (Unix timestamp) and wait until that time before retrying. For batch workloads, implement exponential backoff with jitter to avoid thundering-herd spikes.
Errors
Error envelope
All error responses use a standard JSON envelope. The error.code field is stable and machine-readable. The error.message field is human-readable and may change.
{
"error": {
"code": "validation_error",
"message": "options.digitalSourceType is required",
"details": { "field": "options.digitalSourceType" }
},
"requestId": "b3d9f12c-4a7e-4f1b-8e0c-1a2b3c4d5e6f"
}HTTP status codes
| Code | Description |
|---|---|
| 200 | OK — request succeeded. |
| 400 | Bad Request — invalid or missing parameters. |
| 401 | Unauthorized — missing or malformed credentials. |
| 403 | Forbidden — valid credentials but insufficient scope. |
| 404 | Not Found — resource does not exist. |
| 412 | Precondition Failed — signing certificate not configured. |
| 413 | Payload Too Large — file exceeds the size limit. |
| 415 | Unsupported Media Type — MIME type not accepted. |
| 429 | Too Many Requests — rate limit exceeded. |
| 500 | Internal Server Error — unexpected server failure. |
| 502 | Bad Gateway — upstream signing service error. |
| 503 | Service Unavailable — signing credentials not configured. |
Error codes
| Code | HTTP status | Description |
|---|---|---|
missing_credentials | 401 | No Authorization header or session cookie. |
invalid_credentials | 401 | Token is malformed, expired, or revoked. |
insufficient_scope | 403 | Token does not have the required scope. |
validation_error | 400 | One or more request parameters failed schema validation. |
rate_limited | 429 | Too many requests; see RateLimit-Reset header. |
mime_not_allowed | 415 | File MIME type is not accepted by this endpoint. |
too_large | 413 | File exceeds the maximum allowed size. |
sign_failed | 502 | C2PA signing failed in the upstream signing service. |
signing_unavailable | 503 | Managed signing is not configured. |
not_found | 404 | The requested resource does not exist. |
plan_upgrade_required | 403 | The endpoint requires a paid plan tier (e.g. compliance certificate downloads require Agency or higher). |
ssrf_blocked | 400 | The provided URL resolves to a blocked address (SSRF protection). |
url_fetch_failed | 400 | The remote URL could not be fetched. |
Endpoints
/api/v1/labelLabel a single image
Accepts a multipart upload containing the image file and a JSON options blob. Signs the image with a C2PA manifest using the specified signing mode and persists an audit-log entry. Returns the labeled image as binary (`application/octet-stream`) for API-key callers; returns a JSON response for session-cookie callers (preserves the existing browser UI contract).
labelRate limit30 / 5 minBody parameters
| Name | Type | Required | Description |
|---|---|---|---|
image | File (binary) | Yes | The image to label. Accepted types: `image/jpeg`, `image/png`, `image/webp`, `image/avif`, `image/tiff`, `image/heic`. Max 50 MB. Compatibility alias: `file`. |
options | JSON string | Yes | Serialised `LabelEndpointOptions` object (see fields below). |
options.digitalSourceType | string (enum) | Yes | IPTC `digitalSourceType` value. One of: `trainedAlgorithmicMedia`, `compositeWithTrainedAlgorithmicMedia`, `algorithmicMedia`, `algorithmicallyEnhanced`, `humanEdits`, `digitalCreation`, `digitalCapture`. |
options.regionPreset | string (enum) | Yes | Jurisdiction profile. One of: `eu_strict`, `california`, `china`, `global_minimum`. |
options.signingMode | "managed" | "byoc" | Yes | `managed` — use the Labeler-managed signing certificate. `byoc` — use a certificate you uploaded via the dashboard. |
options.dntEnabled | boolean | Yes | When `true`, embeds a `c2pa.training-mining: notAllowed` assertion. |
options.filename | string | Yes | Original filename (1–255 characters). Written into the manifest. |
options.sha256Input | string (hex) | Yes | SHA-256 hash of the original file (lowercase hex, 64 characters). Used for integrity verification. |
options.preApplied | array | No | Up to 4 client-side layers already applied before upload. Each entry is `{ kind: "watermark", payloadId: string }` or `{ kind: "overlay", position: "TL"|"TR"|"BL"|"BR", text: string }`. |
cURL example
curl -X POST https://labeler.eu/api/v1/label \
-H "Authorization: Bearer lbl_live_xxxxxxxxxxxxxxxx" \
-F "image=@photo.jpg" \
-F 'options={"digitalSourceType":"trainedAlgorithmicMedia","regionPreset":"eu_strict","signingMode":"managed","dntEnabled":true,"filename":"photo.jpg","sha256Input":"<sha256-hex>","preApplied":[]}' \
--output labeled-photo.jpgSuccess 200
API-key callers receive the labeled image as `application/octet-stream` with a `Content-Disposition: attachment; filename="<original-filename>"` header. Session callers receive a JSON response.
// Session callers receive JSON:
{
"ok": true,
"sha256Output": "a1b2c3d4e5f6…",
"filename": "photo.jpg",
"appliedLayers": ["c2pa"],
"signerFingerprint": "SHA256:Ab1Cd2…"
}
// API-key callers receive the raw binary
// with Content-Disposition: attachmentErrors
validation_errorssrf_blockedInvalid parameters.missing_credentialsinvalid_credentialsAuth failed.insufficient_scopeKey lacks `label` scope.too_largeFile exceeds 50 MB.mime_not_allowedUnsupported MIME type.rate_limitedRate limit exceeded.sign_failedSigning service error.signing_unavailableSigning not configured./api/v1/label/batchBatch label up to 10 images
Label up to 10 images in a single request. Each file is signed independently with the provided options. Returns a ZIP archive containing a `manifest.json` with per-file results and the labeled images.
labelRate limit5 / 5 minBody parameters
| Name | Type | Required | Description |
|---|---|---|---|
image | File[] (binary, multipart) | Yes | Repeat the `image` field for up to 10 image files. Same MIME constraints as the single-label endpoint. Total payload max 200 MB. Compatibility alias: `files`. |
options | JSON string | Yes | Shared `LabelEndpointOptions` applied to all files (same fields as single-label, without `filename` and `sha256Input`). |
cURL example
curl -X POST https://labeler.eu/api/v1/label/batch \
-H "Authorization: Bearer lbl_live_xxxxxxxxxxxxxxxx" \
-F "image=@photo1.jpg" \
-F "image=@photo2.png" \
-F 'options={"digitalSourceType":"trainedAlgorithmicMedia","regionPreset":"eu_strict","signingMode":"managed","dntEnabled":true,"preApplied":[]}' \
--output batch-result.zipSuccess 200
`application/zip` archive. Contains `manifest.json` with per-file results, plus the labeled file for each successful entry.
// manifest.json inside the ZIP:
{
"results": [
{
"filename": "photo1.jpg",
"ok": true,
"sha256Output": "a1b2c3…"
},
{
"filename": "photo2.png",
"ok": false,
"error": "mime_not_allowed"
}
]
}Errors
validation_errorInvalid parameters or more than 10 files.missing_credentialsinvalid_credentialsAuth failed.insufficient_scopeKey lacks `label` scope.rate_limitedRate limit exceeded./api/v1/label/urlLabel an image from a URL
Fetches a publicly accessible image URL server-side (SSRF-protected — private IP ranges and loopback addresses are blocked), labels it, and returns the binary result. JSON body.
labelRate limit10 / 5 minBody parameters
| Name | Type | Required | Description |
|---|---|---|---|
url | string (URL) | Yes | Publicly accessible HTTPS URL of the image to fetch and label. |
options | LabelEndpointOptions | Yes | Same options object as the single-label endpoint (without `filename` and `sha256Input` — derived from the fetched file). |
cURL example
curl -X POST https://labeler.eu/api/v1/label/url \
-H "Authorization: Bearer lbl_live_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/photo.jpg","options":{"digitalSourceType":"trainedAlgorithmicMedia","regionPreset":"eu_strict","signingMode":"managed","dntEnabled":true,"preApplied":[]}}' \
--output labeled-photo.jpgSuccess 200
Labeled image as `application/octet-stream`.
// Binary response with headers:
// Content-Type: application/octet-stream
// Content-Disposition: attachment; filename="photo.jpg"Errors
validation_errorssrf_blockedurl_fetch_failedBad URL or fetch failed.missing_credentialsinvalid_credentialsAuth failed.insufficient_scopeKey lacks `label` scope.too_largeRemote file exceeds 50 MB.mime_not_allowedUnsupported MIME type.rate_limitedRate limit exceeded./api/v1/assetsList labeling events
Returns a cursor-paginated list of the authenticated user's labeling events (audit log). Results are ordered by creation date descending.
assetsRate limit60 / minQuery parameters
| Name | Type | Required | Description |
|---|---|---|---|
cursor | string | No | Opaque pagination cursor returned in the previous response. |
limit | integer (1–100) | No | Number of records to return. Defaults to 20. |
modality | string | No | Filter by modality: `image`, `video`, `audio`. |
from | ISO 8601 datetime | No | Return events on or after this timestamp. |
to | ISO 8601 datetime | No | Return events before or on this timestamp. |
cURL example
curl "https://labeler.eu/api/v1/assets?limit=10&modality=image" \
-H "Authorization: Bearer lbl_live_xxxxxxxxxxxxxxxx"Success 200
JSON object with paginated labeling events.
{
"data": [
{
"id": "clxxx123",
"sha256Input": "a1b2c3…",
"sha256Output": "d4e5f6…",
"filename": "photo.jpg",
"mime": "image/jpeg",
"sizeBytes": 2048576,
"modality": "image",
"regionPreset": "eu_strict",
"digitalSourceType": "trainedAlgorithmicMedia",
"signerFingerprint": "SHA256:Ab1Cd2…",
"dntEnabled": true,
"visibleOverlay": false,
"createdAt": "2026-05-16T10:30:00.000Z"
}
],
"nextCursor": "eyJpZCI6ImNseHh4MTI0In0",
"total": 42
}Errors
validation_errorInvalid query parameters.missing_credentialsinvalid_credentialsAuth failed.insufficient_scopeKey lacks `assets` scope.rate_limitedRate limit exceeded./api/v1/assets/{id}Get a labeling event
Returns the full detail record for a single labeling event owned by the authenticated user.
assetsRate limit60 / minQuery parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string (path param) | Yes | ID of the labeling event. |
cURL example
curl "https://labeler.eu/api/v1/assets/clxxx123" \
-H "Authorization: Bearer lbl_live_xxxxxxxxxxxxxxxx"Success 200
JSON object with full labeling event details.
{
"id": "clxxx123",
"sha256Input": "a1b2c3…",
"sha256Output": "d4e5f6…",
"filename": "photo.jpg",
"mime": "image/jpeg",
"sizeBytes": 2048576,
"modality": "image",
"regionPreset": "eu_strict",
"digitalSourceType": "trainedAlgorithmicMedia",
"assertions": ["c2pa.training-mining"],
"signerFingerprint": "SHA256:Ab1Cd2…",
"dntEnabled": true,
"visibleOverlay": false,
"createdAt": "2026-05-16T10:30:00.000Z"
}Errors
missing_credentialsinvalid_credentialsAuth failed.insufficient_scopeKey lacks `assets` scope.not_foundEvent not found or owned by another user.rate_limitedRate limit exceeded./api/v1/certificate/{id}Get a compliance certificate
Returns a machine-readable JSON compliance certificate for a labeling event. The certificate attests to the applied layers, signing key, and IPTC metadata.
certificatesRate limit60 / minQuery parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string (path param) | Yes | ID of the labeling event. |
cURL example
curl "https://labeler.eu/api/v1/certificate/clxxx123" \
-H "Authorization: Bearer lbl_live_xxxxxxxxxxxxxxxx"Success 200
JSON compliance certificate for the labeling event.
{
"data": {
"schemaVersion": 1,
"eventId": "clxxx123",
"issuedAt": "2026-05-16T10:30:00.000Z",
"hashes": {
"input": "a1b2c3…",
"output": "d4e5f6…"
},
"file": {
"filename": "photo.jpg",
"mime": "image/jpeg",
"sizeBytes": 124356,
"modality": "image"
},
"compliance": {
"regionPreset": "eu_strict",
"digitalSourceType": "trainedAlgorithmicMedia",
"assertions": ["c2pa.training-mining"],
"dntEnabled": true
},
"signer": {
"fingerprint": "SHA256:Ab1Cd2…"
},
"verification": {
"url": "https://labeler.eu/verify#d4e5f6…"
}
},
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}Errors
missing_credentialsinvalid_credentialsAuth failed.insufficient_scopeplan_upgrade_requiredKey lacks `certificates` scope, or the account plan does not include compliance certificate downloads (Agency tier or higher required).not_foundEvent not found or owned by another user.rate_limitedRate limit exceeded./api/v1/checkRun a compliance check
Runs the full compliance pipeline on the supplied file (C2PA manifest read, IPTC/XMP metadata, watermark probe, multi-region rule evaluation against EU AI Act, California SB 942, and China CAC). Returns a structured `CheckerReport` identical in shape to the browser-side checker output.
checkRate limit120 / minBody parameters
| Name | Type | Required | Description |
|---|---|---|---|
file | File (binary, multipart) | Yes | The file to check. Same MIME constraints as the labeling endpoint. |
cURL example
curl -X POST https://labeler.eu/api/v1/check \
-H "Authorization: Bearer lbl_live_xxxxxxxxxxxxxxxx" \
-F "file=@photo.jpg"Success 200
JSON `CheckerReport` wrapped in `{ data, requestId }`.
{
"data": {
"schemaVersion": 1,
"sha256": "a1b2c3…",
"filename": "photo.jpg",
"mime": "image/jpeg",
"sizeBytes": 2048576,
"modality": "image",
"createdAt": "2026-05-16T10:30:00.000Z",
"signals": { /* per-region signal map */ },
"verdicts": { /* per-region pass/fail */ },
"c2pa": { /* manifest summary */ },
"iptc": { /* IPTC/XMP fields */ },
"watermark": { /* trustmark probe summary */ }
},
"requestId": "…"
}Errors
invalid_requestmagic_mismatchempty_fileBad request body or file content does not match declared MIME.missing_credentialsinvalid_credentialsAuth failed.insufficient_scopeKey lacks `check` scope.file_too_largeFile exceeds the per-modality cap.mime_not_allowedUnsupported MIME type.rate_limitedRate limit exceeded.internal_errorPipeline failure./api/v1/verifyVerify a file’s C2PA manifest
Server-side C2PA verification. Parses the manifest and IPTC/XMP metadata and cross-checks the file’s SHA-256 against the labeling audit log to surface `signedByPlatform`. Narrower than `/api/v1/check` — this endpoint answers “is this content credential intact and known to us?”
verifyRate limit120 / minBody parameters
| Name | Type | Required | Description |
|---|---|---|---|
file | File (binary, multipart) | Yes | The file to verify. Same MIME constraints as the labeling endpoint. |
cURL example
curl -X POST https://labeler.eu/api/v1/verify \
-H "Authorization: Bearer lbl_live_xxxxxxxxxxxxxxxx" \
-F "file=@photo.jpg"Success 200
JSON `VerifyReport` wrapped in `{ data, requestId }`.
{
"data": {
"sha256": "a1b2c3…",
"filename": "photo.jpg",
"mime": "image/jpeg",
"modality": "image",
"signedByPlatform": true,
"c2pa": { /* manifest summary */ },
"iptc": { /* IPTC/XMP fields */ }
},
"requestId": "…"
}Errors
invalid_requestmagic_mismatchempty_fileBad request body or file content does not match declared MIME.missing_credentialsinvalid_credentialsAuth failed.insufficient_scopeKey lacks `verify` scope.file_too_largeFile exceeds the per-modality cap.mime_not_allowedUnsupported MIME type.rate_limitedRate limit exceeded.internal_errorPipeline failure./api/v1/verify/{hash}Verify a file by SHA-256 hash
Cheap, byte-free audit-log lookup. Returns `signedByPlatform: true` plus the public signing facts (modality, region preset, digital source type, assertions, signer fingerprint, signing timestamp) when the hash matches an event we previously emitted. Returns `signedByPlatform: false` when the hash is unknown. Privacy: user-identifying fields (`userId`, `filename`) are deliberately omitted so any holder of the hash can verify safely.
verifyRate limit600 / minQuery parameters
| Name | Type | Required | Description |
|---|---|---|---|
hash | string (path param) | Yes | 64-character lowercase SHA-256 hex digest. Either the input or output file hash matches; the output hash is preferred when both exist. |
cURL example
curl https://labeler.eu/api/v1/verify/a1b2c3… \
-H "Authorization: Bearer lbl_live_xxxxxxxxxxxxxxxx"Success 200
JSON `{ signedByPlatform, sha256, ... }` wrapped in `{ data, requestId }`.
{
"data": {
"signedByPlatform": true,
"sha256": "a1b2c3…",
"sha256MatchedOn": "output",
"modality": "image",
"mime": "image/jpeg",
"sizeBytes": 482311,
"regionPreset": "eu_strict",
"digitalSourceType": "trainedAlgorithmicMedia",
"assertions": ["c2pa.actions", "c2pa.training-mining"],
"watermarkPayloadId": "wm_…",
"visibleOverlay": false,
"dntEnabled": true,
"signerFingerprint": "7f3c…",
"signedAt": "2026-05-19T10:24:11.000Z"
},
"requestId": "…"
}Errors
invalid_hashPath parameter is not a 64-character SHA-256 hex digest.missing_credentialsinvalid_credentialsAuth failed.insufficient_scopeKey lacks `verify` scope.rate_limitedRate limit exceeded.internal_errorLookup failure.Changelog
Public REST API launched. Endpoints: POST /label, POST /label/batch, POST /label/url, GET /assets, GET /assets/{id}, GET /certificate/{id}, POST /check (beta).