> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zebratruth.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Submit a video for compliance analysis

> 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.




## OpenAPI

````yaml /openapi/zebratruth-v1.yaml post /compliance/check-video
openapi: 3.1.0
info:
  title: ZebraTruth Compliance API
  version: 1.0.0
  description: >
    AI-powered compliance checking for advertising content. Check text and
    images against

    jurisdiction laws, platform policies, and rights clearance requirements.
    Results include

    embeddable annotations mapped to content locations (text spans, video
    timestamps, image regions).
  contact:
    name: ZebraTruth Support
    email: support@zebratruth.ai
    url: https://developers.zebratruth.ai
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT
servers:
  - url: https://api.zebratruth.ai/v1
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Video Compliance
    description: >
      **Primary endpoint.** Async compliance analysis for video ads. 3-step
      flow:

      get an upload URL, upload the video bytes, submit for analysis. Poll or

      receive an HMAC-signed webhook with the report. Returns timestamped

      findings citing stable evidence IDs (`audio_NNN`, `frame_NNN`, `ocr_NNN`,

      `signal_NNN`). 3-180 second videos. mp4 / webm /

      quicktime. Built for ad agencies and creators.
  - name: Authentication
    description: Validate API keys and check account status
  - name: Compliance
    description: >-
      Run compliance checks on text + image content (sync). For video, use the
      Video Compliance endpoints above.
  - name: Agents
    description: Discover and invoke individual compliance agents
  - name: Jobs
    description: Poll async job status and view execution traces
  - name: Reports
    description: Retrieve and replay compliance reports
  - name: Observability
    description: Request logs and webhook delivery status
  - name: Billing
    description: Subscriptions, plan changes, and credit balance
  - name: Documents
    description: Extract text from PDF and Word documents before or as part of a check
  - name: Check History
    description: List and retrieve past compliance checks for the workspace
  - name: Provenance
    description: Inspect and sign Content Credentials (C2PA) on images and video
  - name: Corpus
    description: >-
      Review queue for proposed changes to the governed legal corpus (dashboard
      session)
  - name: Webhooks
    description: Saved webhook endpoints (dashboard session) and delivery logs
paths:
  /compliance/check-video:
    post:
      tags:
        - Video Compliance
      summary: Submit a video for compliance analysis
      description: |
        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.
      operationId: runVideoComplianceCheck
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VideoCheckRequest'
      responses:
        '202':
          description: Job accepted + queued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VideoCheckAcceptedResponse'
        '400':
          description: |
            Validation failure. Common `data.code` values:
              - `blob_path_invalid` — blobPath doesn't start with `videos/{tenantId}/{requestId}/`
              - `unsupported_mode` — only `fast` is supported in V1
              - `video_too_short` / `duration_out_of_range` — duration outside [3, 180]s
              - `blob_not_found` — uploadUrl PUT didn't complete or blob expired
              - `blob_ownership_mismatch` — blob tags don't match the request's tenantId/requestId
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/InsufficientCredits'
        '403':
          $ref: '#/components/responses/TenantScopeDenied'
        '409':
          description: Idempotency conflict — same requestId reused with different inputs
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IdempotencyConflictResponse'
        '429':
          $ref: '#/components/responses/RateLimited'
        '502':
          description: Probe service temporarily unavailable — retry shortly
        '503':
          description: Video analysis temporarily disabled — retry later
components:
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      schema:
        type: string
        format: uuid
      description: >
        Unique request identifier to prevent duplicate execution and
        double-charging.

        Generate a new UUID for each distinct request. Reuse the same key to
        safely retry.
  schemas:
    VideoCheckRequest:
      type: object
      required:
        - requestId
        - blobPath
        - jurisdictions
        - platforms
      properties:
        requestId:
          type: string
          description: The `requestId` returned from `POST /compliance/media/upload-url`.
          example: 82b68316-ed85-489c-991e-2ff0f2b95f29
        blobPath:
          type: string
          description: The `blobPath` returned from `POST /compliance/media/upload-url`.
          example: videos/kp_abc123.../82b68316-.../source.mp4
        jurisdictions:
          type: array
          description: |
            Canonical jurisdiction codes scoped to your account's subscription.
            Inputs are canonicalized (trim + lowercase + sort) before hashing —
            case-only differences are treated as the same request.
          items:
            type: string
          example:
            - us
            - eu
          minItems: 1
        platforms:
          type: array
          description: Canonical platform codes scoped to your account's subscription.
          items:
            type: string
            enum:
              - youtube
              - instagram
              - facebook
              - tiktok
              - linkedin
          example:
            - youtube
            - instagram
          minItems: 1
        mode:
          type: string
          description: Execution mode. V1 supports `fast` only.
          enum:
            - fast
          default: fast
        webhookUrl:
          type: string
          format: uri
          description: |
            Optional callback URL. When set, a signed POST is delivered when the
            job reaches a terminal status. Requires you to have configured a
            webhook signing secret via `GET /compliance/webhooks/video/secret`.
          example: https://your-app.example.com/webhooks/zebratruth
        callbackId:
          type: string
          maxLength: 256
          description: |
            Opaque tag echoed back in the 202 response and the webhook payload.
            Use it to correlate jobs with your internal records.
        reviewScopes:
          type: array
          maxItems: 6
          items:
            $ref: '#/components/schemas/ReviewScope'
          description: >-
            Financial-services review scopes to run on the transcript and
            on-screen text. Same admission rules as the text check. JSON body
            only.
        contentKind:
          allOf:
            - $ref: '#/components/schemas/ContentKind'
          description: What kind of artifact the video is. JSON body only.
    VideoCheckAcceptedResponse:
      type: object
      required:
        - jobId
        - status
        - estimatedCredits
      properties:
        jobId:
          type: string
          description: Same value as the request's `requestId`. Use it to poll status.
          example: 82b68316-ed85-489c-991e-2ff0f2b95f29
        status:
          type: string
          enum:
            - queued
        estimatedCredits:
          type: integer
          description: >-
            Credits reserved for this job. Committed on success or released on
            insufficient_evidence / analysis_failed.
        callbackId:
          type: string
          description: Echoed verbatim from the request when provided.
        idempotent:
          type: boolean
          description: |
            Present + `true` when this request matched an existing job with the
            same `(requestId, blobPath, feature, inputHash)`. No additional
            credits reserved.
    IdempotencyConflictResponse:
      type: object
      description: >
        Returned by `POST /compliance/check-video` 409 when the same

        `requestId` is reused with different inputs. The conflicting field

        is reported in `reason`; precedence is `blobPath` → `feature` →
        `inputHash`.
      required:
        - error
        - code
        - reason
      properties:
        error:
          type: boolean
          example: true
        code:
          type: string
          enum:
            - idempotency_conflict
        reason:
          type: string
          enum:
            - blobPath
            - feature
            - inputHash
          description: Which field differed between the existing and incoming request.
        existing:
          type: string
          description: The value stored on the existing job row.
        incoming:
          type: string
          description: The value submitted in this request.
    ReviewScope:
      type: string
      enum:
        - financial_promotion
        - advisory_suitability
        - credit_servicing
        - markets_trading
        - financial_crime
        - ai_governance
      description: >
        A financial-services review scope. Each maps to one specialist agent:

        financial promotions, disclosures and conduct; investment advice and
        suitability;

        credit origination, servicing and collections; order execution,
        transaction reporting

        and market abuse; KYC, AML/CFT, fraud and sanctions; AI governance,
        model risk and

        automated decisions. Coverage is US, EU and UK for every scope, plus
        China and India

        for `financial_promotion`.
    ContentKind:
      type: string
      enum:
        - marketing
        - suitability_report
        - advice_letter
        - precontract_disclosure
        - adverse_action_notice
        - collections_communication
        - arrears_notice
        - fund_factsheet
        - research_note
        - trader_communication
        - best_execution_disclosure
        - kyc_onboarding
        - scam_warning
        - aml_policy
        - ai_policy
        - model_documentation
        - other
      description: >
        What kind of artifact the check is looking at. Adds applicability
        triggers and ranks

        the obligations the specialists cite. It never removes a selected review
        scope.
  responses:
    Unauthorized:
      description: |
        Invalid, missing, or expired API key. Standard error envelope —
        read `message` for the human-readable reason.
      content:
        application/json:
          schema:
            type: object
            description: Standard error envelope.
            required:
              - statusCode
              - message
            properties:
              error:
                type: boolean
                example: true
              url:
                type: string
                format: uri
                example: https://api.zebratruth.ai/v1/whoami
              statusCode:
                type: integer
                example: 401
              statusMessage:
                type: string
                example: Invalid API key
              message:
                type: string
                description: Canonical human-readable reason — pattern-match on this field.
                example: Invalid API key
    InsufficientCredits:
      description: >
        Not enough credits in the tenant's current period to process this
        request.

        Standard error envelope plus a `data` object with credit specifics

        and an upgrade URL.
      content:
        application/json:
          schema:
            type: object
            description: Standard error envelope with a `data` payload.
            required:
              - statusCode
              - message
            properties:
              error:
                type: boolean
                example: true
              url:
                type: string
                format: uri
              statusCode:
                type: integer
                example: 402
              statusMessage:
                type: string
                example: Insufficient credits
              message:
                type: string
                example: Insufficient credits
              data:
                type: object
                description: >-
                  Per-error context. On the wire this is reduced to `{ code }`
                  plus, where documented, a small set of named fields.
                properties:
                  creditsRemaining:
                    type: number
                    example: 3
                  creditsNeeded:
                    type: number
                  upgradeUrl:
                    type: string
                    format: uri
                    example: https://developers.zebratruth.ai/dashboard/billing
    TenantScopeDenied:
      description: >
        The request asks for a jurisdiction or platform not in your
        subscription, or your

        tenant record is missing / incomplete. The human-readable reason is in
        the

        `statusMessage` and `message` fields (both carry the same string —
        `message` is

        the canonical field in the error envelope). Five possible messages:


        - **Not onboarded** — no tenant record exists yet.
          `"Tenant not onboarded. Complete setup at https://developers.zebratruth.ai before using the API."`
        - **Incomplete config** — tenant exists but has no default jurisdictions
        or platforms set.
          `"Tenant configuration incomplete. Set default jurisdictions and platforms in your dev portal."`
        - **Jurisdiction not subscribed** — request includes jurisdictions
        outside the subscription.
          `"Requested jurisdictions [india, china] are not in your subscription. Allowed: [us, eu]."`
        - **Platform not subscribed** — request includes platforms outside the
        subscription.
          `"Requested platforms [tiktok] are not in your subscription. Allowed: [youtube, instagram]."`
        - **Scope wider than the plan allows** — carries `data.code:
          "scope_limit_exceeded_config"`, the only one of the five that does.
          `"Your plan allows at most 2 jurisdictions, but your account has 3 selected. Reduce your
          jurisdictions in the developer portal, or request at most 2 on this call."`
          Reachable after a plan change, because a scope chosen during the unlimited trial can be
          wider than the plan you end up on.

        Fix: either adjust the request to stay within the subscribed set, or
        update your

        defaults at `https://developers.zebratruth.ai/dashboard/settings`.
      content:
        application/json:
          schema:
            type: object
            description: >-
              Standard error envelope. Read `message` (or `statusMessage`) for
              the human-readable reason.
            required:
              - statusCode
              - message
            properties:
              error:
                type: boolean
                description: Always `true` for error responses.
                example: true
              url:
                type: string
                format: uri
                description: The URL that was requested.
                example: https://api.zebratruth.ai/v1/compliance/check
              statusCode:
                type: integer
                example: 403
              statusMessage:
                type: string
                description: Human-readable reason — same content as `message`.
                example: >-
                  Requested jurisdictions [india, china] are not in your
                  subscription. Allowed: [us, eu].
              message:
                type: string
                description: >-
                  Canonical human-readable reason — pattern-match on this field
                  in client code.
                example: >-
                  Requested jurisdictions [india, china] are not in your
                  subscription. Allowed: [us, eu].
    RateLimited:
      description: |
        Per-tenant rate limit exceeded. Read the `Retry-After` header for the
        wait duration. Body is the standard error envelope plus a
        `data` object with limit context.
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds to wait before retrying.
      content:
        application/json:
          schema:
            type: object
            description: Standard error envelope with a `data` payload.
            required:
              - statusCode
              - message
            properties:
              error:
                type: boolean
                example: true
              url:
                type: string
                format: uri
              statusCode:
                type: integer
                example: 429
              statusMessage:
                type: string
                example: Rate limit exceeded
              message:
                type: string
                example: Rate limit exceeded
              data:
                type: object
                description: >-
                  Per-error context. On the wire this is reduced to `{ code }`
                  plus, where documented, a small set of named fields.
                properties:
                  retryAfter:
                    type: integer
                    description: >-
                      Mirrors the `Retry-After` header for clients that prefer
                      body fields.
                    example: 5
                  limit:
                    type: integer
                    description: Tenant's requests-per-minute cap.
                    example: 60
                  remaining:
                    type: integer
                    description: Requests remaining in the current window.
                    example: 0
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: |
        API key starting with `zt_live_` (production) or `zt_test_` (sandbox).
        Get your key at https://developers.zebratruth.ai

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.