# Invoke a single agent Source: https://docs.zebratruth.ai/api-reference/agents/invoke-a-single-agent /openapi/zebratruth-v1.yaml post /agents/{agentId} Run a single compliance agent directly instead of the full pipeline. Useful for targeted re-checks or building custom workflows. # List available agents Source: https://docs.zebratruth.ai/api-reference/agents/list-available-agents /openapi/zebratruth-v1.yaml get /agents Discover all available compliance agents. Agents are added regularly — always use this endpoint to discover dynamically rather than hardcoding agent IDs. # Get credit usage Source: https://docs.zebratruth.ai/api-reference/authentication/get-credit-usage /openapi/zebratruth-v1.yaml get /usage Returns current credit balance, usage this period, and plan details. # Get engine version Source: https://docs.zebratruth.ai/api-reference/authentication/get-engine-version /openapi/zebratruth-v1.yaml get /version Returns current compliance engine version, rules version, and agent versions. # Validate API key Source: https://docs.zebratruth.ai/api-reference/authentication/validate-api-key /openapi/zebratruth-v1.yaml get /whoami Returns tenant info, tier, and credit balance for the authenticated API key. # Get subscription status Source: https://docs.zebratruth.ai/api-reference/billing/get-subscription-status /openapi/zebratruth-v1.yaml get /subscription Plan, subscription state, trial state, current-period credits and which billing actions the caller may take. Not billed. # Get usage for one externalId Source: https://docs.zebratruth.ai/api-reference/billing/get-usage-for-one-externalid /openapi/zebratruth-v1.yaml get /usage/project/{projectId} Ledger entries whose `externalId` equals `projectId`, newest first. `totals` covers the returned page only. Not billed. # Get usage totals by feature Source: https://docs.zebratruth.ai/api-reference/billing/get-usage-totals-by-feature /openapi/zebratruth-v1.yaml get /usage/summary Credits and request counts for a period, broken down by billable feature. Not billed. # Start a subscription Source: https://docs.zebratruth.ai/api-reference/billing/start-a-subscription /openapi/zebratruth-v1.yaml post /billing/checkout Creates a hosted checkout session for a first subscription and returns its URL. First purchase only. An account that already has a subscription gets `409`; to move from Individual to Pro use `POST /billing/upgrade`. # Upgrade Individual to Pro Source: https://docs.zebratruth.ai/api-reference/billing/upgrade-individual-to-pro /openapi/zebratruth-v1.yaml post /billing/upgrade Moves an Individual subscription to Pro, effective immediately. - You are charged for Pro straight away. - The billing period **restarts on the upgrade date** and the renewal date moves with it. Upgrading on the 14th means the next charge is the 14th of the following month. - Unused Individual credits carry into the first Pro period. `GET /usage` reports the parts separately as `baseCredits` and `carriedCredits`. Downgrading from Pro to Individual is not supported. **Retries.** `Idempotency-Key` is required, and a retry after a timeout must reuse the same key. A retry with a new key can charge twice. # Get a past check with its full report Source: https://docs.zebratruth.ai/api-reference/check-history/get-a-past-check-with-its-full-report /openapi/zebratruth-v1.yaml get /compliance/checks/{requestId} The persisted envelope for one check, with the report inlined. Not billed. Requires the `logs:read` key scope (company employees: `reports:read`). # List past compliance checks Source: https://docs.zebratruth.ai/api-reference/check-history/list-past-compliance-checks /openapi/zebratruth-v1.yaml get /compliance/checks Newest first, within the workspace's retention window (30 days on trial and Individual, 365 days on Pro). Company employees see their own checks; owners and managers see the whole workspace. Not billed. Requires the `logs:read` key scope. # Check image rights Source: https://docs.zebratruth.ai/api-reference/compliance/check-image-rights /openapi/zebratruth-v1.yaml post /compliance/check-image Dedicated image rights clearance. Checks for: - **Restricted-category content** — six advertising-restricted categories: alcohol & spirits, weapons & firearms, tobacco & smoking, gambling, pharmaceuticals, financial products (incl. crypto / forex / investments). See the [Restricted Categories guide](/guides/restricted-categories) for the full catalog. - **Content safety (ZebraSafety)** — adult / violence / racy / medical likelihood signals. Tiered: `(high confidence)` → `status: "block"`; `(medium confidence)` → `status: "flag"`. - **Brand logo & stock-agency watermark detection** — trademarks and unlicensed-stock indicators. - **C2PA / IPTC metadata** — Content Credentials, provenance, AI generation markers. - **Known-person likeness** — right-of-publicity flag. *Surfaces only when the celebrity-detection provider is enabled in the deployment; not active in the default ZebraTruth-managed configuration today.* Use this for image-only checks. For combined text + image checks, use `/compliance/check` with `content.imageUrls`. **Request formats.** Accepts either `application/json` (`imageUrls` referencing publicly accessible URLs) or `multipart/form-data` (upload up to 2 image binaries directly — use when you don't have a public URL handy). Total request body is capped at 4.5 MB. **Per-jurisdiction citations.** Citations on returned checks are composed from the laws applicable to the jurisdictions you requested AND the platform policies for the platforms you requested. A restricted-category detection with `jurisdictions: ["us"]` cites the federal regulator (e.g. FTC, FDA, SEC); the same detection with `jurisdictions: ["eu", "uk"]` cites European directives and UK codes. Adding `platforms: ["facebook", "tiktok"]` appends the corresponding platform-policy references. **Check statuses.** Stock watermarks and high-severity restricted categories return `status: "block"`; brand logos, medium-severity restricted categories, and medium-confidence content-safety signals return `status: "flag"` (review). See the [Image Rights Clearance guide](/guides/image-rights-clearance) for the full matrix. **Tenant scoping.** Every request is scoped to your account's subscribed jurisdictions and platforms. Requesting a jurisdiction or platform outside that set returns 403. See the [Tenant Onboarding and Scoping guide](/guides/tenant-onboarding-and-scoping). # Force refresh of legal corpus rows Source: https://docs.zebratruth.ai/api-reference/compliance/force-refresh-of-legal-corpus-rows /openapi/zebratruth-v1.yaml post /corpus/refresh Triggers an out-of-band refresh of one or more rows in your tenant's legal corpus. Each corpus row is the pre-researched legal context that Stage A consumes — refreshing replaces its enrichment with the latest available regulator guidance. **When you'd use this**: regulatory guidance changed mid-window, or you want fresh enrichment before running an important compliance check. **Idempotent**: concurrent calls or repeated calls on the same row don't fan out duplicate work — calls that arrive while a refresh is already in flight return `skippedCount += 1` instead of enqueueing. **Rate-limited**: max 50 rows enqueued per call. Excess returns as `pending`. Re-call after ~1 minute to drain remaining rows. **Async**: returns immediately after enqueueing; the actual refresh runs on a background worker. Subsequent reads automatically pick up the new enrichment. # Run compliance check Source: https://docs.zebratruth.ai/api-reference/compliance/run-compliance-check /openapi/zebratruth-v1.yaml post /compliance/check Check content against jurisdiction laws, platform policies, and rights clearance. Supports three response modes: sync (block until complete), stream (SSE events per agent), and async (webhook callback on completion). **Request formats.** Accepts either `application/json` (the canonical format, full feature set including streaming and webhooks) or `multipart/form-data` (for uploading image binaries directly from a local file system). The multipart form is sync-only and bypasses the response cache (cache is keyed on text, so mixing image sets would return wrong results). **Latency.** Sync checks typically complete in **15–120 seconds** depending on mode, number of jurisdictions/platforms, and content length. Cached hits return in ≤1s. **Set client timeouts to at least 2.5 minutes (150s).** For long-running or batched workflows, prefer `responseMode: "async"` with a webhook to avoid long-held connections. **Tenant scoping.** Every request is scoped to your account's subscribed jurisdictions and platforms (set during onboarding at `https://developers.zebratruth.ai`). Requesting a jurisdiction or platform outside that set returns 403. See the [Tenant Onboarding and Scoping guide](/guides/tenant-onboarding-and-scoping). **Financial services.** Workspaces on the `financial_services` industry select the specialist review scopes a check should run with `reviewScopes`, describe the artifact with `contentKind`, and may narrow the regulated actors with `regulatedActors`. Admission is checked in this order, all before any credits are reserved: unknown or disabled scope (400 `review_scope_not_enabled`), scope without corpus coverage for a requested jurisdiction (400 `review_scope_jurisdiction_unsupported`), actor not enabled on the workspace (400 `regulated_actor_not_enabled`), scope not in the plan (403 `review_scope_not_entitled`). Successful responses then carry `agentsRun` and `decisionReasons`. At most 6 specialist agents run per request. **Documents.** The multipart form accepts a single `document` part (PDF or Word `.docx`) in place of `text`. The document is extracted server-side and checked as text. To inspect the extracted text first, call `POST /compliance/documents/extract`. # Approve a proposal Source: https://docs.zebratruth.ai/api-reference/corpus/approve-a-proposal /openapi/zebratruth-v1.yaml post /memory/review-queue/{proposalId}/approve Records one approval. The card is promoted, and the proposal becomes `approved`, only when `approvalsReceived` reaches `approvalsRequired`; below quorum the response is still `accepted` and the proposal stays `needs_human`. One approval per reviewer; authors cannot act on their own proposals. **Dashboard session only.** # Get the caller's reviewer profile Source: https://docs.zebratruth.ai/api-reference/corpus/get-the-callers-reviewer-profile /openapi/zebratruth-v1.yaml get /memory/reviewer/me Roles, scopes and allowed actions for the authenticated reviewer. **Dashboard session only.** # Get the review packet for a proposal Source: https://docs.zebratruth.ai/api-reference/corpus/get-the-review-packet-for-a-proposal /openapi/zebratruth-v1.yaml get /memory/review-queue/{proposalId} Everything needed to decide: proposed card and diff, source snapshots, gate results, authority and entailment, scope, conflicts, allowed actions and decision history. Echo `packetEtag` on the action you take. **Dashboard session only.** # List corpus proposals awaiting review Source: https://docs.zebratruth.ai/api-reference/corpus/list-corpus-proposals-awaiting-review /openapi/zebratruth-v1.yaml get /memory/review-queue Proposed additions and changes to the governed legal corpus, with SLA state and quorum progress. **Dashboard session only** (a user bearer token, never an API key); the caller must be a registered reviewer, or an owner, manager, or employee with the `proposals:review` capability on a company workspace named by `x-workspace-id`. Company callers see their own workspace's proposals only. Proposals are created by the engine's observation pipeline; `POST /memory/propose` always returns 403 `direct_memory_propose_forbidden`. # Reject a proposal Source: https://docs.zebratruth.ai/api-reference/corpus/reject-a-proposal /openapi/zebratruth-v1.yaml post /memory/review-queue/{proposalId}/reject Requires `note` and `reasonCode`. Terminal. **Dashboard session only.** # Send a proposal back for changes Source: https://docs.zebratruth.ai/api-reference/corpus/send-a-proposal-back-for-changes /openapi/zebratruth-v1.yaml post /memory/review-queue/{proposalId}/request_changes Requires `note`; `requestedFields` is optional. **Dashboard session only.** # Extract text from a PDF or Word document Source: https://docs.zebratruth.ai/api-reference/documents/extract-text-from-a-pdf-or-word-document /openapi/zebratruth-v1.yaml post /compliance/documents/extract Returns the text a compliance check would run on, with word and page counts and the limits that apply to your plan. Use it to preview or trim a document before checking it, or to build your own text check. **Not billed.** Extraction is also performed implicitly when you send a `document` part to `POST /compliance/check`. Limits: 30 MB per file, 100 pages, 400,000 characters, 30 seconds of extraction, one document per request, and a per-plan word cap returned as `limits.maxWords`. Scanned PDFs with no text layer are rejected. A `Content-Length` header is required. # Get agent execution trace Source: https://docs.zebratruth.ai/api-reference/jobs/get-agent-execution-trace /openapi/zebratruth-v1.yaml get /compliance/jobs/{jobId}/trace Detailed per-agent metrics for a job. Shows latency, cache hits, and whether any agent was truncated. # Poll job status Source: https://docs.zebratruth.ai/api-reference/jobs/poll-job-status /openapi/zebratruth-v1.yaml get /compliance/jobs/{jobId} Check the status of an async compliance check job. # Resume SSE stream Source: https://docs.zebratruth.ai/api-reference/jobs/resume-sse-stream /openapi/zebratruth-v1.yaml get /compliance/jobs/{jobId}/stream Reconnect to a streaming compliance check after a disconnect. Partial results are persisted server-side. The stream resumes from the next uncompleted agent. # Query request logs Source: https://docs.zebratruth.ai/api-reference/observability/query-request-logs /openapi/zebratruth-v1.yaml get /logs Search your API request history. Filter by external ID, date range, or status code. # API Reference Source: https://docs.zebratruth.ai/api-reference/overview Complete endpoint reference for the ZebraTruth Compliance API # API Reference Base URL: `https://api.zebratruth.ai/v1` All endpoints require authentication via `Authorization: Bearer {api_key}` unless marked public. Endpoints that reserve credits or change state accept an `Idempotency-Key` header; reuse it to retry safely. ## Endpoint Groups * `POST /compliance/check` — Text, or a PDF / Word `document` part. Financial-services scopes via `reviewScopes` * `POST /compliance/check-image` — Image check * `POST /compliance/documents/extract` — Extract document text, not billed * `POST /compliance/media/upload-url` — Create upload URL * `POST /compliance/check-video` — Submit video check (accepts `reviewScopes`) * `GET /compliance/check-video/{requestId}` — Poll status and report * `GET` / `POST /compliance/webhooks/video/secret…` — Video webhook signing secret * `GET /agents` — Discover agents for your industries, including the six `fs-compliance-*` specialists and `pharma-healthcare-compliance` * `POST /agents/{agentId}` — Invoke one agent * `GET /compliance/jobs/{jobId}` — Poll an async check * `GET /compliance/jobs/{jobId}/trace` — Agent trace * `GET /compliance/jobs/{jobId}/stream` — Resume SSE * `GET /reports/{reportId}` — Get report * `POST /reports/{reportId}/replay` — Replay check * `GET /compliance/checks` — List past checks within retention * `GET /compliance/checks/{requestId}` — Full persisted report, including the rules applied * `GET /logs` — Request history * `POST /media/provenance/inspect` — Read Content Credentials on an image or video * `POST /media/provenance/sign` — Sign with your registered signer (video is async) * `GET /media/provenance/jobs/{jobId}` — Poll a video signing job * `GET /media/provenance/signers` — List signers * `GET /media/provenance/signing-keys` — Public: keys used to sign requests to your signer * `GET /whoami` — Validate key, tenant info * `GET /usage` — Credit balance for the period * `GET /usage/summary` — Totals by feature * `GET /usage/project/{projectId}` — Usage for one `externalId` * `GET /subscription` — Plan and subscription state * `GET /version` — Engine version * `POST /billing/checkout` — Start a subscription * `POST /billing/upgrade` — Individual → Pro ## Dashboard-session endpoints A few routes are part of the same API but can only be called from a signed-in dashboard session, never with an API key. They are documented so you know what they return and what your team can automate through the dashboard: * **Saved webhook endpoints** — `GET`, `POST /webhooks` and `DELETE /webhooks/{webhookId}`, plus `GET /webhooks/deliveries`. Owner role, recent-authentication proof, Pro plan. Per-request `webhookUrl` on a check needs none of this and works on every plan. * **Signer registration** — `POST /media/provenance/signers`. Owner role with a recent-authentication proof. * **Corpus review** — `GET /memory/review-queue`, the review packet, and the approve / reject / request-changes actions. Registered reviewers, or company owners and managers. * **Company policies** — publishing your own policies and brand assets happens in the dashboard; every check applies them automatically. ## Plan-restricted endpoints Some endpoints are not available on every plan. On the **Individual** plan, `GET /logs` and the four `/webhooks` management endpoints — list, register, remove, and delivery log — return: ```json theme={null} { "error": true, "statusCode": 403, "statusMessage": "Your plan does not include request logs", "data": { "code": "entitlement_required" } } ``` `403` rather than `402`: the request and credential are both valid, the plan simply does not include the feature. `402` means you are out of credits, which has a different remedy. See [Plans & Billing](/concepts/credits-and-billing). **`data` carries a stable `code` and nothing else.** Every error response is reduced to `{ code }` before it is sent, so no other field — the plan, the entitlement, the limit — is available to your client. Branch on `code`, and use `statusMessage` when you want to show the reason to a person. Sending a `webhookUrl` on a compliance check is **not** plan-restricted, so `responseMode: "async"` works on every plan. Only the saved-endpoint surface above is gated. The video signing-secret routes under `/v1/compliance/webhooks/video/…` are likewise unrestricted. The API reference pages below are auto-generated from the [API specification](https://github.com/dafstar76/zebratruth-ai-labs/blob/master/docs/openapi/zebratruth-v1.yaml). Each endpoint includes request/response schemas and a playground for testing. # Get the keys ZebraTruth signs endpoint requests with Source: https://docs.zebratruth.ai/api-reference/provenance/get-the-keys-zebratruth-signs-endpoint-requests-with /openapi/zebratruth-v1.yaml get /media/provenance/signing-keys Public, unauthenticated. Returns the Ed25519 public keys and the verification recipe your signing endpoint must apply to every request from ZebraTruth before signing. Fetch this before registering a signer. # Inspect Content Credentials on an image or video Source: https://docs.zebratruth.ai/api-reference/provenance/inspect-content-credentials-on-an-image-or-video /openapi/zebratruth-v1.yaml post /media/provenance/inspect Reads the C2PA manifest on an asset and reports whether it is present, valid and signed by a trusted signer, together with a SHA-256 of the bytes and a distribution durability assessment. Send the file as multipart, or a JSON body with a URL. Synchronous. Credits are reserved before any fetch and committed on success. Requires the `provenance:inspect` key scope. # List registered signers Source: https://docs.zebratruth.ai/api-reference/provenance/list-registered-signers /openapi/zebratruth-v1.yaml get /media/provenance/signers Every signer registered for the workspace, including failed and revoked ones, with counts by status. The certificate chain is never returned. Not billed. # Poll a video signing job Source: https://docs.zebratruth.ai/api-reference/provenance/poll-a-video-signing-job /openapi/zebratruth-v1.yaml get /media/provenance/jobs/{jobId} Status of an asynchronous video signing job. On `completed`, a fresh time-limited download link is minted for the signed copy on every call. Jobs are scoped to your tenant; another tenant's job is simply not found. Not billed. Requires the `provenance:sign:invoke` key scope. # Register a signer Source: https://docs.zebratruth.ai/api-reference/provenance/register-a-signer /openapi/zebratruth-v1.yaml post /media/provenance/signers Registers your organisation's signing certificate and endpoint. The chain is validated against the trust anchors, the endpoint is challenged, and proof of possession of the leaf key is verified before the signer becomes `active`. Rejections are persisted as a `failed` signer so they appear in the list with `rejectionCode`. This is an owner-only, dashboard-session action: it requires a recent-authentication proof (`X-Recent-Auth-Proof`) and an `Idempotency-Key`, and cannot be performed with an API key. Fetch `GET /media/provenance/signing-keys` first so your endpoint can verify the challenge. Not billed. # Sign an image or video with Content Credentials Source: https://docs.zebratruth.ai/api-reference/provenance/sign-an-image-or-video-with-content-credentials /openapi/zebratruth-v1.yaml post /media/provenance/sign Attaches a C2PA manifest, signed by one of your registered signers, and stores the signed copy alongside the untouched original. **Images** (multipart `file`, or JSON `url`) are signed synchronously and return 200 with a time-limited download link. **Video** (JSON with `blobPath` from `POST /compliance/media/upload-url`) is signed asynchronously: the call returns 202 with a `jobId`, credits are reserved rather than charged, and you poll `GET /media/provenance/jobs/{jobId}`. A signature is not a compliance approval; `complianceGate.enforced` is always `false`. Requires the `provenance:sign:invoke` key scope, which is granted to service keys only. # Get compliance report Source: https://docs.zebratruth.ai/api-reference/reports/get-compliance-report /openapi/zebratruth-v1.yaml get /reports/{reportId} Retrieve a full compliance report by ID. # Replay a compliance check Source: https://docs.zebratruth.ai/api-reference/reports/replay-a-compliance-check /openapi/zebratruth-v1.yaml post /reports/{reportId}/replay Re-run the exact same check to compare results (e.g., after a rules update). Creates a new job with a new budget reservation. # Mint a video upload URL Source: https://docs.zebratruth.ai/api-reference/video-compliance/mint-a-video-upload-url /openapi/zebratruth-v1.yaml post /compliance/media/upload-url Returns a tenant-scoped, short-lived (15 min) SAS URL the client uses to PUT video bytes **directly to blob storage** — bypassing the API for the upload itself. The returned `requestId` is the handle for the subsequent `POST /compliance/check-video` call. **Why direct upload?** Videos can be up to 200 MB. Streaming them through the API endpoint would add latency, double bandwidth costs, and hit serverless function timeout limits. Direct-to-blob upload is the same pattern major cloud object stores use for large files. **Allowed content types:** `video/mp4`, `video/webm`, `video/quicktime`. The duration limits (3–180s) and file-size cap (200 MB) are enforced by the subsequent `check-video` call's probe step — not at upload time. **The PUT must include the returned `uploadInstructions.headers` verbatim** (they carry ownership tags + the storage `x-ms-blob-type` header). # Poll video job status + retrieve report Source: https://docs.zebratruth.ai/api-reference/video-compliance/poll-video-job-status-+-retrieve-report /openapi/zebratruth-v1.yaml get /compliance/check-video/{requestId} Returns the current state of a video compliance job. When the job is terminal (`status: completed | failed`), the full `report` payload is inlined in the response — no extra fetch needed. **Polling cadence:** every 2 seconds is recommended; the engine has a 3-minute upper bound on a single job's worker time. **`reportFetchFailed`.** Blob storage is eventually-consistent — a read immediately after the worker completes can occasionally miss. Rather than 500, the response surfaces `reportFetchFailed: true` so the client can retry the same endpoint shortly. **Tenant scoping.** A `requestId` belonging to a different tenant returns 404 (no info leak about another tenant's jobs). # Retrieve the tenant's video webhook signing secret Source: https://docs.zebratruth.ai/api-reference/video-compliance/retrieve-the-tenants-video-webhook-signing-secret /openapi/zebratruth-v1.yaml get /compliance/webhooks/video/secret Returns the tenant-level HMAC signing secret used to sign outbound video webhook payloads. **Auto-creates the secret on the first call** and persists it — subsequent calls return the same value. **Verification.** Use this secret in your webhook receiver to verify the `x-zt-webhook-signature` header on each delivery: ``` signature == HMAC_SHA256(secret, "POST" + "\n" + url + "\n" + timestamp + "\n" + sha256(body)) ``` The signature is computed once at delivery enqueue and stays stable across all retries for a given delivery — so your verifier sees the same signature regardless of which attempt arrives. **One-time visibility.** This is the ONLY surface that returns the secret. It never appears in `POST /compliance/check-video` responses or anywhere else. Treat it like an API key; store it in your secret manager. # Rotate the tenant's video webhook signing secret Source: https://docs.zebratruth.ai/api-reference/video-compliance/rotate-the-tenants-video-webhook-signing-secret /openapi/zebratruth-v1.yaml post /compliance/webhooks/video/secret/rotate Generates a brand-new tenant-level webhook signing secret, replacing any existing value. Returns the new secret in the response (the only time it is exposed). **Important — in-flight deliveries are unaffected.** Each delivery's HMAC signature is computed at enqueue time and stored on the delivery record, so retries for already-queued webhooks continue to verify against the OLD secret. **New** jobs sign with the new secret. **Update your verifier immediately.** After rotation, your receiver must accept the NEW secret on the next job's webhook. Most receivers accept both old + new during a brief grace window — recommended. # Submit a video for compliance analysis Source: https://docs.zebratruth.ai/api-reference/video-compliance/submit-a-video-for-compliance-analysis /openapi/zebratruth-v1.yaml post /compliance/check-video Submits an uploaded video (referenced by `requestId` + `blobPath` from a prior `POST /compliance/media/upload-url`) for compliance analysis. **Asynchronous** — returns 202 immediately with a `jobId`. Poll `GET /compliance/check-video/{requestId}` for status + the inlined report when terminal, OR set `webhookUrl` to receive a signed callback. **Required:** `jurisdictions` + `platforms` (mirror text/image checks — same canonical codes, e.g. `["us", "eu"]`, `["youtube", "instagram"]`). Inputs are canonicalized (trim + lowercase + sort) before storage and hashed — case-only differences are treated as the same request. **Idempotency.** The request is keyed on `(blobPath, feature, inputHash)`. Re-posting with the same `requestId` AND the same inputs returns 202 with `idempotent: true`. Different inputs on the same `requestId` returns 409 `idempotency_conflict` with `reason` set to the field that differs. **Billing.** Credits are reserved at submit time (`estimatedCredits` returned). They are committed when the job completes with a real report, OR released for `insufficient_evidence` / `analysis_failed` / poison-queue failures — tenants only pay for valid reports. **Latency.** Typical: 8–30 seconds for a 5–30s video. Longer videos and dense-content frames take proportionally longer. # Get webhook delivery logs Source: https://docs.zebratruth.ai/api-reference/webhooks/get-webhook-delivery-logs /openapi/zebratruth-v1.yaml get /webhooks/deliveries Delivery attempts to saved endpoints, including failed and dead-letter deliveries. **Dashboard session only** and Pro plan, like the other saved-endpoint routes. # List saved webhook endpoints Source: https://docs.zebratruth.ai/api-reference/webhooks/list-saved-webhook-endpoints /openapi/zebratruth-v1.yaml get /webhooks Saved endpoints receive every matching event without a per-request `webhookUrl`. **Dashboard session only**: the webhook management routes are not callable with an API key (403 `permission_not_granted`), and require the Pro plan (403 `entitlement_required`). Per-request `webhookUrl` on a check is unaffected and works on every plan. Secrets are never returned here. # Register a webhook endpoint Source: https://docs.zebratruth.ai/api-reference/webhooks/register-a-webhook-endpoint /openapi/zebratruth-v1.yaml post /webhooks **Dashboard session only**, owner role, with a recent-authentication proof (`X-Recent-Auth-Proof`) and an `Idempotency-Key`. Returns the signing secret exactly once. Deliveries carry `X-ZebraTruth-Signature` (`sha256=` HMAC-SHA256 of the raw body with this secret), `X-ZebraTruth-Timestamp` and `X-ZebraTruth-Event-Id`; retries reuse all three, so de-duplicate on the event id. # Remove a webhook endpoint Source: https://docs.zebratruth.ai/api-reference/webhooks/remove-a-webhook-endpoint /openapi/zebratruth-v1.yaml delete /webhooks/{webhookId} **Dashboard session only**, owner role, with a recent-authentication proof and an `Idempotency-Key`. # Authentication Source: https://docs.zebratruth.ai/authentication API keys, validation, and security # Authentication All API requests require a bearer token in the `Authorization` header. ```bash theme={null} Authorization: Bearer zt_live_your_key_here ``` ## API Key Types | Prefix | Environment | Credits | Agents | Use For | | ---------- | ----------- | ------------- | ------------------------------ | ------------------------ | | `zt_live_` | Production | Consumed | Real agents | Live integrations | | `zt_test_` | Sandbox | None consumed | Deterministic canned responses | Development, testing, CI | Never share API keys in conversation, chat logs, or client-side code. Always use environment variables or a secrets manager. ## Getting a Key 1. Sign up at [developers.zebratruth.ai](https://developers.zebratruth.ai) 2. Navigate to **Dashboard → API Keys** 3. Click **Create Key** and choose a name and environment (live or test) 4. Copy the key immediately — it is shown only once ## Validating Your Key ```bash theme={null} curl https://api.zebratruth.ai/v1/whoami \ -H "Authorization: Bearer $ZEBRATRUTH_API_KEY" ``` ```json 200 — Valid Key theme={null} { "tenantId": "tenant_abc", "tier": "pro", "creditsRemaining": 82500, "creditsTotal": 83250, "rateLimitPerMinute": 300 } ``` ```json 401 — Invalid Key theme={null} { "error": "Invalid API key" } ``` ## Test Mode Test keys (`zt_test_...`) return deterministic sandbox responses: * No credits deducted * No real LLM calls — responses are pre-built canned scenarios * Deterministic — same input always produces the same output * Responses include `_meta.mode: "test"` so you can detect sandbox mode ```json Test Mode Response theme={null} { "score": 55, "decision": "HOLD", "checks": [...], "_meta": { "mode": "test", "sandbox": true, "creditsCharged": "…", "estimatedProductionCost": "…", "scenarioId": "claim-violation" } } ``` ## Idempotency All mutating endpoints (POST, DELETE) require an `Idempotency-Key` header: ```bash theme={null} Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000 ``` * Generate a new UUID for each distinct request * Reuse the same key to safely retry a failed request * Prevents duplicate execution and double-charging * Keys expire after 24 hours ## Rate Limits | Tier | Requests/min | Concurrent Load Units | | ---------- | ------------ | --------------------- | | Free | 10 | 2 | | Individual | 30 | 5 | | Pro | 300 | 25 | | Enterprise | Custom | Custom | When rate-limited, the API returns `429` with a `Retry-After` header indicating seconds to wait. # Compliance Agents Source: https://docs.zebratruth.ai/concepts/agents The specialist agents behind every check, what each one covers, and how to call one directly # Compliance Agents Every check is answered by a set of specialist agents, each responsible for one body of rules. The agents that run depend on the industries your workspace has selected and, for financial services, on the review scopes you choose per request. Agents are added regularly. Always use `GET /agents` to discover the set available to your workspace rather than hardcoding ids. ## Financial services Six specialists, one per review scope. In a check they are selected with `reviewScopes` on `POST /compliance/check` or `POST /compliance/check-video`. Each runs against the governed corpus for its scope and reports how it finished in the response's `agentsRun`. | Review scope | `agentId` | What it checks | | ---------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | | `financial_promotion` | `fs-compliance-financial-promotion` | Financial promotions, product and fund disclosures, and conduct rules for marketing to retail and professional clients | | `advisory_suitability` | `fs-compliance-advisory-suitability` | Investment advice, suitability and appropriateness, portfolio management, and the advice / guidance boundary | | `credit_servicing` | `fs-compliance-credit-servicing` | Credit origination, creditworthiness, pre-contract information, adverse-action notices, servicing and debt collection | | `markets_trading` | `fs-compliance-markets-trading` | Order execution and best-execution disclosures, transaction reporting, market abuse, research and investment recommendations | | `financial_crime` | `fs-compliance-financial-crime` | KYC and customer due diligence, AML/CFT, fraud and scam prevention, sanctions obligations | | `ai_governance` | `fs-compliance-ai-governance` | AI governance, model risk, automated decision-making and cyber-resilience obligations | Coverage is US, EU and UK for every scope, plus China and India for financial promotions. Tell the specialists what they are reading with `contentKind` (for example `advice_letter`, `adverse_action_notice`, `kyc_onboarding`) and, where it matters, which regulated actor the check is for with `regulatedActors`. **Direct invocation.** Only `fs-compliance-financial-promotion` can be called through `POST /agents/{agentId}` today. The other five return `400 review_scope_not_enabled` on direct invocation and run as part of a full check. `financial-services-compliance` is an older alias of the promotion agent that is still accepted. ## Pharma and healthcare | Agent | `agentId` | What it checks | | ---------------------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Pharma & Healthcare Compliance** | `pharma-healthcare-compliance` | Advertising and promotion of medicines and devices, disclosure and fair-balance rules, industry codes, digital-health obligations and health-specific privacy across US, EU, UK and India | Set `regulatedActor` (for example `manufacturer`, `provider`, `pharmacy`, `HCP`, `telehealth_provider`) so only rules that bind that actor can drive a blocking finding. It defaults to `advertiser`. ## Media and advertising | Agent | `agentId` | Phase | Input | What it checks | | ---------------------------- | ------------------------ | ----- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **AI Laws** | `ai-laws` | 1 | Text | AI-content laws — disclosure, watermarking, transparency (EU AI Act Art. 50, California SB 942, Deep Synthesis Provisions, IT Rules, ELVIS Act, etc.) | | **Advertising Law** | `advertising-law` | 2 | Text | Claim substantiation, endorsement rules, comparative advertising, targeting restrictions | | **Platform Policy** | `platform-policy` | 2 | Text | YouTube, Instagram, Facebook, TikTok, LinkedIn, Douyin, Weibo policies | | **Metadata & Labeling** | `metadata-labeling` | 3 | Text | C2PA Content Credentials, AI disclosure labels, machine-readable provenance | | **Rights Clearance (Text)** | `rights-clearance-text` | 3 | Text | Celebrity references, trademarks, copyrighted characters, music licensing, voice cloning | | **Rights Clearance (Image)** | `rights-clearance-image` | 3 | Image | Restricted categories (alcohol, weapons, tobacco, gambling, pharmaceuticals, financial products), content safety (adult / violence / racy), brand logos, stock-agency watermarks, C2PA metadata. See the [Restricted Categories guide](/guides/restricted-categories). | | **Video Compliance Check** | `compliance-check-video` | async | Video | Restricted categories, content safety, on-screen text (burned-in disclaimers, `#ad` labels), spoken-audio claims, brand logos, watermarks, AI provenance — orchestrated async pipeline via `POST /v1/compliance/check-video`. Findings carry timestamped `evidenceIds` and `timelineLocation` so clients can jump to the offending second. | | **Compliance Score** | `compliance-score` | 4 | All | Deterministic scoring from all checks | ## Cross-industry rules Privacy, rights and IP, AI-disclosure law and ad-platform policy are applied to every workspace whatever its industries. Company policies and brand assets published from the dashboard are applied alongside them. ## Reading `agentsRun` When a check runs financial-services specialists, the response lists each one with an outcome: | `outcome` | Meaning | | ----------------------- | ------------------------------------------------------------------------------------------------------ | | `completed` | Ran and produced findings | | `completed_no_findings` | Ran and found nothing to report | | `not_applicable` | Skipped because nothing in the artifact triggered it | | `failed_required` | A required specialist failed; the check is HOLD with `decisionReasons: ["required_review_incomplete"]` | | `failed_optional` | An optional specialist failed; the decision stands on the others | | `insufficient_evidence` | The artifact did not give it enough to assess | A failure carries a `reason`. `legal_context_unavailable` means the scope's corpus is not yet being served for your workspace. ## Invoking a single agent Call any discoverable agent directly via `POST /agents/{agentId}` when you only need one aspect re-checked. See the [Individual Agents guide](/guides/individual-agents). # Annotations Source: https://docs.zebratruth.ai/concepts/annotations Map compliance issues to content locations for native UI embedding # Annotations Annotations map compliance issues to **specific locations** in your content. They're what makes ZebraTruth results feel native inside client products — not just raw JSON reports. ## Annotation Types ### Text Span For scripts, ad copy, captions, descriptions. Uses character offsets. ```json theme={null} { "type": "text-span", "agentId": "advertising-law", "severity": "high", "message": "'Guaranteed' claim may violate FTC rules", "suggestion": "Consider 'may help' instead", "textLocation": { "start": 120, "end": 137, "matchedText": "guaranteed results" } } ``` ### Timeline For video content. Uses millisecond ranges. ```json theme={null} { "type": "timeline", "agentId": "metadata-labeling", "severity": "medium", "message": "AI-generated segment missing disclosure label", "suggestion": "Add visible AI disclosure overlay", "timelineLocation": { "startMs": 15000, "endMs": 28000, "track": "video" } } ``` ### Image Region For visual content. Uses bounding box coordinates. ```json theme={null} { "type": "image-region", "agentId": "rights-clearance-image", "severity": "high", "message": "Possible celebrity face detected", "suggestion": "Obtain likeness rights or replace", "imageLocation": { "imageUrl": "https://example.com/ad.jpg", "boundingBox": { "x": 120, "y": 45, "width": 200, "height": 250 } } } ``` ### Global Applies to the entire content, not a specific location. ```json theme={null} { "type": "global", "agentId": "platform-policy", "severity": "medium", "message": "YouTube requires AI disclosure toggle for AI-generated content", "suggestion": "Enable the AI-generated content disclosure in YouTube Studio" } ``` ## Using Annotations in Your UI See [Embedding Annotations guide](/guides/embedding-annotations) for detailed implementation patterns including suggested severity colors and grouping strategies. # Compliance Pipeline Source: https://docs.zebratruth.ai/concepts/compliance-pipeline Fast vs Full mode execution # Compliance Pipeline ## Fast Mode All agents run **in parallel** with raw content. No enrichment between phases. * Best for: quick checks, real-time UI feedback, development ## Full Mode **4-phase sequential pipeline** with content enrichment between phases: 1. **Phase 1** — Jurisdiction agent detects applicable laws and creates a compliance profile 2. **Phase 2** (parallel) — Advertising Law + Platform agents see Phase 1 findings 3. **Phase 3** (parallel) — Metadata + Rights agents see Phase 1 + Phase 2 findings 4. **Phase 4** — Score agent aggregates all checks * Best for: final pre-publish checks, thorough analysis ## Choosing a Mode | Scenario | Recommended Mode | | ------------------------ | ---------------- | | Real-time editor checks | Fast | | Pre-publish final review | Full | | Batch processing | Full (async) | | Development/testing | Fast | ## Response Modes Both fast and full mode support three response modes: * **Sync** — block until complete, return full report * **Stream** — SSE events per agent (ideal for full mode) * **Async** — return immediately, deliver via webhook # Plans & Billing Source: https://docs.zebratruth.ai/concepts/credits-and-billing Plans, entitlements, scope limits and the billing endpoints # Plans & Billing ZebraTruth meters usage in **credits**. Every check reserves credits before it runs and commits them when it completes, so `GET /v1/usage` always reflects what the current period can still spend. ## Plans | Plan | Intended for | | ---------- | --------------------------------------- | | Free trial | Evaluating the full breadth of coverage | | Individual | Solo use | | Pro | Teams and production integrations | | Enterprise | Custom scope, volume and support | Each plan carries a credit allowance per billing period, a rate limit and a concurrency limit. Current allowances are shown on the [dashboard](https://developers.zebratruth.ai). Credits do not roll over. ### Individual plan limits Individual is scoped to solo use. It includes compliance checks and the API, but not the operational surface around them: | | Individual | Pro | | ----------------------- | ---------- | -------- | | Jurisdictions | Up to 2 | All | | Platforms | Up to 2 | All | | Check history | 30 days | 365 days | | Request logs | — | Yes | | Saved webhook endpoints | — | Yes | | Per-request callbacks | Yes | Yes | The same limits apply to an **expired trial**. The trial itself is unlimited — it is there to show the full breadth of coverage — but once it lapses the account is held to the entry-level allowance until a plan is chosen. Calling a restricted endpoint returns `403 entitlement_required`. ### Callbacks work on every plan Only **saved webhook endpoints** are restricted — registering them, listing them, and reading delivery history, all under `/v1/webhooks`. Those return `403 entitlement_required` on Individual. Passing a `webhookUrl` on an individual request is not restricted, on any plan. `"responseMode": "async"` therefore works on Individual too, which matters most there: a video check can run for minutes, and async is how you avoid holding the connection open for it. On Individual, set `webhookUrl` per request. There is nothing to register in advance, and no delivery history to read afterwards — treat the callback itself as the record. ### Scope limits are enforced twice | Code | When | What to do | | ----------------------------- | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- | | `scope_limit_exceeded` | You tried to **save** more jurisdictions or platforms than the plan allows | Select fewer, or upgrade | | `scope_limit_exceeded_config` | A **request** would have used a saved scope that is wider than the plan allows | Reduce the selection in settings, or send at most the allowed number on the request | The second one exists because your plan can change *after* you chose your scope. Every Individual subscription starts as a trial, where the limits do not apply, so a selection made then can be wider than the plan you end up on. Rather than silently narrowing a compliance check — which would change its verdict while still returning `200` — the request is refused. A request that names its own jurisdictions and platforms is judged on what it names, so you can keep working immediately by sending no more than the limit, without editing your saved settings first. ## Changing plan Start a subscription with `POST /v1/billing/checkout`, passing `plan` as either `individual` or `pro`. ### Upgrading Individual to Pro `POST /v1/billing/upgrade` moves an Individual subscription to Pro immediately: * You are charged for Pro straight away. * Your billing period restarts on the upgrade date, and renewal moves with it. Upgrading on the 14th means your next charge is the 14th of the following month. * Unused Individual credits carry into the first Pro period, on top of the Pro allowance. `GET /v1/usage` reports the two parts separately as `baseCredits` and `carriedCredits`. The request requires an `Idempotency-Key` header. Reuse the same key when retrying after a timeout — a retry with a new key can charge twice. Downgrading from Pro to Individual is not supported. ## Checking Your Balance ```bash theme={null} GET /v1/usage ``` Returns `creditsRemaining`, `creditsTotal`, `creditsUsedThisMonth`, and period dates. `creditsTotal` is also broken out as `baseCredits` (your plan's allowance) plus `carriedCredits` (anything carried in from an upgrade). `carriedCredits` is `0` unless you have upgraded from Individual to Pro mid-period — without it, a Pro invoice next to a cap above the Pro allowance looks like a billing error. ## Cost Breakdown Every response includes `costBreakdown` showing per-agent, per-stage credit usage with cache hit indicators. # Images, Documents and Video Source: https://docs.zebratruth.ai/concepts/other-content-types How the same compliance check applies to images, PDF and Word documents, and video # Images, Documents and Video A check is the same operation whatever the agent produced. Text goes to `POST /v1/compliance/check` as JSON. This page covers the other three artifact types, which share the report envelope, the decision vocabulary and the industry rules. | You have | Endpoint | Mode | | -------------------------------------------------- | ----------------------------------------------------------------------------- | --------------------- | | Text: a notice, letter, disclosure, script or copy | `POST /v1/compliance/check` (JSON) | sync, stream or async | | A PDF or Word document | `POST /v1/compliance/check` (multipart `document`) | sync | | A single image | `POST /v1/compliance/check-image` (multipart) | sync | | A video | `POST /v1/compliance/media/upload-url` then `POST /v1/compliance/check-video` | async | ## Documents Send one PDF or `.docx` as a multipart `document` part. The text is extracted server-side and checked exactly as pasted text would be, including financial-services review scopes and content kinds. ```bash theme={null} curl -X POST https://api.zebratruth.ai/v1/compliance/check \ -H "Authorization: Bearer $ZEBRATRUTH_API_KEY" \ -F "document=@precontract-disclosure.pdf" \ -F "jurisdictions=uk" \ -F "platforms=linkedin" \ -F "reviewScopes=credit_servicing" \ -F "contentKind=precontract_disclosure" ``` Limits: 30 MB, 100 pages, 400,000 characters, one document per request, and a per-plan word cap. Scanned PDFs with no text layer are rejected with `document_no_extractable_text`; legacy `.doc` is not accepted. The multipart path is sync only. To inspect the text before checking it, call `POST /v1/compliance/documents/extract`, which is not billed. ## Images **Endpoint:** `POST https://api.zebratruth.ai/v1/compliance/check-image` Synchronous. Multipart form upload, no separate upload-URL step. Send `jurisdictions` and `platforms` as repeated fields. ```bash theme={null} curl -X POST https://api.zebratruth.ai/v1/compliance/check-image \ -H "Authorization: Bearer $ZEBRATRUTH_API_KEY" \ -F "image=@creative.jpg" \ -F "jurisdictions=us" -F "jurisdictions=eu" \ -F "platforms=instagram" -F "platforms=facebook" ``` Returns the same report envelope as a text check, with findings located by `bbox` rather than by sentence. Detects on-image claims, brand marks and stock-agency watermarks, restricted categories, content safety, provenance metadata, and your published brand rules and reference assets. Image checks do not run the financial-services specialists; sending `reviewScopes` or `contentKind` returns `400 review_scope_not_enabled`. See the [image guide](/guides/image-rights-clearance). ## Video Video is asynchronous: request a signed upload URL, upload the bytes directly to managed storage, submit the check, then poll or receive a webhook. Findings carry stable evidence ids and a `timelineLocation`, so a reviewer can jump to the second where a spoken claim, on-screen text or visual signal appears. Financial-services review scopes and `contentKind` are accepted on the JSON submission. Up to 200 MB and 3 to 180 seconds. See the [video guide](/guides/video-compliance-check) and the [video concept page](/video). ## Choosing between them * If the artifact is a video, check the video. Submitting frames to the image endpoint loses the transcript and the temporal context that spoken claims depend on. * If the artifact is a document, send the document rather than pasting its text; the persisted check then records the format and the extraction, which matters for the audit trail. * If an image carries the only claim, check the image; if the claim is in the surrounding copy, check the text and attach the image with `content.imageUrls`. ## API reference Every endpoint, request body, response envelope and error code. # Scoring Source: https://docs.zebratruth.ai/concepts/scoring How compliance scores and decisions work # Scoring ## Score Calculation Starting from **100**, each non-pass check deducts points based on severity: | Severity | Point Deduction | | -------- | --------------- | | Critical | -25 | | High | -15 | | Medium | -8 | | Low | -3 | | Info | 0 | Score is clamped to **\[0, 100]**. ## Decision Logic | Score Range | Decision | Condition | | ----------- | --------- | ---------------------------------------------- | | 80-100 | `PUBLISH` | No critical or high-severity checks | | 60-79 | `HOLD` | Score below 80, or any high-severity flag | | 0-59 | `BLOCK` | Score below 60, or any critical-severity check | A single `critical` severity check always triggers `BLOCK` regardless of score. A single `high` severity check triggers `HOLD` if the score would otherwise be `PUBLISH`. ## Agent Summaries Each response includes `agentSummaries` — a high-level view per agent: ```json theme={null} { "advertising-law": { "status": "flag", "checkCount": 5, "passCount": 3, "flagCount": 2, "blockCount": 0, "summary": "2 advertising claim issues found" } } ``` # Webhooks Source: https://docs.zebratruth.ai/concepts/webhooks Async delivery, HMAC security, retry policy # Webhooks Webhooks deliver compliance results asynchronously when the request uses `responseMode: "async"`. The webhook URL and a per-job signing secret are supplied with the original request and returned in the 202 response — no upfront endpoint registration is required. For a full submit → receive → verify walkthrough, see the [Async + Webhook Execution guide](/guides/async-webhook-execution). ## Event types | Event | Fired on | | ---------------------- | -------------------------------------------------------------- | | `compliance.completed` | Successful end-to-end run, includes the full classified result | | `compliance.failed` | Pipeline failure or function timeout | ## Delivery guarantees * **At-least-once delivery.** Use the top-level `eventId` to deduplicate on your side. * **HMAC signature** in the `X-ZebraTruth-Signature` header: `sha256=`. * **Replay protection.** `X-ZebraTruth-Timestamp` is an ISO 8601 timestamp; reject deliveries with drift greater than 5 minutes. * **Per-attempt timeout.** Each HTTP attempt has a 10-second timeout. * **Retry policy.** Up to 4 attempts (initial + 3 retries) with delays `1 s, 5 s, 25 s`. Total worst-case window: \~31 s. * **Dead-letter recording.** After exhaustion, the delivery is recorded with status `dead` in the internal delivery log. ## Webhook payload ```json theme={null} { "eventId": "evt_0971a7addc874ba7", "event": "compliance.completed", "jobId": "job_dd1928e710aa4e16", "callbackId": "my-internal-id-456", "timestamp": "2026-05-01T03:58:52.553Z", "version": 1, "data": { "jobId": "job_dd1928e710aa4e16", "score": 10, "decision": "BLOCK", "checks": [ /* ... */ ], "checksById": { /* ... */ }, "indexes": { /* ... */ }, "annotations": [], "profile": { /* ... */ }, "agentSummaries": { /* ... */ }, "versionInfo": { /* ... */ }, "creditsCharged": "…", "cached": false, "costBreakdown": { /* ... */ } } } ``` `callbackId` is the opaque tag you supplied on the original request — included so you can correlate the webhook with whatever record triggered it. ## Headers on every delivery | Header | Example | Purpose | | ------------------------ | -------------------------- | ------------------------------ | | `Content-Type` | `application/json` | — | | `X-ZebraTruth-Event-Id` | `evt_0971a7addc874ba7` | Idempotency key for dedup | | `X-ZebraTruth-Timestamp` | `2026-05-01T03:58:52.553Z` | Replay protection | | `X-ZebraTruth-Signature` | `sha256=bbb03af377...` | HMAC over the raw request body | ## Verifying a webhook ```javascript theme={null} import crypto from 'node:crypto' function verifyWebhook({ rawBody, headers, secret }) { const signature = headers['x-zebratruth-signature'] const timestamp = headers['x-zebratruth-timestamp'] if (!signature || !timestamp) return false // Reject deliveries more than 5 minutes off the wall clock if (Math.abs(Date.now() - new Date(timestamp).getTime()) > 5 * 60 * 1000) { return false } const expected = 'sha256=' + crypto .createHmac('sha256', secret) .update(rawBody) .digest('hex') return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected), ) } ``` Always verify against the **raw bytes** of the body — re-stringifying parsed JSON breaks the HMAC even if the payload is semantically identical. The `secret` is the `webhookSecret` returned in the 202 response when the job was submitted. Store it keyed by `jobId`. Each async job gets a fresh secret; secrets are not shared across jobs. ## Failed delivery and recovery If your webhook handler returns a non-2xx status (or the request times out), we retry with the schedule above. After all attempts fail, the result is still available via the [polling endpoint](/guides/async-webhook-execution#step-3-polling-fallback): ```http theme={null} GET /v1/compliance/jobs/{jobId} ``` Polling is the recovery / verification fallback. Use it to backfill missed webhooks or to confirm that a webhook payload matches what's actually in our system. You can also list dead-letter deliveries for the tenant: ```http theme={null} GET /v1/webhooks/deliveries?status=dead ``` ## When the orchestrator fails A pipeline error fires `compliance.failed`: ```json theme={null} { "event": "compliance.failed", "jobId": "job_...", "data": { "jobId": "job_...", "error": { "message": "...", "code": "pipeline_error" } } } ``` Common error codes: | `code` | Meaning | | ---------------- | --------------------------------------------------------------------------------------------------------------------- | | `pipeline_error` | An agent or sub-agent threw during the run | | `vercel_timeout` | Set by the stale-job sweeper when a run exceeded the function `maxDuration` ceiling without reaching a terminal state | # Async + Webhook Execution Source: https://docs.zebratruth.ai/guides/async-webhook-execution Submit a compliance check, get notified by webhook when it's ready, fall back to polling if the webhook misses # Async + Webhook Execution Use `responseMode: "async"` when you can't or shouldn't hold an HTTP connection open for the duration of a compliance run (server-to-server pipelines, mobile apps, batch workflows). The API returns immediately with a `jobId`, processes the check in the background, and delivers the result to your webhook URL when complete. A polling endpoint exists as a recovery and verification fallback. ## How it works Submit a check with `responseMode: "async"` and a `webhookUrl`. You get back `202 + jobId` immediately. When the check completes, ZebraTruth POSTs the HMAC-signed report to your `webhookUrl` (primary delivery). A polling endpoint at `GET /v1/compliance/jobs/{jobId}` exists as a recovery / verification fallback. ## Step 1 — Submit the check ```http theme={null} POST https://api.zebratruth.ai/v1/compliance/check Authorization: Bearer {api_key} Content-Type: application/json { "jurisdictions": ["us"], "platforms": ["youtube"], "content": { "text": "..." }, "mode": "fast", "responseMode": "async", "webhookUrl": "https://your-app.com/webhooks/zebratruth", "callbackId": "my-internal-id-456" } ``` `webhookUrl` is **required** when `responseMode` is `async` — the API returns `400` without it. `callbackId` is your opaque tag, echoed back in the webhook payload and the polling response. Use it to correlate the result with whatever record triggered the check. ### Response — `202 Accepted` ```json theme={null} { "jobId": "job_dd1928e710aa4e16", "status": "queued", "callbackId": "my-internal-id-456", "webhookSecret": "whsec_4b8a2e7f9c1d3e6a5b8c0f2d4e7a1b3c" } ``` **Save the `webhookSecret` keyed by `jobId`.** You'll use it to verify the HMAC on the eventual webhook delivery. **It is returned only in this 202 response** — the polling endpoint never re-exposes it. The 202 returns in well under a second; orchestration runs in the background. ## Step 2 — Receive the webhook When the check completes, ZebraTruth POSTs to your `webhookUrl`: ```http theme={null} POST https://your-app.com/webhooks/zebratruth Content-Type: application/json X-ZebraTruth-Event-Id: evt_0971a7addc874ba7 X-ZebraTruth-Timestamp: 2026-05-01T03:58:52.553Z X-ZebraTruth-Signature: sha256=bbb03af37790b7d003d3a3c13a620018e340ded24b45c28eb1e82a23a64d131b { "eventId": "evt_0971a7addc874ba7", "event": "compliance.completed", "jobId": "job_dd1928e710aa4e16", "callbackId": "my-internal-id-456", "timestamp": "2026-05-01T03:58:52.553Z", "version": 1, "data": { "jobId": "job_dd1928e710aa4e16", "score": 10, "decision": "BLOCK", "checks": [ /* full classified checks */ ], "checksById": { /* O(1) lookup map */ }, "indexes": { /* byJurisdiction / bySeverity / etc. */ }, "annotations": [], "profile": { /* compliance profile */ }, "agentSummaries": { /* per-agent rollup */ }, "versionInfo": { /* stack version */ }, "creditsCharged": "…", "cached": false, "costBreakdown": { "totalCredits": "…", "agents": [] } } } ``` ### On failure — `compliance.failed` ```json theme={null} { "eventId": "evt_...", "event": "compliance.failed", "jobId": "job_...", "callbackId": "...", "timestamp": "...", "version": 1, "data": { "jobId": "job_...", "error": { "message": "Compliance pipeline error: ...", "code": "pipeline_error" } } } ``` ### Verifying the signature ```javascript theme={null} import crypto from 'node:crypto' function verifyWebhook({ rawBody, headers, secret }) { const signature = headers['x-zebratruth-signature'] const timestamp = headers['x-zebratruth-timestamp'] if (!signature || !timestamp) return false // Reject if the timestamp is more than 5 minutes off — replay protection. if (Math.abs(Date.now() - new Date(timestamp).getTime()) > 5 * 60 * 1000) { return false } const expected = 'sha256=' + crypto .createHmac('sha256', secret) .update(rawBody) .digest('hex') // Constant-time comparison return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected), ) } ``` Pass `rawBody` as the unparsed request body bytes. Do not parse + re-stringify the JSON before verifying — even semantically-equivalent reformatting changes the byte representation and breaks the HMAC. ### Delivery guarantees * **At-least-once.** Use `eventId` to deduplicate on your side. * **Retry policy.** Up to 4 attempts (initial + 3 retries) with delays `1 s, 5 s, 25 s`. After exhaustion the delivery is recorded as `dead` in our internal log. * **Timeout.** Each delivery attempt has a 10-second per-request timeout. Make your webhook handler fast — return `2xx` and process asynchronously if the work is heavy. If your webhook returns any non-`2xx` status code, we treat it as a failure and retry. ## Step 3 — Polling (fallback) Polling exists as a **recovery and verification mechanism**. Webhooks are the recommended primary delivery channel. ### Use cases for polling * The webhook delivery failed all 4 attempts and you need to recover the result. * You want to verify the data in the webhook matches what's actually on our side (defence-in-depth). * Your client cannot receive webhooks at all (no public URL, behind NAT) — in which case async is still useful for the "submit and check later" pattern, you just rely on polling for delivery. * You want to reconcile what was charged across a recent batch. ### Endpoint ```http theme={null} GET https://api.zebratruth.ai/v1/compliance/jobs/{jobId} Authorization: Bearer {api_key} ``` ### Recommended cadence **First poll at 30 seconds, then every 30 seconds.** Most fast-mode checks complete in 60–110 seconds; full-mode in 90–240 seconds. The polling endpoint enforces a hard floor: a minimum of 5 seconds between polls per `(tenantId, jobId)`. Polls faster than that get `429` with `Retry-After` guidance. ### Response shapes While the job is in flight (`queued` or `running`): ```json theme={null} { "jobId": "job_dd1928e710aa4e16", "status": "running", "createdAt": "2026-05-01T03:00:00.000Z", "startedAt": "2026-05-01T03:00:00.020Z", "callbackId": "my-internal-id-456" } ``` Once `completed`: ```json theme={null} { "jobId": "job_dd1928e710aa4e16", "status": "completed", "createdAt": "...", "startedAt": "...", "completedAt": "2026-05-01T03:01:33.702Z", "callbackId": "my-internal-id-456", "result": { /* same shape as the webhook payload's `data` field */ } } ``` If `failed`: ```json theme={null} { "jobId": "...", "status": "failed", "completedAt": "...", "error": { "message": "...", "code": "pipeline_error" } } ``` ### Tenant scoping A request for a `jobId` that doesn't belong to the authenticated tenant returns `404 Job not found`. Job existence is never leaked across tenants. ### After 90 days Job rows and their report blobs are retained for 90 days. After that: * The polling endpoint returns `410 Gone` if the row's still there but the report blob has been pruned. * The polling endpoint returns `404 Not Found` if the row itself has been swept. ## Streaming an in-flight async job If you submitted with `responseMode: "async"` but later want a live progress feed, open an SSE connection to: ```http theme={null} GET https://api.zebratruth.ai/v1/compliance/jobs/{jobId}/stream Authorization: Bearer {api_key} Accept: text/event-stream ``` This emits a synthetic `job.queued` or `job.started` event on connect, then the terminal `complete` or `error` event when the job reaches that state. Live per-agent events (`agent.started` / `agent.completed`) are **not** replayed — for full per-agent streaming, use `responseMode: "stream"` from the start. The resume stream times out after 4 minutes; if reached it emits an `error` event with code `stream_timeout` and closes. Recover by polling the GET endpoint. ## Operational notes * **Function timeout ceiling.** Each async job runs inside a serverless function with a 300-second ceiling. Jobs that exceed it (extremely long full-mode runs with all jurisdictions and platforms) may be killed mid-orchestration, leaving the row in `running` until a sweeper flips it to `failed` with code `vercel_timeout`. We're working on a queue-backed worker for unbounded async — talk to us if you hit this. * **Idempotency.** Pass an `Idempotency-Key` header on the POST to deduplicate retries safely. * **No queue priority yet.** Async jobs run as soon as the request arrives; there is no per-tier queue prioritisation. # Content Compliance Check Source: https://docs.zebratruth.ai/guides/content-compliance-check Step-by-step guide # Content Compliance Check Full procedure for running a compliance check in any of the three response modes. ## Prerequisites * Valid API key (validated via `GET /whoami`) * **Completed onboarding** — your tenant record must have default jurisdictions and platforms set. See the [Tenant Onboarding and Scoping guide](/guides/tenant-onboarding-and-scoping). Requests from a tenant that hasn't onboarded return 403. * Content to check (text, optionally images) * Target jurisdictions and platforms — must be a subset of what you subscribed to ## Procedure ### Step 1: Check Credits ``` GET https://api.zebratruth.ai/v1/usage Authorization: Bearer {api_key} ``` Verify `creditsRemaining` is sufficient for the checks you are about to run. If insufficient, inform the user and suggest upgrading at the `upgradeUrl` in the response. ### Step 2: Choose Mode and Response Mode **Execution mode:** * `fast` — All agents run in parallel. Faster. Good for quick checks. * `full` — 4-phase pipeline with content enrichment between phases. Thorough. Use for final pre-publish checks. **Response mode:** * `sync` — Block until complete. Best for fast mode or simple integrations. * `stream` — SSE events per agent. Best for full mode with real-time UI updates. * `async` — Return immediately, deliver result via webhook. Best for batch processing. ### Step 3: Submit the Check Two request formats are supported. **JSON** — canonical format, full feature set (streaming, webhooks, image URLs): ``` POST https://api.zebratruth.ai/v1/compliance/check Authorization: Bearer {api_key} Content-Type: application/json Idempotency-Key: {generate-a-uuid-here} { "jurisdictions": ["us", "eu"], "platforms": ["youtube", "instagram"], "content": { "text": "The full ad script or content text goes here.", "imageUrls": ["https://example.com/ad-image.jpg"] }, "mode": "fast", "responseMode": "sync", "externalId": "my-project-123" } ``` **Multipart** — use when you need to upload an image binary directly instead of referencing a public URL: ```bash theme={null} curl -X POST https://api.zebratruth.ai/v1/compliance/check \ -H "Authorization: Bearer $ZEBRATRUTH_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -F "text=The full ad script or content text goes here." \ -F "image=@./ad.png" \ -F "jurisdictions=us" -F "jurisdictions=eu" \ -F "platforms=youtube" -F "platforms=instagram" \ -F "mode=fast" ``` ### Step 4: Handle the Response **Sync mode** — the full report is in the response body. **Stream mode** — see [Streaming Integration](/guides/streaming-integration) **Async mode** — see [Async and Webhook Execution](/guides/async-webhook-execution) ### Step 5: Interpret Results 1. Check `decision`: PUBLISH, HOLD, or BLOCK 2. If HOLD or BLOCK, review `checks[]` for detailed findings 3. Use `annotations[]` to map issues to content locations 4. Present `recommendation` fields as actionable fixes ### Step 6: Iterate (if needed) If the decision is HOLD or BLOCK: 1. Fix the flagged content based on recommendations 2. Re-submit with the updated content (use a NEW `Idempotency-Key`) 3. Repeat until the decision is PUBLISH ## Latency & Timeouts Sync compliance checks typically complete in **15–120 seconds**, depending on: * Execution mode (`fast` is parallel across agents; `full` is a 4-phase pipeline and runs longer) * Number of jurisdictions and platforms in scope (more = more legal context injected into each agent prompt) * Content length (longer text = more tokens per agent) * Whether research-stage caches hit (Stage A is cached with a 30-day TTL; identical topic queries return in milliseconds) Cached full-result hits (identical `content.text + jurisdictions + platforms + mode`) return in **≤1 second** and consume no credits. **Recommended client configuration:** * Set your HTTP client timeout to **at least 2.5 minutes (150 seconds)** to leave headroom above the upper typical bound. Timeouts shorter than 2 min will occasionally cut healthy requests. * For workflows where a 90-second block is unacceptable (batch jobs, UI threads), use `responseMode: "async"` with a webhook — the endpoint returns immediately with a `jobId`, and the result is POSTed to your webhook when ready. See [async-webhook-execution.md](/guides/async-webhook-execution). * For progressive UI updates while the check runs, use `responseMode: "stream"` (SSE) — see [streaming-integration.md](/guides/streaming-integration). ## Error Handling * **400 Bad Request**: Check that `jurisdictions` and `platforms` contain valid IDs. * **402 Payment Required**: Credits exhausted. Response includes `creditsRemaining` and `upgradeUrl`. * **403 Forbidden** — tenant scope: The request asks for a jurisdiction or platform outside your subscription, or your tenant record is missing / incomplete. The `error` field carries the exact runtime message. Four shapes: * `"Tenant not onboarded. Complete setup at https://developers.zebratruth.ai before using the API."` — no tenant record yet * `"Tenant configuration incomplete. Set default jurisdictions and platforms in your dev portal."` — tenant exists but has empty defaults * `"Requested jurisdictions [india, china] are not in your subscription. Allowed: [us, eu]."` * `"Requested platforms [tiktok] are not in your subscription. Allowed: [youtube, instagram]."` Fix by adjusting the request or updating defaults at [developers.zebratruth.ai/dashboard/settings](https://developers.zebratruth.ai/dashboard/settings). * **429 Too Many Requests**: Rate limited. Wait for `Retry-After` seconds. ## Example: Full Flow ```bash theme={null} # 1. Check credits curl https://api.zebratruth.ai/v1/usage \ -H "Authorization: Bearer $ZEBRATRUTH_API_KEY" # 2. Run check curl -X POST https://api.zebratruth.ai/v1/compliance/check \ -H "Authorization: Bearer $ZEBRATRUTH_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "jurisdictions": ["us"], "platforms": ["youtube"], "content": { "text": "Try our amazing guaranteed weight loss pills!" }, "mode": "fast", "responseMode": "sync" }' ``` # Debugging Observability Source: https://docs.zebratruth.ai/guides/debugging Step-by-step guide # Debugging & Observability Tools for debugging compliance checks, viewing request logs, inspecting agent traces, and replaying checks. ## Request Logs Query your API request history: ``` GET https://api.zebratruth.ai/v1/logs?externalId=my-project-123&since=2026-04-01&limit=50 Authorization: Bearer {api_key} ``` **Response:** ```json theme={null} [ { "requestId": "req_abc", "method": "POST", "path": "/v1/compliance/check", "statusCode": 200, "latencyMs": 8500, "creditsUsed": "…", "cached": false, "externalId": "my-project-123", "jobId": "job_xyz", "createdAt": "2026-04-15T10:30:00Z" } ] ``` Filter by `externalId` to find all requests related to your project. ## Agent Execution Trace Get detailed per-agent metrics for a job: ``` GET https://api.zebratruth.ai/v1/compliance/jobs/{jobId}/trace Authorization: Bearer {api_key} ``` **Response:** ```json theme={null} [ { "agentId": "ai-laws", "agentVersion": "1.3.0", "phase": 1, "startedAt": "2026-04-15T10:30:00Z", "completedAt": "2026-04-15T10:30:03Z", "latencyMs": 3200, "inputTokens": 500, "outputTokens": 800, "maxTokensBudget": 1200, "credits": "…", "cached": false, "annotationCount": 0, "partial": false }, { "agentId": "advertising-law", "agentVersion": "1.1.0", "phase": 2, "latencyMs": 5100, "cached": true, "credits": "…" } ] ``` Use this to understand: * Which agent was slow (`latencyMs`) * Which agents hit cache (`cached: true`) * Whether any agent was truncated (`partial: true`) * Token budget vs actual usage (`maxTokensBudget` vs `outputTokens`) ## Replay a Check Re-run the exact same check to compare results (e.g., after a rules update): ``` POST https://api.zebratruth.ai/v1/reports/{reportId}/replay Authorization: Bearer {api_key} Idempotency-Key: {new-uuid} ``` This creates a new job with the same input. A new budget reservation is created. Compare the two reports to see what changed. ## Common Issues ### Check returns different results than before Compare `versionInfo` between the two reports. If `rulesVersion` changed, a rule update caused the difference. Check the agent trace to see which agent produced different checks. ### Agent shows `partial: true` in trace The agent hit its token budget cap. The result is truncated. This usually means the content was very long or complex. Try splitting the content into smaller chunks. ### High latency on specific agents Check the trace for `cached: false` — uncached research stages (Stage A) take longer on the first call. Subsequent calls with the same jurisdictions/platforms will be faster. ### 429 errors in batch processing You're hitting the rate limit for your tier. Either: * Space out requests (check `Retry-After` header) * Use async mode with queue (automatically rate-managed) * Upgrade to a higher tier for more capacity # Embedding Annotations in Your UI Source: https://docs.zebratruth.ai/guides/embedding-annotations Step-by-step guide # Embedding Annotations in Your UI Annotations map compliance issues to specific locations in content. This guide shows how to use them to highlight issues natively in your application. ## Annotation Types ### Text Span For scripts, ad copy, captions, descriptions. ```json theme={null} { "type": "text-span", "agentId": "advertising-law", "severity": "high", "message": "'Guaranteed' claim may violate FTC rules", "suggestion": "Consider 'may help' instead", "textLocation": { "start": 120, "end": 137, "matchedText": "guaranteed results" } } ``` **How to use:** * `start` and `end` are character offsets (0-indexed) into the original `content.text` * Highlight `matchedText` in the editor with the appropriate severity color * Show `message` on hover/click * Show `suggestion` as an inline fix option ### Timeline For video content with timestamps. ```json theme={null} { "type": "timeline", "agentId": "metadata-labeling", "severity": "medium", "message": "AI-generated segment missing disclosure label", "suggestion": "Add visible AI disclosure overlay", "timelineLocation": { "startMs": 15000, "endMs": 28000, "track": "video" } } ``` **How to use:** * Mark the flagged time range on the video timeline * `track` indicates which track (video, audio, subtitle) * Show a warning marker at `startMs` on the timeline scrubber ### Image Region For visual content with detected elements. ```json theme={null} { "type": "image-region", "agentId": "rights-clearance-image", "severity": "high", "message": "Possible celebrity face detected", "suggestion": "Obtain likeness rights or replace", "imageLocation": { "imageUrl": "https://example.com/ad.jpg", "boundingBox": { "x": 120, "y": 45, "width": 200, "height": 250 } } } ``` **How to use:** * Draw a highlight rectangle at the bounding box coordinates * `boundingBox` uses pixel coordinates relative to the original image dimensions ### Global Applies to the entire content, not a specific location. ```json theme={null} { "type": "global", "agentId": "platform-policy", "severity": "medium", "message": "YouTube requires AI disclosure toggle for AI-generated content", "suggestion": "Enable the AI-generated content disclosure in YouTube Studio" } ``` **How to use:** * Show as a banner or top-level warning * Not tied to a specific content location ## Severity Colors (Suggested) | Severity | Color | Usage | | -------- | ---------------- | ------------------------------------- | | critical | Red (#FF3B30) | Bright red highlight, blocking banner | | high | Orange (#FF9500) | Orange highlight, prominent warning | | medium | Yellow (#FFCC00) | Yellow highlight, informational | | low | Blue (#007AFF) | Subtle blue indicator | | info | Gray (#8E8E93) | Gray text, no highlight | ## Grouping Annotations For a clean UI, group annotations by: 1. **By agent** — "Advertising Law: 2 issues, Platform Policy: 1 issue" 2. **By severity** — Show critical/high first 3. **By location** — Sort text-span by `start` offset, timeline by `startMs` # Image Rights Clearance Source: https://docs.zebratruth.ai/guides/image-rights-clearance Step-by-step guide # Image Rights Clearance Check images for restricted-category content, brand logos, stock-agency watermarks, content safety signals, AI-generation provenance (C2PA / IPTC metadata), and known-person likeness. ## Endpoint Two request formats are supported. **JSON** — reference publicly-hosted images by URL: ``` POST https://api.zebratruth.ai/v1/compliance/check-image Authorization: Bearer {api_key} Content-Type: application/json Idempotency-Key: {uuid} { "imageUrls": ["https://example.com/ad-image.jpg"], "jurisdictions": ["us"], "platforms": ["instagram"] } ``` **Multipart** — upload image binaries directly (use when you don't have a public URL): ```bash theme={null} curl -X POST https://api.zebratruth.ai/v1/compliance/check-image \ -H "Authorization: Bearer $ZEBRATRUTH_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -F "image=@./ad-1.png" -F "image=@./ad-2.png" \ -F "jurisdictions=us" -F "jurisdictions=eu" \ -F "platforms=instagram" ``` ## What It Checks 1. **C2PA / IPTC metadata** — Content Credentials, provenance, AI generation markers 2. **Restricted-category content** — Six advertising-restricted categories detected from image content: alcohol & spirits, weapons & firearms, tobacco & smoking, gambling, pharmaceuticals, and financial products (incl. crypto / forex / investments). Each carries jurisdiction-aware citations and platform-policy references. Full list and citation matrix in the [Restricted Categories guide](/guides/restricted-categories). 3. **Content safety (ZebraSafety)** — adult / violence / racy / medical likelihood signals. Tiered: high-confidence detections produce `block` checks; medium-confidence produce `flag` checks for review. Medical content is surfaced as info-only (pharmaceutical-advertising rules may apply but it never auto-blocks). 4. **Brand logo detection** — Recognized trademarks (Nike, Coca-Cola, etc.) — `flag` for review (could be fair use, comparative advertising, or authorized). 5. **Stock-agency watermark detection** — Shutterstock, Getty, Adobe Stock, etc. — `block` (unlicensed stock imagery). 6. **Known-person / celebrity likeness detection** — Right-of-publicity flag for recognizable public figures. *Surfaces when the celebrity-detection provider is enabled in your deployment; not active in the default ZebraTruth-managed configuration today.* 7. **Deterministic risk aggregation** — Combines all detections into compliance checks with status (`pass | flag | block`), severity (`info | low | medium | high | critical`), and recommendation. ## Check Statuses Not every detection is a hard block. The aggregator maps detections to statuses as follows: | Detection | Status | Severity | Rationale | | -------------------------------------------------------------------------- | ------- | ---------- | -------------------------------------------------------------------------------------------------------------------- | | Stock-agency watermark (Shutterstock, Getty, etc.) | `block` | `critical` | Unlicensed stock imagery — high copyright liability | | Restricted category — high severity (alcohol, weapons, tobacco, gambling) | `block` | `high` | Hard-restricted advertising category in most jurisdictions | | Restricted category — medium severity (pharmaceutical, financial-products) | `flag` | `medium` | Regulated advertising — substantiation and disclosure requirements | | Content safety — adult/violence/racy at high confidence | `block` | `high` | Platform-policy violation in most ad networks | | Content safety — adult/violence/racy at medium confidence | `flag` | `medium` | Review: may be intentional and permitted in context | | Content safety — medical content | `pass` | `info` | Informational only — surfaces pharmaceutical-rule applicability | | Known-person face *(when enabled)* | `flag` | `high` | Review: the person may be licensed, a paid spokesperson, or consented | | Brand logo | `flag` | `high` | Review: could be nominative fair use, comparative advertising, or authorized | | C2PA credentials missing | `flag` | `medium` | Required by some jurisdictions/platforms (EU AI Act, California SB 942, China Deep Synthesis Provisions, MeitY 2024) | | AI-generation markers present (non-C2PA) | `pass` | `info` | AI origin documented in metadata | | C2PA credentials present | `pass` | `info` | Verified, with generator and assertion details | | No AI-generation markers found | `flag` | `high` | Image's origin cannot be verified from metadata | Treat `flag` as "human review needed," not "reject." Use the `recommendation` field to drive the reviewer's next step. ## Per-Jurisdiction Citations Citations on returned checks are composed from the laws applicable to the jurisdictions you requested. Ask for `["us"]` and you get US citations; ask for `["us", "eu", "uk"]` and you get a semicolon-joined list covering all three. Platform-specific policy citations (Meta, TikTok, YouTube, X) are appended based on requested platforms. **Example — same restricted-category detection across different request scopes:** | Request scope | Citation on `Restricted Category: Alcohol & spirits` check | | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `jurisdictions: ["us"]`, `platforms: []` | `27 U.S.C. § 213 (Federal Alcohol Administration Act); FTC alcohol self-regulation guides; state alcohol-board rules (e.g. California ABC, NY SLA)` | | `jurisdictions: ["us", "uk"]`, `platforms: ["facebook", "tiktok"]` | All US + UK regulatory citations, plus `Meta Advertising Standards — Alcohol; TikTok Advertising Policies — Alcohol` | | `jurisdictions: ["china"]`, `platforms: ["youtube"]` | `China Advertising Law Art. 23 (alcohol); SAMR enforcement; Google Ads Policies — Alcohol` | The same pattern applies to all check types — brand logos cite trademark law (Lanham Act, EU Trademark Regulation, etc.), stock watermarks cite copyright law, content-safety checks cite jurisdiction-specific decency / broadcasting / advertising codes plus platform policies. ## Response ```json theme={null} { "checks": [ { "id": "chk_87bdd543", "agentId": "rights-clearance-image", "checkName": "Restricted Category: Alcohol & spirits", "status": "block", "severity": "high", "message": "Image depicts Alcohol & spirits (detected label: liquor, 94% confidence). Restricted advertising category — alcohol marketing requires age-gating, jurisdictional licensing, and specific disclaimer language.", "recommendation": "Block publication or obtain explicit clearance for alcohol & spirits advertising under the cited regulations and platform policies.", "citation": "27 U.S.C. § 213 (Federal Alcohol Administration Act); FTC alcohol self-regulation guides; state alcohol-board rules; UK CAP Code Section 18 (Alcohol); Meta Advertising Standards — Alcohol; TikTok Advertising Policies — Alcohol", "jurisdictions": ["us", "uk"], "platforms": ["facebook", "tiktok"], "primaryCategory": "other" }, { "id": "chk_ad1f2e90", "agentId": "rights-clearance-image", "checkName": "Adult Content Detected (high confidence)", "status": "block", "severity": "high", "message": "Image safety analysis indicates a high likelihood of adult content in this image. Restricted advertising and platform-policy rules apply in the requested scope.", "recommendation": "Block publication. Replace the image, or obtain explicit clearance for adult content under the cited regulations and platform policies.", "citation": "FTC truth-in-advertising; COPPA (where child-directed); state adult-content statutes; Meta Community Standards — Adult Nudity & Sexual Activity; TikTok Community Guidelines — Sexual Behavior & Nudity", "jurisdictions": ["us"], "platforms": ["facebook", "tiktok"] }, { "id": "chk_ae6642b7", "agentId": "rights-clearance-image", "checkName": "No C2PA Content Credentials", "status": "flag", "severity": "medium", "message": "Image does not contain C2PA content credentials. California SB 942 requires machine-readable AI provenance; TikTok uses C2PA for auto-detection of synthetic media; Meta auto-labels AI content via C2PA/IPTC metadata.", "recommendation": "Embed C2PA content credentials before publication. Use Content Authenticity Initiative tools or Adobe Content Credentials.", "citation": "California SB 942; TikTok Community Guidelines — Synthetic Media; Meta Transparency Center — Approach to AI-Generated Content" } ], "checksById": { "chk_87bdd543": { /* same as above */ } }, "indexes": { "byStatus": { "block": ["chk_87bdd543", "chk_ad1f2e90"], "flag": ["chk_ae6642b7"] }, "bySeverity": { "high": ["chk_87bdd543", "chk_ad1f2e90"], "medium": ["chk_ae6642b7"] }, "byJurisdiction": { "us": ["chk_87bdd543", "chk_ad1f2e90", "chk_ae6642b7"] }, "byPlatform": { "tiktok": ["chk_87bdd543", "chk_ad1f2e90", "chk_ae6642b7"] } }, "annotations": [], "decision": "BLOCK", "score": 22, "cached": false, "creditsCharged": "…", "costBreakdown": { "totalCredits": "…", "agents": [ { "agent": "rights-clearance-image", "creditsUsed": "…", "latencyMs": 714 } ] } } ``` ### Response field reference | Field | Type | Meaning | | ---------------- | ------- | --------------------------------------------------------------------------------------------------------------------------- | | `checks` | array | All compliance checks emitted for the image(s). Each carries `status`, `severity`, `message`, `recommendation`, `citation`. | | `checksById` | object | Same checks keyed by `id` for direct lookup. | | `indexes` | object | Inverted indexes: `byStatus`, `bySeverity`, `byJurisdiction`, `byPlatform`, `byCategory` — convenient for UI filtering. | | `annotations` | array | `image-region` annotations with bounding boxes for detected elements (when available). | | `decision` | enum | Canonical decision: `PUBLISH` (clean) / `HOLD` (review) / `BLOCK` (do not publish). Driven by aggregating check severities. | | `score` | number | Compliance score 0–100. Higher is better. Below 50 typically means `BLOCK`. | | `cached` | boolean | Whether the result came from the request cache (identical inputs within the cache window). | | `creditsCharged` | number | Credits debited from your tenant balance for this request. | | `costBreakdown` | object | Per-agent credit + latency breakdown. | ## Notes * JSON path: images must be publicly accessible URLs * Multipart path: upload binaries directly, up to 2 per request, 4.5 MB total body * Maximum 2 images per request on both paths (hard cap) * Requests are scoped to your subscribed jurisdictions and platforms — see [Tenant Onboarding and Scoping](/guides/tenant-onboarding-and-scoping) for the 403 model * Results include `image-region` annotations with bounding boxes for detected elements (when the underlying detection produces one) * The restricted-categories list is admin-tunable in real time without a redeploy — see the [Restricted Categories guide](/guides/restricted-categories) for the full catalog and how to request additions for your account # Individual Agent Invocation Source: https://docs.zebratruth.ai/guides/individual-agents Step-by-step guide # Individual Agent Invocation Call a single compliance agent directly instead of running the full pipeline. Useful for targeted checks or building custom workflows. ## Discover Available Agents ``` GET https://api.zebratruth.ai/v1/agents Authorization: Bearer {api_key} ``` **Response:** ```json theme={null} [ { "id": "ai-laws", "name": "AI Laws", "description": "Checks AI-content laws (disclosure, watermarking, transparency) for the requested jurisdictions", "phase": 1, "inputType": "text", "version": "1.3.0" }, { "id": "advertising-law", "name": "Advertising Law", "description": "Checks FTC, UCPD, and advertising regulations", "phase": 2, "inputType": "text", "version": "1.1.0" }, ... ] ``` Always use this endpoint to discover agents — new agents are added regularly. ## Invoke a Single Agent ``` POST https://api.zebratruth.ai/v1/agents/{agentId} Authorization: Bearer {api_key} Content-Type: application/json Idempotency-Key: {uuid} { "jurisdictions": ["us"], "platforms": ["youtube"], "content": { "text": "..." } } ``` **Response:** ```json theme={null} { "agentId": "advertising-law", "checks": [...], "annotations": [...], "costBreakdown": { "totalCredits": "…", "agents": [ { "agent": "advertising-law", "stage": "A", "credits": "…", "cached": true }, { "agent": "advertising-law", "stage": "B", "credits": "…" }, { "agent": "advertising-law", "stage": "C", "credits": "…" } ] } } ``` ## Use Cases * **Targeted re-check**: After fixing a flagged issue, re-run just that agent instead of the full pipeline * **Custom pipelines**: Build your own orchestration logic, calling agents in your preferred order * **Focused checks**: Only run the agents relevant to your content type # Interpreting Compliance Reports Source: https://docs.zebratruth.ai/guides/interpreting-reports Step-by-step guide # Interpreting Compliance Reports How to read and act on ZebraTruth compliance results. ## Score & Decision | Score Range | Decision | What It Means | Action | | ----------- | ----------------- | ---------------------------------- | -------------------------------------------------------- | | 80-100 | `PUBLISH` | Content is compliant | Safe to publish as-is | | 60-79 | `HOLD` | Issues found, none critical | Review and fix flagged items before publishing | | 0-59 | `HOLD` or `BLOCK` | Significant or critical violations | Must fix. If `BLOCK`, at least one critical issue exists | ### Decision Logic * `BLOCK` if any check has `status: "block"` OR any check has `severity: "critical"` * `HOLD` if score \< 60 OR any check has `severity: "high"` * `PUBLISH` otherwise ### Scoring Formula Starting from 100, each non-pass check deducts: * critical: -25 points * high: -15 points * medium: -8 points * low: -3 points * info: 0 points Score is clamped to \[0, 100]. ## Checks Array Each check represents a finding from one agent: ```json theme={null} { "agentId": "advertising-law", "checkName": "unsubstantiated-claim", "status": "flag", "severity": "high", "message": "Human-readable description of the issue", "recommendation": "Specific action to fix it", "citation": "Legal reference (e.g., '16 CFR Part 255')" } ``` ### Check Status Values * `pass` — No issue found * `flag` — Issue found, review recommended * `block` — Critical issue, must fix * `escalate` — Needs human review (ambiguous) ## Agent Summaries The `agentSummaries` field provides a high-level view per agent: ```json theme={null} { "advertising-law": { "status": "flag", "checkCount": 5, "passCount": 3, "flagCount": 2, "blockCount": 0, "summary": "2 advertising claim issues found" } } ``` ## Version Info ```json theme={null} { "engineVersion": "1.3.2", "rulesVersion": "2026-04-01", "agentVersions": { "advertising-law": "1.1.0", ... }, "stackVersion": "2026.04-a7f3" } ``` Use this to explain why results may change over time. `rulesVersion` bumps when the compiled law set changes; `agentVersions` bumps when an individual agent's logic changes; `stackVersion` is an opaque identifier for the underlying model/prompt pipeline — it bumps whenever that pipeline changes. Treat `stackVersion` as a black-box string and compare only for equality. ## Cost Breakdown ```json theme={null} { "totalCredits": "…", "agents": [ { "agent": "jurisdiction", "stage": "A", "credits": "…", "cached": true }, { "agent": "jurisdiction", "stage": "B", "credits": "…" }, ... ] } ``` `cached: true` means the research stage used a cached result (still logged for analytics). # Restricted Categories Source: https://docs.zebratruth.ai/guides/restricted-categories Image content classification for advertising-restricted categories # Restricted Categories The `/v1/compliance/check-image` endpoint detects image content belonging to six advertising-restricted categories and emits a dedicated compliance check for each match. Each check carries jurisdiction-aware regulatory citations and platform-policy references filtered to the request scope. ## When a check fires The image is analyzed for recognizable objects/scenes. When a detected label maps to a restricted category (case-insensitive exact-word match against the category's `matchLabels`), a `Restricted Category: {categoryName}` check is emitted. Aggregation rules: * Multiple detected labels matching the **same category** → **one** check, using the highest-confidence label in the interpolated message * Detected labels below 50% confidence are ignored (most signal noise lives below this threshold) * Match is exact-word and case-insensitive: `"alcohol"` matches `"Alcohol"` and `"alcohol "` but **does NOT** match `"non-alcoholic"` ## The six categories | Category | Severity | Status | Sample match labels | | --------------------------------------------------------- | -------- | ------- | -------------------------------------------------------------------------------------------------------- | | **Alcohol & spirits** | `high` | `block` | alcohol, wine, beer, whiskey, spirits, champagne, cocktail, vodka, rum, liquor | | **Weapons & firearms** | `high` | `block` | firearm, weapon, gun, rifle, pistol, ammunition, bullet, knife | | **Tobacco & smoking products** | `high` | `block` | tobacco, cigarette, cigar, vape, e-cigarette, smoking, smoker | | **Gambling** | `high` | `block` | casino, gambling, slot machine, poker chip, roulette, lottery, betting | | **Pharmaceutical & prescription drugs** | `medium` | `flag` | medicine, pill, pharmacy, drug, pharmaceutical, prescription, capsule | | **Financial products (incl. crypto, forex, investments)** | `medium` | `flag` | bitcoin, cryptocurrency, currency, investment, trading, stock market, forex, banking, loan, crypto, coin | `high`-severity categories produce a hard `block` decision. `medium`-severity categories produce a `flag` (review required) — typically for regulated content that's allowed with substantiation and disclosure. ## Citations per category Each category carries per-jurisdiction citations (US / EU / UK / China / India) and per-platform citations (Facebook, Instagram, TikTok, YouTube). Only those matching the request scope are included on the emitted check. ### Alcohol & spirits | Scope | Citations | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | **us** | 27 U.S.C. § 213 (Federal Alcohol Administration Act); FTC alcohol self-regulation guides; state alcohol-board rules (e.g. California ABC, NY SLA) | | **eu** | EU AVMSD Art. 9 (commercial communications for alcoholic beverages); EU Council Recommendation 2001/458/EC | | **uk** | UK CAP Code Section 18 (Alcohol); ASA enforcement | | **china** | China Advertising Law Art. 23 (alcohol); SAMR enforcement | | **india** | India Cable Television Networks Rules Rule 7(2)(viii); Drugs and Cosmetics Act 1940 (surrogate-advertising provisions) | | **facebook** / **instagram** | Meta Advertising Standards — Alcohol | | **tiktok** | TikTok Advertising Policies — Alcohol | | **youtube** | Google Ads Policies — Alcohol | ### Weapons & firearms | Scope | Citations | | ---------------------------- | ----------------------------------------------------------------------- | | **us** | ATF firearms-advertising regulations; state firearms-marketing statutes | | **eu** | EU member-state firearms-advertising restrictions | | **uk** | UK Firearms Act 1968; CAP Code Rule 11 (Distance Selling) | | **china** | China Firearms Control Law Art. 5 | | **india** | India Arms Act 1959 | | **facebook** / **instagram** | Meta Advertising Standards — Weapons, ammunition & explosives | | **tiktok** | TikTok Advertising Policies — Weapons & explosives | | **youtube** | Google Ads Policies — Dangerous products or services | ### Tobacco & smoking products | Scope | Citations | | ---------------------------- | --------------------------------------------------------------------------------------------------------------------- | | **us** | Family Smoking Prevention and Tobacco Control Act (FSPTCA); FDA tobacco advertising rules; state tobacco-control laws | | **eu** | EU Tobacco Advertising Directive 2003/33/EC; TPD 2014/40/EU | | **uk** | UK Tobacco Advertising and Promotion Act 2002 | | **china** | China Advertising Law Art. 22 (tobacco advertising ban) | | **india** | India COTPA 2003 (Cigarettes and Other Tobacco Products Act) | | **facebook** / **instagram** | Meta Advertising Standards — Tobacco & related products | | **tiktok** | TikTok Advertising Policies — Tobacco products | | **youtube** | Google Ads Policies — Tobacco | ### Gambling | Scope | Citations | | ---------------------------- | ------------------------------------------------------------------------------------------------------------ | | **us** | State gaming-commission rules (NJ DGE, NV GCB, etc.); Wire Act; UIGEA | | **eu** | EU member-state gambling regulators (French ANJ, Spanish DGOJ, etc.); EU Commission gambling recommendations | | **uk** | UK Gambling Act 2005; CAP Code Section 16; Gambling Commission rules | | **china** | China Criminal Law Art. 303 (gambling prohibition) | | **india** | India Public Gambling Act 1867; state lottery laws | | **facebook** / **instagram** | Meta Advertising Standards — Online gambling & gaming | | **tiktok** | TikTok Advertising Policies — Gambling | | **youtube** | Google Ads Policies — Gambling and games | ### Pharmaceutical & prescription drugs | Scope | Citations | | ---------------------------- | ----------------------------------------------------------------------------------------- | | **us** | FDA prescription-drug advertising rules (21 CFR § 202); FTC self-regulation for OTC drugs | | **eu** | EU Directive 2001/83/EC Art. 86–100 (medicinal-product advertising); EMA guidelines | | **uk** | UK Human Medicines Regulations 2012 Part 14; PMCPA Code of Practice | | **china** | China Advertising Law Art. 15–18 (medical advertising); SAMR enforcement | | **india** | India Drugs and Magic Remedies (Objectionable Advertisements) Act 1954 | | **facebook** / **instagram** | Meta Advertising Standards — Online pharmacies | | **tiktok** | TikTok Advertising Policies — Drugs, drug-related products | | **youtube** | Google Ads Policies — Healthcare and medicines | ### Financial products (incl. crypto, forex, investments) | Scope | Citations | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | **us** | SEC marketing rule (Rule 206(4)-1); FINRA Rule 2210 (communications with the public); CFTC commodity-advertising rules; state Blue Sky laws | | **eu** | EU MiFID II; ESMA marketing-communications guidelines; MiCA crypto-asset-markets marketing rules | | **uk** | UK FCA Financial Promotion regime (FSMA s.21); FCA crypto-promotion rules (PS23/6) | | **china** | China Advertising Law Art. 25 (financial-product advertising); PBOC and CBIRC rules | | **india** | India SEBI Investment Adviser Regulations; RBI advertising guidelines | | **facebook** / **instagram** | Meta Advertising Standards — Financial products & services | | **tiktok** | TikTok Advertising Policies — Financial services | | **youtube** | Google Ads Policies — Financial products & services | ## Example response check When an image of a wine bottle is checked with `jurisdictions: ["us", "uk"]` and `platforms: ["facebook", "tiktok"]`: ```json theme={null} { "id": "chk_87bdd543", "agentId": "rights-clearance-image", "checkName": "Restricted Category: Alcohol & spirits", "status": "block", "severity": "high", "message": "Image depicts Alcohol & spirits (detected label: liquor, 94% confidence). Restricted advertising category — alcohol marketing requires age-gating, jurisdictional licensing, and specific disclaimer language.", "recommendation": "Block publication or obtain explicit clearance for alcohol & spirits advertising under the cited regulations and platform policies.", "citation": "27 U.S.C. § 213 (Federal Alcohol Administration Act); FTC alcohol self-regulation guides; state alcohol-board rules (e.g. California ABC, NY SLA); UK CAP Code Section 18 (Alcohol); ASA enforcement; Meta Advertising Standards — Alcohol; TikTok Advertising Policies — Alcohol", "jurisdictions": ["us", "uk"], "platforms": ["facebook", "tiktok"] } ``` The `message` interpolates `{{category}}` (categoryName), `{{label}}` (the highest-confidence matched label, lower-cased), and `{{confidence}}` (integer 0–100). ## Notes on extensibility The category list, match labels, severity, message template, and citation maps are **admin-tunable in real time** — stored in a managed configuration table and edited without a code deploy. Changes propagate within 60 seconds via a cached loader. Contact your account team to request: * New categories specific to your industry (e.g. supplements, CBD, political content) * Adjustments to the match-label list for an existing category * Per-tenant severity overrides If the managed table is unreachable for any reason, the runtime falls back to the in-repo manifest (currently the 6 categories above), so image checks never break on a transient outage. ## Related * [Image Rights Clearance](/guides/image-rights-clearance) — the parent endpoint and full check catalog * [Interpreting Reports](/guides/interpreting-reports) — how `decision`, `score`, and `severity` interact * [Tenant Onboarding and Scoping](/guides/tenant-onboarding-and-scoping) — how `jurisdictions` and `platforms` filter the citation set # Streaming Integration (SSE) Source: https://docs.zebratruth.ai/guides/streaming-integration Receive progressive compliance results over Server-Sent Events # Streaming Integration (SSE) Set `responseMode: "stream"` to receive progressive compliance results as each agent finishes. The connection stays open until the run completes; events arrive as soon as each phase produces output. Useful when you want to show live progress in a UI rather than waiting 60–240 seconds for a single sync response. ## Endpoint ```http theme={null} POST https://api.zebratruth.ai/v1/compliance/check Authorization: Bearer {api_key} Content-Type: application/json Accept: text/event-stream { "jurisdictions": ["us"], "platforms": ["youtube"], "content": { "text": "..." }, "mode": "fast", "responseMode": "stream" } ``` The response is `Content-Type: text/event-stream`. Time-to-first-event is typically under 1 second. ## Event sequence A successful run produces this sequence (events are SSE format: `event: \ndata: \n\n`): ``` event: job.started data: {"timestamp":"2026-05-01T03:04:06.683Z"} event: agent.started data: {"agentId":"ai-laws","timestamp":"..."} event: agent.started data: {"agentId":"advertising-law","timestamp":"..."} event: agent.started data: {"agentId":"platform-policy","timestamp":"..."} event: agent.started data: {"agentId":"metadata-labeling","timestamp":"..."} event: agent.started data: {"agentId":"rights-clearance","timestamp":"..."} event: agent.completed data: {"agentId":"platform-policy","checks":[...],"timestamp":"..."} event: agent.completed data: {"agentId":"advertising-law","checks":[...],"timestamp":"..."} ... (more agent.completed events as each agent finishes) ... event: score.computed data: {"score":10,"decision":"BLOCK","timestamp":"..."} event: complete data: {"data": { /* full classified response */ }, "timestamp":"..."} ``` The terminal event is always either `complete` (success) or `error` (failure). After a terminal event the server closes the connection. ### Event types | Event | When | Payload | | ----------------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `job.started` | Once, immediately after headers flush | `{ timestamp }` | | `agent.started` | Once per agent, when the agent begins work | `{ agentId, timestamp }` | | `agent.completed` | Once per agent, when the agent's promise resolves | `{ agentId, checks: [], timestamp }` | | `score.computed` | After Stage D finishes scoring | `{ score, decision, timestamp }` | | `complete` | Terminal success | `{ data: { score, decision, checks, checksById, indexes, annotations, profile, agentSummaries, versionInfo, creditsCharged, cached, costBreakdown }, timestamp }` | | `error` | Terminal failure | `{ message, code, timestamp }` | `agentId` is one of: `ai-laws`, `advertising-law`, `rights-clearance`, `rights-clearance-image`, `metadata-labeling`, `platform-policy`. ## Client implementation ### EventSource (browser) ```javascript theme={null} // EventSource doesn't support custom headers in the browser, so use fetch // with a ReadableStream instead. Native EventSource is fine for tests // without auth, or via a same-origin proxy that injects the API key. const response = await fetch('https://api.zebratruth.ai/v1/compliance/check', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', 'Accept': 'text/event-stream', }, body: JSON.stringify({ jurisdictions: ['us'], platforms: ['youtube'], content: { text: '...' }, mode: 'fast', responseMode: 'stream', }), }) const reader = response.body.getReader() const decoder = new TextDecoder() let buffer = '' while (true) { const { value, done } = await reader.read() if (done) break buffer += decoder.decode(value, { stream: true }) // Split on the SSE event terminator (blank line) const events = buffer.split('\n\n') buffer = events.pop() ?? '' for (const raw of events) { const lines = raw.split('\n') const eventLine = lines.find((l) => l.startsWith('event: ')) const dataLine = lines.find((l) => l.startsWith('data: ')) if (!eventLine || !dataLine) continue const type = eventLine.slice(7) const data = JSON.parse(dataLine.slice(6)) if (type === 'agent.completed') { console.log(`${data.agentId} → ${data.checks.length} checks`) } else if (type === 'complete') { console.log('done', data) reader.cancel() break } else if (type === 'error') { console.error('pipeline error:', data.message, data.code) reader.cancel() break } } } ``` ### curl For quick testing: ```bash theme={null} curl -N -X POST https://api.zebratruth.ai/v1/compliance/check \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"jurisdictions":["us"],"platforms":["youtube"],"content":{"text":"..."},"mode":"fast","responseMode":"stream"}' ``` The `-N` flag disables curl's output buffering so you see events arrive progressively. ## Concurrent stream limit A maximum of **5 concurrent streams per tenant** is enforced. Requests over the cap return `429` with a message advising retry. The cap exists to prevent runaway clients from holding open many connections at once. If you legitimately need higher concurrency, contact support. ## When to use streaming vs. async Streaming holds a connection open for the entire run (typically 60–240 seconds depending on `mode` and scope). Choose: * **Streaming** when you want to render live progress in a UI and the client can keep a connection open. * **Async + webhook** ([guide](/guides/async-webhook-execution)) when the client can't or shouldn't hold a connection open — server-to-server pipelines, mobile apps, or batch workflows. * **Sync** (the default) when the run is short enough that progress events aren't worth the complexity. ## Error handling A pipeline failure delivers a single `error` event then closes: ``` event: error data: {"message":"Compliance pipeline error: ...","code":"pipeline_error","timestamp":"..."} ``` Network disconnects mid-run: the orchestration continues server-side until completion. To recover the result, store the request with a sync re-issue or — preferred — use `responseMode: "async"` from the start so the result lands in your webhook and persists in the polling endpoint. ## Limitations * **No resume from a specific event**. There is no `resumeFrom=advertising-law` query parameter. If the connection drops, re-issue the request (idempotency keys deduplicate). * **No partial fallback**. If the orchestrator fails mid-run, the server emits one `error` event with the failure reason — partial check results are not returned. * **Multipart requests stay sync**. The `multipart/form-data` upload path does not support `responseMode: "stream"`. Pass image URLs in the JSON body if you need streaming with image inputs. # Video Compliance Check — Full Guide Source: https://docs.zebratruth.ai/guides/video-compliance-check 3-step async flow with curl, Node, Python. Error handling, webhook HMAC verification, billing semantics. # Video Compliance Check — Full Guide ZebraTruth runs compliance analysis on video ads through a **server-side async pipeline**. Your client uploads the video to managed blob storage, submits a job, then either polls or receives a webhook when the report is ready. **POST `https://api.zebratruth.ai/v1/compliance/check-video`** **No client-side ffmpeg.** ZebraTruth handles video probing, audio transcription, frame extraction, OCR, visual signals, and Stage A→C compliance analysis server-side. ## Supported inputs | Limit | Value | | -------------------- | -------------------------------------------- | | Content types | `video/mp4`, `video/webm`, `video/quicktime` | | Duration | 3 to 180 seconds | | File size | Up to 200 MB | | Execution | Async only | | Client preprocessing | None required | ## The 3-step flow 1. **`POST /v1/compliance/media/upload-url`** — get a 15-min signed upload URL for managed blob storage 2. **`PUT `** — upload your video bytes directly to managed storage 3. **`POST /v1/compliance/check-video`** — submit the uploaded video for analysis (returns 202) 4. **Poll** `GET /v1/compliance/check-video/{requestId}` OR receive an HMAC-signed webhook *** ## Step 1 — Request an upload URL ```bash theme={null} curl -X POST https://api.zebratruth.ai/v1/compliance/media/upload-url \ -H "Authorization: Bearer $ZEBRATRUTH_API_KEY" \ -H "Content-Type: application/json" \ -d '{"contentType": "video/mp4"}' ``` ```json Response theme={null} { "uploadUrl": "https://studio2.blob.core.windows.net/videos/...?sv=...&sig=...", "blobPath": "videos///source.mp4", "requestId": "319b5d9e-a67f-45ee-9789-d7384fd73a3e", "expiresAt": "2026-05-29T18:30:00Z", "maxSizeBytes": 209715200, "requiredTags": { "tenantId": "", "requestId": "319b5d9e-...", "kind": "video" }, "uploadInstructions": { "method": "PUT", "headers": { "x-ms-blob-type": "BlockBlob", "x-ms-tags": "tenantId=&requestId=319b5d9e-...&kind=video", "Content-Type": "video/mp4" } } } ``` The `uploadUrl` is valid for 15 minutes. Save `requestId` + `blobPath` — you'll pass `blobPath` back in step 3 + use `requestId` to poll. ## Step 2 — Upload the video PUT your video bytes directly to `uploadUrl`. **Use exactly the headers from `uploadInstructions.headers`** — `x-ms-tags` is required by the storage service for the integrity check. ```bash theme={null} curl -X PUT "$UPLOAD_URL" \ -H "x-ms-blob-type: BlockBlob" \ -H "x-ms-tags: tenantId=...&requestId=...&kind=video" \ -H "Content-Type: video/mp4" \ --data-binary @your-ad.mp4 ``` The storage service returns 201 Created on success. Bytes go directly to ZebraTruth-managed blob storage — they don't pass through ZebraTruth servers. ## Step 3 — Submit for analysis ```bash theme={null} curl -X POST https://api.zebratruth.ai/v1/compliance/check-video \ -H "Authorization: Bearer $ZEBRATRUTH_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "requestId": "319b5d9e-a67f-45ee-9789-d7384fd73a3e", "blobPath": "videos//319b5d9e-.../source.mp4", "jurisdictions": ["us", "eu"], "platforms": ["tiktok", "youtube"], "mode": "fast", "callbackId": "your-internal-id-optional", "webhookUrl": "https://yourapp.com/zebratruth-callback" }' ``` ```json Response (HTTP 202) theme={null} { "jobId": "319b5d9e-a67f-45ee-9789-d7384fd73a3e", "status": "queued", "estimatedCredits": "…" } ``` The endpoint validates the upload (existence + content-length under 200 MB + duration in \[3, 180] sec), reserves the job's credits, and enqueues it. ### Request body fields | Field | Required | Notes | | --------------- | -------- | -------------------------------------------------------------------------------- | | `requestId` | yes | UUID. Same as the one returned from upload-url. Also serves as idempotency key. | | `blobPath` | yes | From upload-url response. | | `jurisdictions` | yes | At least 1. Values: `us`, `eu`, `uk`, `in`, `cn`, etc. | | `platforms` | yes | At least 1. Values: `tiktok`, `youtube`, `instagram`, `facebook`, `linkedin`. | | `mode` | yes | `"fast"` only (V1). | | `callbackId` | no | Opaque string echoed back in webhook payloads. | | `webhookUrl` | no | If set + tenant has a signing secret, ZebraTruth POSTs the report when terminal. | ### Possible error responses | Status | Body code | Meaning | | | | ------ | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------ | --------- | -------------- | | 400 | `blob_path_invalid` | Path doesn't match `videos///...` | | | | 400 | `unsupported_mode` | Mode other than `fast` | | | | 400 | `video_too_short` | Probed duration \< 3 sec | | | | 400 | `video_too_long` | Probed duration > 180 sec | | | | 400 | `video_too_large` | Size > 200 MB | | | | 402 | `insufficient_credits` | Tenant budget exhausted | | | | 403 | — | A requested jurisdiction or platform is not one you have selected. `statusMessage` lists which, and what is allowed. | | | | 403 | `scope_limit_exceeded_config` | Your saved jurisdictions or platforms exceed what your plan allows. See [Plans & Billing](/concepts/credits-and-billing) | | | | 409 | `idempotency_conflict` | Same `requestId` already used with different inputs (the response body includes \`reason: "blobPath" | "feature" | "inputHash"\`) | | 503 | `video_analysis_disabled` | Feature flag off (should not happen in prod) | | | Jurisdictions and platforms are checked before the job is queued, so a scope refusal costs no credits and creates no job. ## Step 4 — Poll for results ```bash theme={null} curl https://api.zebratruth.ai/v1/compliance/check-video/319b5d9e-a67f-45ee-9789-d7384fd73a3e \ -H "Authorization: Bearer $ZEBRATRUTH_API_KEY" ``` ```json Response (in-flight) theme={null} { "requestId": "319b5d9e-...", "status": "processing", "phase": "transcribeAudio", "billingStatus": "reserved" } ``` ```json Response (terminal) theme={null} { "requestId": "319b5d9e-...", "status": "completed", "reportStatus": "complete", "billingStatus": "committed", "workerCompletedAt": "2026-05-29T18:20:00Z", "completedAt": "2026-05-29T18:20:01Z", "durationMs": 28000, "reportBlobPath": "videos//319b5d9e-.../report.json", "report": { "schemaVersion": "video-report.v1", "preprocessorVersion": "video-preprocessor.v1", "evidenceVersion": "evidence-timeline.v1", "tenantId": "", "requestId": "319b5d9e-...", "generatedAt": "2026-05-29T18:20:00Z", "status": "complete", "checks": [ { "agentId": "advertising-law", "checkName": "Unsubstantiated efficacy claim", "status": "flag", "severity": "medium", "evidenceIds": ["audio_003"], "timelineLocation": { "startMs": 4500, "endMs": 7200, "track": "audio" }, "message": "Claim 'guaranteed results in 30 days' lacks substantiation — FTC §5 risk.", "recommendation": "Add 'individual results may vary' overlay or remove claim.", "citation": "FTC Act §5", "primaryCategory": "unsubstantiated-health-claim" }, { "agentId": "rights-clearance-image", "checkName": "Brand detected: Nike", "status": "flag", "severity": "medium", "evidenceIds": ["frame_007", "frame_009"], "timelineLocation": { "startMs": 12000, "endMs": 18000, "track": "video" } } ], "analysisDiagnostics": [], "preprocessing": { "transcriptionStatus": "complete", "frameAnalysisStatus": "complete", "degraded": false, "evidenceTruncated": false } } } ``` ### Status state machine ``` queued → processing → completed (reportStatus: complete | insufficient_evidence) ↓ failed (reportStatus: analysis_failed) ``` Poll every **2 seconds**. Typical completion: 20-60 seconds for a 30-second video. Hard ceiling: 3 minutes. ### Terminal report statuses | `reportStatus` | Meaning | Billing | | ----------------------- | ------------------------------------------------------------------------- | -------------------- | | `complete` | Real findings produced | Charged | | `insufficient_evidence` | Silent video / no OCR / no signals — pipeline ran but had nothing to flag | Released (no charge) | | `analysis_failed` | Engine error — failure recorded for audit | Released (no charge) | ## Webhooks (recommended for production) Instead of polling, configure a webhook URL once at the account level and submit videos with `webhookUrl` set. ### Get / create your tenant webhook secret ```bash theme={null} curl https://api.zebratruth.ai/v1/compliance/webhooks/video/secret \ -H "Authorization: Bearer $ZEBRATRUTH_API_KEY" ``` ```json Response theme={null} { "secret": "whsec_a1b2c3d4...", "createdAt": "2026-05-29T18:00:00Z" } ``` Save the secret securely — ZebraTruth only returns it once on creation. ### Verify webhook signatures Each delivery is HMAC-SHA256-signed. Signature is **stable across retries** (computed once at enqueue + cached on the row). Headers: * `x-zt-timestamp: ` * `x-zt-signature: ` Canonical signing format: ``` HMAC-SHA256(secret, "POST" + "\n" + path + "\n" + timestamp + "\n" + sha256(rawBody)) ``` Node.js example: ```js theme={null} import crypto from "node:crypto"; function verifyZebraTruthWebhook(rawBody, headers, secret) { const timestamp = headers["x-zt-timestamp"]; const signature = headers["x-zt-signature"]; // Reject if timestamp older than 5 min (replay protection) if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) { return false; } const bodyHash = crypto.createHash("sha256").update(rawBody).digest("hex"); const canonical = `POST\n/your-webhook-path\n${timestamp}\n${bodyHash}`; const expected = crypto.createHmac("sha256", secret).update(canonical).digest("hex"); return crypto.timingSafeEqual( Buffer.from(expected), Buffer.from(signature) ); } ``` ### Retry semantics | Attempt | Delay after previous | | ------- | -------------------- | | 1 | (immediate) | | 2 | +1 minute | | 3 | +5 minutes | | 4 | +15 minutes | | 5 | +1 hour | After 5 failures, the delivery is marked `failed` and dropped. **Webhook failures do NOT mutate the compliance job row** — the job stays `completed_committed`. The webhook delivery has its own retry state in `videoWebhookDeliveries`. ### Rotate the secret ```bash theme={null} curl -X POST https://api.zebratruth.ai/v1/compliance/webhooks/video/secret/rotate \ -H "Authorization: Bearer $ZEBRATRUTH_API_KEY" ``` In-flight deliveries keep their original signature (sig is computed at enqueue). New jobs use the rotated secret immediately. ## Idempotency Same `(requestId, blobPath, jurisdictions, platforms, mode)` → 202 with `idempotent: true`, no extra charge. Same `requestId` with different `blobPath` / `jurisdictions` / `platforms` / `mode` → 409 `idempotency_conflict`: ```json theme={null} { "error": true, "statusCode": 409, "data": { "code": "idempotency_conflict", "reason": "blobPath", // or "feature" or "inputHash" "existing": { "blobPath": "...", "inputHash": "..." }, "incoming": { "blobPath": "...", "inputHash": "..." } } } ``` Precedence: `blobPath > feature > inputHash` (deterministic order for stable error messages). ## Credit lifecycle Credits are reserved at submit and committed on `reportStatus: complete`. `insufficient_evidence` and `analysis_failed` release the reservation, so a failed analysis is never charged. ## Common errors | HTTP | code | Cause | Fix | | ---- | ------------------------- | -------------------------------------- | ------------------------------------------- | | 400 | `blob_path_invalid` | Path doesn't match the tenant's prefix | Use the `blobPath` from upload-url verbatim | | 400 | `video_too_short` | \< 3 seconds | Cut the source long enough | | 400 | `video_too_long` | > 180 seconds | Split into segments | | 400 | `video_too_large` | > 200 MB | Reduce bitrate or resolution before upload | | 402 | `insufficient_credits` | Budget exhausted | Top up or wait for next billing period | | 409 | `idempotency_conflict` | Same `requestId`, different inputs | Use a fresh UUID per submission | | 503 | `video_analysis_disabled` | (Should not happen in prod) | Contact support | ## Next HMAC verification deep dive, retry semantics. How to read scores + decisions + agent breakdowns. Install the skill so your LLM agent runs video checks autonomously. # Runtime compliance for AI agents Source: https://docs.zebratruth.ai/introduction Check what an AI agent is about to send, publish or file before it goes out. Laws, regulator guidance, enforcement practice and your own policies, with a decision, evidence and an audit record. Built for financial services, pharma, healthcare and media. # Runtime Compliance for AI Agents ZebraTruth is the compliance check that runs **inside** your agent workflow. Before an agent sends a notice to a customer, publishes a promotion, files a disclosure or answers a client, it asks ZebraTruth whether that action is compliant. The answer comes back in seconds: a **PUBLISH / HOLD / BLOCK** decision, every finding tied to the rule and the evidence that triggered it, and a record your compliance team can defend. **POST `https://api.zebratruth.ai/v1/compliance/check`** Send the text, document, image or video an agent has prepared. Get back a decision, a 0-100 score, evidence-linked findings, the rules that were applied, the agents that ran and why the decision is what it is. [**Run your first check →**](/quickstart) ZebraTruth identifies issues and recommends a next step. It is **not legal advice**. Your team decides how to resolve each case, and HOLD is there precisely so a person can. **Not an advertising-only tool.** Advertising and financial promotions are one use case. The same check covers client onboarding and KYC journeys, suitability reports and advice letters, adverse-action notices and collections messages, best-execution disclosures and research, scam warnings, AML and AI policies, fund factsheets and regulatory filings, across text, documents, images and video. ## What ZebraTruth checks against Every check brings together external requirements and your institution's own guidance. The applicable set depends on the action, the business line, the jurisdiction and the regulated actor. The binding requirements for the action and jurisdiction, maintained as a versioned legal corpus with effective dates. Supervisory expectations, published guidance and interpretations, cited separately from the statute they interpret. Past enforcement priorities and cases that show how rules are applied. Sector standards and self-regulatory codes for financial services, pharma and advertising. Your own policies, approval rules, brand rules and reference assets, published from the dashboard and applied to every check. Privacy, rights and IP, AI-disclosure laws and ad-platform policies, applied to every industry. ## Industry coverage You choose the industries your workspace operates in. ZebraTruth loads that industry's corpus and the cross-industry rules, and runs the specialist agents that apply. ### Financial services Six specialist agents, one per review scope. Select the scopes a check needs with `reviewScopes` and tell ZebraTruth what kind of artifact it is looking at with `contentKind`. | Review scope | `reviewScopes` value | What it covers | | -------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------ | | **Financial promotion** | `financial_promotion` | Financial promotions, product and fund disclosures, conduct rules for marketing to retail and professional clients | | **Advisory & suitability** | `advisory_suitability` | Investment advice, suitability and appropriateness, portfolio management, the advice / guidance boundary | | **Credit & servicing** | `credit_servicing` | Credit origination, creditworthiness, pre-contract information, adverse-action notices, servicing and collections | | **Markets & trading** | `markets_trading` | Order execution and best-execution disclosures, transaction reporting, market abuse, research and recommendations | | **Financial crime** | `financial_crime` | KYC and customer due diligence, AML/CFT, fraud and scam warnings, sanctions obligations | | **AI governance** | `ai_governance` | AI governance, model risk, automated decision-making and cyber-resilience obligations | Coverage is **US, EU and UK** across all six scopes, with **China and India** added for financial promotions. Content kinds include `marketing`, `suitability_report`, `advice_letter`, `precontract_disclosure`, `adverse_action_notice`, `collections_communication`, `fund_factsheet`, `research_note`, `best_execution_disclosure`, `kyc_onboarding`, `scam_warning` and `aml_policy`. ### Pharma & healthcare Advertising and promotion of medicines and devices, disclosure and fair-balance rules, industry codes of practice, digital-health obligations and health-specific privacy across US, EU, UK and India. Set `regulatedActor` (manufacturer, provider, pharmacy, HCP, telehealth provider and more) so only the rules that bind that actor can drive a decision. ### Media & advertising Advertising law, AI-content disclosure and labelling laws, platform policies for YouTube, Instagram, Facebook, TikTok, LinkedIn and Douyin, and rights clearance for text and images. ## Any artifact an agent produces The same check and the same report envelope, whatever the agent prepared. Customer communications, notices, disclosures, ad copy, scripts. JSON body, sync, streamed or async with a webhook. Suitability reports, fund factsheets, policies, filings. Send the file as a multipart `document` part, or extract text first with `POST /v1/compliance/documents/extract`. Creative, banners and on-image claims. Restricted categories, brand marks, content safety and provenance metadata, with findings located by bounding box. Ads and social video. Async upload, per-frame OCR, transcript analysis and timestamped findings. ## A check in 30 seconds A US advisory agent has drafted an investment recommendation for a retail client. Before the letter goes out, the workflow checks it. ```bash theme={null} curl -X POST https://api.zebratruth.ai/v1/compliance/check \ -H "Authorization: Bearer $ZEBRATRUTH_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jurisdictions": ["us"], "platforms": ["linkedin"], "reviewScopes": ["advisory_suitability"], "contentKind": "advice_letter", "content": { "text": "Based on our review, we recommend moving 60% of your portfolio into the Apex Leveraged Growth Fund. It has returned 24% a year and is the best option for you." } }' ``` ```json Response (trimmed) theme={null} { "decision": "HOLD", "score": 46, "decisionReasons": ["flagged_check"], "agentsRun": [ { "id": "fs-compliance-advisory-suitability", "outcome": "completed" } ], "checks": [ { "checkName": "Suitability basis not documented", "status": "flag", "severity": "high", "primaryCategory": "consumer-information-disclosure", "message": "The recommendation to concentrate 60% of the portfolio in a single leveraged fund does not record the client's risk tolerance, time horizon or liquidity needs, and presents past performance as the reason it is \"the best option\" ...", "citation": "...", "recommendation": "Document the client profile the recommendation relies on and why the product fits it, disclose the leverage and concentration risks, and remove the implication that past returns will continue." } ] } ``` `platforms` is required on every request and must name a platform enabled for your workspace, even when the artifact is not published to one. `decision` is what the workflow acts on. `decisionReasons` lists every rule that produced it. `agentsRun` records which specialists ran and how each one finished. Together with the findings they form the audit record for that action. ## What comes back * **Decision and score.** `PUBLISH`, `HOLD` or `BLOCK`, plus a 0-100 score derived from severity, applicability and jurisdiction weighting. See [Scoring](/concepts/scoring). * **Findings with evidence.** Each finding cites the rule, explains the issue and points to the exact sentence, region or timestamp that triggered it. See [Interpreting Reports](/guides/interpreting-reports). * **The rules that applied.** The legal context snapshot records which laws, guidance and company policies were in force for that check. * **Human review where it matters.** HOLD routes the case to a person. Your own approval policies are checked alongside the law. * **Delivery your way.** Sync, server-sent events per agent, or async with signed webhooks. See [Webhooks](/concepts/webhooks). ## Get started Get an API key, run a check, read the report. API keys, workspaces, scopes and test mode. The specialist agents, what each one checks, and how to call one directly. Give your own agents the ZebraTruth skill so they can run checks autonomously. Publish your policies and brand assets from the dashboard. Every check applies them. Every endpoint, request and response. # Installation Source: https://docs.zebratruth.ai/llm-skill/installation Install the ZebraTruth compliance skill in your LLM agent # Installation ## Claude Code Two steps inside the Claude Code session — both are required, in order: **Step 1.** Register the ZebraTruth AI Labs marketplace (only needed once per machine): ``` /plugin marketplace add dafstar76/zebratruth-ai-labs ``` **Step 2.** Install the compliance plugin: ``` /plugin install zebratruth-compliance@zebratruth-ai-labs ``` Verify the install: ``` /plugin list ``` You should see `zebratruth-compliance` in the output. Then set your API key in the shell that launches Claude Code (the env var is read at session start): ```bash theme={null} export ZEBRATRUTH_API_KEY=zt_live_your_key_here ``` If Claude Code is already running, restart the session so it picks up the env var. ### Autonomous-agent install (agent driving Claude Code on a customer's behalf) Run these two slash commands sequentially via your agent's command-execution interface, then ensure `ZEBRATRUTH_API_KEY` is in the environment before Claude Code starts: ``` /plugin marketplace add dafstar76/zebratruth-ai-labs /plugin install zebratruth-compliance@zebratruth-ai-labs ``` The marketplace-add step is required — skipping it produces `Marketplace 'zebratruth-ai-labs' not found`. ## OpenClaw ```bash theme={null} openclaw skills install zebratruth-compliance openclaw config set env.vars.ZEBRATRUTH_API_KEY "zt_live_your_key_here" openclaw gateway restart ``` ## Manual Integration For any LLM platform (or autonomous agent) that supports markdown injection: 1. Download [SKILL.md](https://raw.githubusercontent.com/dafstar76/zebratruth-ai-labs/master/skills/zebratruth-compliance/SKILL.md) — this is the **raw** URL, suitable for both browser download and `curl`/`wget`: ```bash theme={null} curl -O https://raw.githubusercontent.com/dafstar76/zebratruth-ai-labs/master/skills/zebratruth-compliance/SKILL.md ``` 2. Include it in the LLM's system prompt or context 3. Set `ZEBRATRUTH_API_KEY` in the environment The LLM will automatically learn how to authenticate, run checks, and interpret results. The 9 workflow documents listed below live alongside `SKILL.md` under the same directory pattern, so an agent that needs one can fetch it on demand: ```bash theme={null} curl -O https://raw.githubusercontent.com/dafstar76/zebratruth-ai-labs/master/skills/zebratruth-compliance/workflows/{workflow-name}.md ``` ## Workflow Documents The SKILL.md references 9 workflow documents for detailed procedures. These are loaded by the LLM on demand — you don't need to include them all upfront: | Workflow | Purpose | | ----------------------------- | --------------------- | | `content-compliance-check.md` | Full check procedure | | `image-rights-clearance.md` | Image-specific checks | | `streaming-integration.md` | SSE consumption | | `async-webhook-execution.md` | Submit-then-poll | | `individual-agents.md` | Per-agent invocation | | `interpreting-reports.md` | Scores & decisions | | `embedding-annotations.md` | Map to UI | | `debugging.md` | Logs, traces, replay | # LLM Skill Overview Source: https://docs.zebratruth.ai/llm-skill/overview Integrate ZebraTruth compliance into any LLM agent via SKILL.md # LLM Skill (SKILL.md) ZebraTruth can be integrated into **any LLM agent** by injecting a single markdown file into the agent's context. No SDK, no code generation, no tool schemas — the LLM reads the documentation and makes HTTP calls. ## How It Works 1. The `SKILL.md` file is injected into the LLM's system prompt 2. The LLM learns how to authenticate, run checks, and interpret results 3. When the user asks to check content, the LLM calls the ZebraTruth API via HTTP 4. Results are interpreted and presented to the user with actionable recommendations ## What the LLM Can Do * Validate API keys via `GET /whoami` * Run compliance checks (sync, stream, or async) * Run native video checks with `POST /v1/compliance/media/upload-url`, `POST /v1/compliance/check-video`, and `GET /v1/compliance/check-video/{requestId}` * Invoke individual agents for targeted checks * Interpret scores, decisions, and annotations * Check the credit balance before long-running operations * Handle errors (401, 402, 429) with appropriate user guidance Agents should use this guide when a user asks to check a video file. No client-side ffmpeg is required. ## Why This Approach The SKILL.md pattern is: * **Platform-agnostic** — works with Claude Code, OpenClaw, or any LLM that supports markdown injection * **No SDK to maintain** — the integration IS the documentation * **Always up to date** — update the SKILL.md, all integrations update automatically * **Lazy-loaded** — workflow docs are loaded on demand, keeping base context small ## SKILL.md Structure The skill file has two parts: 1. **YAML frontmatter** — machine-readable metadata (name, API base URL, auth method, capabilities) 2. **Markdown body** — the LLM system prompt (auth flow, quick start, result interpretation, workflow routing table) Detailed workflows are in separate files loaded by the LLM on demand. # Quickstart Source: https://docs.zebratruth.ai/quickstart Run your first compliance check in five minutes, on the text or document an agent is about to send # Run Your First Check End to end in five minutes: get an API key, check a draft an agent has prepared, read the decision. The same call works for a customer notice, an advice letter, a promotion, a KYC message or ad copy. ## 1. Get your API key Sign up at [developers.zebratruth.ai](https://developers.zebratruth.ai), choose the industries your workspace operates in, and create an API key. ```bash theme={null} export ZEBRATRUTH_API_KEY=zt_live_your_key_here ``` Test keys (`zt_test_...`) return deterministic sandbox responses and consume no credits. Use them for development and integration testing. ## 2. Validate your key ```bash theme={null} curl https://api.zebratruth.ai/v1/whoami \ -H "Authorization: Bearer $ZEBRATRUTH_API_KEY" ``` ```json Response theme={null} { "tenantId": "kp_a269d9695f734329a216669410731a21", "tier": "pro", "authType": "apikey", "scopes": ["compliance:check", "agents:*", "reports:read", "policies:read"] } ``` ## 3. Check a draft before it goes out A US advisory agent has drafted an investment recommendation for a retail client. Submit it with the review scope that applies and say what kind of artifact it is. ```bash theme={null} curl -X POST https://api.zebratruth.ai/v1/compliance/check \ -H "Authorization: Bearer $ZEBRATRUTH_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jurisdictions": ["us"], "platforms": ["linkedin"], "reviewScopes": ["advisory_suitability"], "contentKind": "advice_letter", "content": { "text": "Based on our review, we recommend moving 60% of your portfolio into the Apex Leveraged Growth Fund. It has returned 24% a year and is the best option for you." } }' ``` ```json Response (trimmed) theme={null} { "requestId": "req_9f1c2e7a", "decision": "HOLD", "score": 46, "decisionReasons": ["flagged_check"], "agentsRun": [ { "id": "fs-compliance-advisory-suitability", "outcome": "completed" } ], "checks": [ { "checkName": "Suitability basis not documented", "status": "flag", "severity": "high", "primaryCategory": "consumer-information-disclosure", "message": "The recommendation to concentrate 60% of the portfolio in a single leveraged fund does not record the client's risk tolerance, time horizon or liquidity needs ...", "citation": "...", "recommendation": "Document the client profile the recommendation relies on and why the product fits it, disclose the leverage and concentration risks, and remove the implication that past returns will continue." } ], "cached": false } ``` Three fields drive the workflow. `decision` is what the agent acts on: `PUBLISH` proceeds, `HOLD` routes to a person, `BLOCK` stops. `decisionReasons` lists every rule that produced it. `agentsRun` records which specialists ran and how each finished, which together with the findings is the audit record. `jurisdictions` and `platforms` must be within the set your workspace subscribed to during onboarding; anything outside it returns 403. `platforms` is required on every request even when the artifact is not published to a platform. `reviewScopes` applies to workspaces on the financial-services industry; omit it elsewhere. ## 4. Check a document instead Send a PDF or Word file as a multipart `document` part. It is extracted server-side and checked exactly like text. ```bash theme={null} curl -X POST https://api.zebratruth.ai/v1/compliance/check \ -H "Authorization: Bearer $ZEBRATRUTH_API_KEY" \ -F "document=@suitability-report.pdf" \ -F "jurisdictions=us" \ -F "platforms=linkedin" \ -F "reviewScopes=advisory_suitability" \ -F "contentKind=suitability_report" \ -F "mode=fast" ``` To see the extracted text first, call `POST /v1/compliance/documents/extract` with the same `document` part. Extraction is not billed. ## 5. Choose how you want the answer | `responseMode` | Behaviour | Use when | | ---------------- | ------------------------------------------------------------------ | ------------------------------------------- | | `sync` (default) | Blocks until the report is ready, typically 15 to 120 seconds | One artifact at a time inside an agent step | | `stream` | Server-sent events as each agent finishes | Showing progress to a reviewer | | `async` | Returns a `jobId` immediately and POSTs the report to `webhookUrl` | Batches, long documents, video | Set client timeouts to at least 150 seconds for `sync`. See [Streaming Integration](/guides/streaming-integration) and [Async and Webhook Execution](/guides/async-webhook-execution). ## Images and video Images go to `POST /v1/compliance/check-image` as a multipart `image` part and return synchronously with findings located by bounding box. Video is a three-step asynchronous flow: request an upload URL, upload the bytes, submit the check, then poll for timestamped findings. Both are covered in [Images, Documents and Video](/concepts/other-content-types) and the [video guide](/guides/video-compliance-check). ## Next steps Every request field, response mode and error code. The specialists by industry, the review scopes, and how to read `agentsRun`. Decisions, scores, severities, and how findings cite their evidence. Give your own agents the ZebraTruth skill so they can run checks autonomously. # Video Compliance Source: https://docs.zebratruth.ai/video How ZebraTruth analyzes video — async pipeline, evidence IDs, the report schema # Video Compliance — Concept ZebraTruth runs compliance checks on video ads through a **server-side async pipeline**. Your client uploads the video, submits a job, and either polls or receives a webhook when the report is ready. No client-side ffmpeg, no preprocessing, no transcript provisioning — ZebraTruth handles everything. **POST `https://api.zebratruth.ai/v1/compliance/check-video`** [**Read the full guide with curl, Node, and Python examples →**](/guides/video-compliance-check) ## Endpoint family | Endpoint | Method | Purpose | | --------------------------------------------- | ------ | ------------------------------------------------------------------- | | `/v1/compliance/media/upload-url` | POST | Get a signed upload URL for managed blob storage (15-min TTL) | | `/v1/compliance/check-video` | POST | Submit for analysis (returns 202 with `jobId` + `estimatedCredits`) | | `/v1/compliance/check-video/{requestId}` | GET | Poll for status + inlined report | | `/v1/compliance/webhooks/video/secret` | GET | Get/create your tenant's webhook signing secret | | `/v1/compliance/webhooks/video/secret/rotate` | POST | Rotate the secret (in-flight deliveries keep old signature) | ## The 3-step flow ``` 1. Get upload URL ──→ 2. PUT video bytes ──→ 3. POST check-video ──→ 4. Poll or webhook (1 round-trip) (direct to blob) (returns 202 + jobId) (status: completed) ``` See [Quickstart](/quickstart) or the [full guide](/guides/video-compliance-check) for working curl commands at each step. ## Evidence IDs — how findings cite the source Every check in the report carries an `evidenceIds[]` array referencing stable IDs from the evidence timeline: | ID format | What it cites | | ------------ | ---------------------------------------------------------------------------------------- | | `audio_NNN` | A transcript segment from the audio track (sentence-level) | | `frame_NNN` | A sampled video frame (with timestamp) | | `ocr_NNN` | A specific OCR detection within a frame | | `signal_NNN` | A controlled-vocab signal (brand, watermark, restricted category, regulatory disclosure) | Plus every check carries a `timelineLocation`: ```json theme={null} { "startMs": 4500, "endMs": 7200, "track": "audio" } ``` So findings always pin to a specific moment in the source video, enabling timestamped UI annotations in your video editor. ## VideoComplianceCheck schema ```typescript theme={null} interface VideoComplianceCheck { agentId: "ai-laws" | "advertising-law" | "platform-policy" | "metadata-labeling" | "rights-clearance" | "rights-clearance-image" checkName: string status: "pass" | "flag" | "block" | "escalate" severity: "info" | "low" | "medium" | "high" | "critical" message: string recommendation?: string citation?: string primaryCategory?: string categories?: string[] // Video-mode required fields: evidenceIds: string[] timelineLocation: { startMs: number endMs: number track: "audio" | "video" | "mixed" } } ``` Checks that fail to populate `evidenceIds` or `timelineLocation` are **dropped + emitted as `analysisDiagnostics`** — never leaked as fake compliance findings. Enforced at the Stage C boundary. ## Report envelope ```typescript theme={null} interface VideoReport { schemaVersion: "video-report.v1" preprocessorVersion: "video-preprocessor.v1" evidenceVersion: "evidence-timeline.v1" tenantId: string requestId: string generatedAt: string status: "complete" | "insufficient_evidence" | "analysis_failed" reason?: string checks: VideoComplianceCheck[] analysisDiagnostics?: AnalysisDiagnostic[] preprocessing: { transcriptionStatus: string frameAnalysisStatus: string degraded: boolean evidenceTruncated: boolean } } ``` The 3 terminal report statuses: | `status` | Meaning | Billing | | ----------------------- | ------------------------------------------------------------------------------------ | -------------------- | | `complete` | Stage A→C ran, real findings produced | Committed (charged) | | `insufficient_evidence` | Pipeline ran but no meaningful content to analyze (silent video, no OCR, no signals) | Released (no charge) | | `analysis_failed` | Engine error — failure recorded for audit | Released (no charge) | ## Limits | | | | ---------------- | ------------------------------------------------------ | | Duration | 3 to 180 seconds | | File size | 200 MB | | Content types | `video/mp4`, `video/webm`, `video/quicktime` | | Frame sample cap | 20 frames per video (scene-change-aware + claim-aware) | | Mode | `fast` (V1 only — full pipeline) | ## What it detects ZebraTruth's video compliance checks span 5 specialized agents: | Agent | Detects | | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | **AI Laws** | Jurisdiction applicability (FTC, EU UCPD, UK ASA, India ASCI, China Ad Law) + AI disclosure rules | | **Advertising Law** | Unsubstantiated claims, missing disclaimers, comparative ad rules, endorsement / influencer marketing rules | | **Platform Policy** | TikTok, YouTube, Instagram, Meta, LinkedIn AI-disclosure + monetization rules | | **Metadata & Labeling** | Required visible labels, watermarks, C2PA metadata | | **Rights Clearance** | Brand logos, restricted-category content safety (alcohol/tobacco/firearm/gambling/pharmaceutical), watermarks, regulatory disclosure presence | Plus deterministic checks emitted by the per-frame analysis (brand-detected, watermark-detected, restricted-label, safesearch-flag, regulatory-disclosure) that don't require LLM calls. ## Webhooks vs polling **Polling** is the default. Poll `GET /v1/compliance/check-video/{requestId}` every 2 seconds. Stop when `status` is `completed` or `failed`. **Webhooks** are recommended for production. Submit with `webhookUrl` set, configure a tenant signing secret via `/v1/compliance/webhooks/video/secret`, and receive HMAC-signed POSTs with stable signatures across retries (5 attempts, exponential backoff 0/1m/5m/15m/1h). See [Async + Webhooks](/guides/async-webhook-execution) for HMAC verification. ## Idempotency Submitting `POST /v1/compliance/check-video` with the same `(requestId, blobPath, jurisdictions, platforms, mode)` tuple returns 202 with `idempotent: true` — no extra charge. Different inputs with the same `requestId` return 409 `idempotency_conflict`. ## Next Full step-by-step guide. [**Open the guide →**](/guides/video-compliance-check) HMAC verification, retry semantics, replay protection. [**Read the webhooks guide →**](/guides/async-webhook-execution)