# This file is mirrored from ../../../docs/artlint-developer-api.openapi.yaml.
# Update the shared source file first, then run `npm run sync:openapi` from frontend/docs.
openapi: 3.1.0
info:
  title: Artlint API
  version: 1.0.0
  summary: Analyze videos from your own systems with one call.
  description: |
    Send a video, as its bytes or a URL, with a credit ceiling. Artlint prepares it,
    prices the whole clip, runs Standard (the default) or Max and reports the outcome once, by
    webhook and on the analysis. Every response and event says what the analysis
    cost and what your organization has left.

    Standard checks visual quality, continuity, and playback.
    Max includes Standard, with deeper review of motion and physical interactions.
    Select `analysis_level: max` to request Max.

    Authenticate with a workspace API key created in the Artlint console under
    Account → API keys, sent as `Authorization: Bearer satk_…`. Keys work only
    on this API, and nothing else works here.

    All plans allow files up to 2 GiB (2,147,483,648 bytes), uploaded or fetched
    from a URL. Free allows 1 minute per video and 1 concurrent analysis;
    Creator 5 minutes and 1; Pro 10 minutes and 2; Studio 30 minutes and 3.
    Concurrency is shared across the organization's workspaces and app/API work,
    including downloads, preparation and queued checks. Credit packs and unlimited
    credits do not raise these limits. Without an active, unexpired recognized
    plan, Free limits apply. Workspace storage allowances and service capacity
    can impose additional limits.

    Read completed results through the analysis summary and paginated findings.
    Full-report retrieval and evidence download grants are currently disabled
    on this API and return 404. New responses and events omit the report link.

    This file is the source of truth for the developer API's reference
    (docs/ARTLINT_DEVELOPER_API.md). The Mintlify docs mirror it into
    `frontend/docs/openapi/artlint-developer-api.openapi.yaml`.
jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema
servers:
  - url: https://api.artlint.com
    description: Production
  - url: http://localhost:8084
    description: A local video API with ARTLINT_DEVELOPER_API=on
security:
  - apiKey: []
tags:
  - name: analyses
    description: Submit videos and follow their analyses.
  - name: account
    description: Your balance and the key you are using.
paths:
  /v1/analyses:
    post:
      tags: [analyses]
      summary: Analyze a video
      description: |
        The one call. Send the file as multipart/form-data (an `options` part, then a
        `file` part), or a `video_url` as JSON. The analysis is asynchronous: this
        answers 202 with it, and its result arrives as an `artlint.analysis.*` event,
        or by reading it. A repeated `Idempotency-Key` with the same options answers
        200 with the analysis the first request made.
      operationId: createAnalysis
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [options, file]
              properties:
                options:
                  $ref: "#/components/schemas/AnalysisOptions"
                file:
                  type: string
                  format: binary
                  description: An MP4, MOV or WebM video of at most 2 GiB (2,147,483,648 bytes) on every plan. Send it after `options`; options are limited to 16 KiB and multipart overhead to 64 KiB.
            encoding:
              options:
                contentType: application/json
              file:
                contentType: video/*, application/octet-stream
          application/json:
            schema:
              $ref: "#/components/schemas/AnalysisURLRequest"
            example:
              video_url: https://acme-media.s3.amazonaws.com/ep02.mp4?X-Amz-Signature=…
              max_credits: 20
              client_reference: acme-job-8813
      responses:
        "202":
          description: The analysis was created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AnalysisResponse"
        "200":
          description: The analysis an earlier request with this Idempotency-Key made.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AnalysisResponse"
        "400":
          $ref: "#/components/responses/Error"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          description: "`idempotency_key_reused`, `analysis_level_unavailable` (Max is not configured or available), `upload_allowance_exceeded` or `storage_limit_exceeded`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "413":
          description: "`file_too_large`: over 2 GiB on any plan. A URL download over this ceiling instead ends the accepted analysis as failed with source_too_large."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "415":
          description: "`unsupported_media_type`: neither multipart/form-data nor JSON."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: "`invalid_request` (with `details.field`), `invalid_video_url`, `options_must_precede_file` or `unsupported_container`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          $ref: "#/components/responses/TooMany"
        "503":
          description: "`upload_busy`, or `storage_unavailable` naming the failed analysis in `details.analysis_id`. Send it again after Retry-After."
          headers:
            Retry-After:
              $ref: "#/components/headers/RetryAfter"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    get:
      tags: [analyses]
      summary: List analyses
      description: The key's workspace's analyses, newest first. Items carry no balance; deleted analyses are not listed.
      operationId: listAnalyses
      parameters:
        - name: client_reference
          in: query
          schema: { type: string, maxLength: 128 }
        - name: status
          in: query
          schema:
            $ref: "#/components/schemas/AnalysisStatus"
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 100, default: 50 }
        - name: cursor
          in: query
          description: The `next_cursor` of the previous page.
          schema: { type: string }
      responses:
        "200":
          description: A page of analyses.
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: object
                    required: [items, next_cursor]
                    properties:
                      items:
                        type: array
                        items:
                          $ref: "#/components/schemas/Analysis"
                      next_cursor:
                        type: [string, "null"]
        "401":
          $ref: "#/components/responses/Unauthorized"
        "422":
          $ref: "#/components/responses/Error"
  /v1/analyses/{analysis_id}:
    parameters:
      - $ref: "#/components/parameters/AnalysisID"
    get:
      tags: [analyses]
      summary: Get an analysis
      description: Where the analysis stands, with the balance as of now.
      operationId: getAnalysis
      responses:
        "200":
          description: The analysis.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AnalysisResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "410":
          $ref: "#/components/responses/Deleted"
    delete:
      tags: [analyses]
      summary: Delete an analysis
      description: |
        Erases the analysis's video, report and evidence, canceling and refunding the
        analysis if it has not ended. From then on the analysis answers 410. Deleting it
        again answers the same.
      operationId: deleteAnalysis
      responses:
        "202":
          description: The erasure was recorded.
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: object
                    required: [id, deleted, state]
                    properties:
                      id: { type: string }
                      deleted: { const: true }
                      state:
                        type: string
                        enum: [pending, verified]
                        description: "`verified` once storage is proven empty."
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
  /v1/analyses/{analysis_id}/cancel:
    parameters:
      - $ref: "#/components/parameters/AnalysisID"
    post:
      tags: [analyses]
      summary: Cancel an analysis
      description: Ends an unfinished analysis. After its check has started, the check is canceled and its charge refunded.
      operationId: cancelAnalysis
      responses:
        "202":
          description: The analysis, canceled.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AnalysisResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: "`analysis_finished`: it had already ended."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "410":
          $ref: "#/components/responses/Deleted"
  # Disabled developer API surface; retained for a future restore.
  #   /v1/analyses/{analysis_id}/report:
  #     parameters:
  #       - $ref: "#/components/parameters/AnalysisID"
  #     get:
  #       tags: [analyses]
  #       summary: Get the report
  #       description: The immutable report (schema 1.3) of a completed analysis.
  #       operationId: getAnalysisReport
  #       responses:
  #         "200":
  #           description: The report.
  #           content:
  #             application/json:
  #               schema:
  #                 type: object
  #                 required: [data]
  #                 properties:
  #                   data:
  #                     $ref: "#/components/schemas/Report"
  #         "401":
  #           $ref: "#/components/responses/Unauthorized"
  #         "404":
  #           $ref: "#/components/responses/NotFound"
  #         "409":
  #           $ref: "#/components/responses/ReportNotReady"
  #         "410":
  #           $ref: "#/components/responses/Deleted"
  /v1/analyses/{analysis_id}/findings:
    parameters:
      - $ref: "#/components/parameters/AnalysisID"
    get:
      tags: [analyses]
      summary: List findings
      description: The report's findings, a page at a time.
      operationId: listAnalysisFindings
      parameters:
        - name: category
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 100, default: 50 }
        - name: cursor
          in: query
          schema: { type: string }
      responses:
        "200":
          description: A page of findings.
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: object
                    required: [items, next_cursor]
                    properties:
                      items:
                        type: array
                        items:
                          $ref: "#/components/schemas/Finding"
                      next_cursor:
                        type: [string, "null"]
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/ReportNotReady"
        "410":
          $ref: "#/components/responses/Deleted"
        "422":
          $ref: "#/components/responses/Error"
  # Disabled developer API surface; retained for a future restore.
  #   /v1/analyses/{analysis_id}/evidence/{evidence_id}:
  #     parameters:
  #       - $ref: "#/components/parameters/AnalysisID"
  #       - name: evidence_id
  #         in: path
  #         required: true
  #         schema: { type: string }
  #     get:
  #       tags: [analyses]
  #       summary: Get an evidence link
  #       description: Five minutes' access to one of the report's evidence files.
  #       operationId: getAnalysisEvidence
  #       responses:
  #         "200":
  #           description: A link valid for five minutes.
  #           content:
  #             application/json:
  #               schema:
  #                 type: object
  #                 required: [data]
  #                 properties:
  #                   data:
  #                     type: object
  #                     required: [url, expires_at]
  #                     properties:
  #                       url: { type: string, format: uri }
  #                       expires_at: { type: string, format: date-time }
  #                       kind: { type: string }
  #                       source_span: { type: object }
  #         "401":
  #           $ref: "#/components/responses/Unauthorized"
  #         "404":
  #           $ref: "#/components/responses/NotFound"
  #         "409":
  #           $ref: "#/components/responses/ReportNotReady"
  #         "410":
  #           $ref: "#/components/responses/Deleted"
  /v1/balance:
    get:
      tags: [account]
      summary: Get the balance
      description: Your organization's credits, shared by all its workspaces, and Standard and Max prices.
      operationId: getBalance
      responses:
        "200":
          description: The balance.
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    allOf:
                      - $ref: "#/components/schemas/Balance"
                    properties:
                      pricing:
                        type: object
                        properties:
                          analysis:
                            $ref: "#/components/schemas/AnalysisPrice"
                            description: Legacy alias for Standard pricing.
                          standard:
                            $ref: "#/components/schemas/AnalysisPrice"
                          max:
                            $ref: "#/components/schemas/AnalysisPrice"
              example:
                data:
                  credits_remaining: 476
                  unlimited: false
                  as_of: "2026-10-02T14:05:00Z"
                  pricing:
                    analysis:
                      credits_per_second: 1
                      minimum_credits: 10
                    standard:
                      credits_per_second: 1
                      minimum_credits: 10
                    max:
                      credits_per_second: 5
                      minimum_credits: 150
        "401":
          $ref: "#/components/responses/Unauthorized"
  /v1/key:
    get:
      tags: [account]
      summary: Describe this key
      description: The key that made the request, masked, and its workspace.
      operationId: getKey
      responses:
        "200":
          description: The key.
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: object
                    required:
                      [id, name, key, access, workspace, created_at, expires_at, last_used_at]
                    properties:
                      id: { type: string }
                      name: { type: string }
                      key:
                        {
                          type: string,
                          description: "The masked key, such as satk_Jq3…xY9z. Never the key.",
                        }
                      access: { type: string, enum: [full, read] }
                      workspace:
                        type: object
                        required: [id, name]
                        properties:
                          id: { type: string }
                          name: { type: string }
                      created_at: { type: string, format: date-time }
                      expires_at: { type: [string, "null"], format: date-time }
                      last_used_at:
                        {
                          type: [string, "null"],
                          format: date-time,
                          description: Updated at most once a minute.,
                        }
        "401":
          $ref: "#/components/responses/Unauthorized"
webhooks:
  artlint.analysis.completed:
    post:
      summary: An analysis completed
      description: Its report is published. `findings` holds the most severe findings that fit in the event; `findings_truncated` says whether any were left out. Schema, contracts/webhooks/artlint.analysis.completed.schema.json.
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AnalysisEvent"
      responses:
        "2XX":
          description: Received.
  artlint.analysis.failed:
    post:
      summary: An analysis failed
      description: It ended without a report; `error.code` says why. Schema, contracts/webhooks/artlint.analysis.failed.schema.json.
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AnalysisEvent"
      responses:
        "2XX":
          description: Received.
  artlint.analysis.canceled:
    post:
      summary: An analysis was canceled
      description: By you (`reason` requested) or because its video was deleted (`reason` deleted). Schema, contracts/webhooks/artlint.analysis.canceled.schema.json.
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AnalysisEvent"
      responses:
        "2XX":
          description: Received.
components:
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: A workspace API key, `satk_…`. Read-only keys may only GET.
  parameters:
    AnalysisID:
      name: analysis_id
      in: path
      required: true
      schema: { type: string, pattern: "^ana_[0-9a-f]{32}$" }
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: Makes the request safe to repeat. 1 to 200 printable ASCII characters, without spaces.
      schema: { type: string, minLength: 1, maxLength: 200 }
  headers:
    RetryAfter:
      description: Seconds to wait before trying again.
      schema: { type: integer }
  responses:
    Error:
      description: The request was refused; `error.code` says why.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Unauthorized:
      description: "`unauthorized`: a missing, unknown, expired or revoked key, or anything but a key."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Forbidden:
      description: "`insufficient_scope` (a read-only key writing) or `developer_api_disabled`."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    NotFound:
      description: "`not_found`: no such analysis in the key's workspace."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Deleted:
      description: "`analysis_deleted`."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    ReportNotReady:
      description: "`report_not_ready`: the analysis has not completed."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    TooMany:
      description: |
        `rate_limited`, `upload_in_progress` (one upload at a time per key),
        `too_many_active_analyses` (workspace service capacity), or `plan_concurrency_limit`.
        Plan concurrency is shared across the organization's app/API work, including
        downloading, preparing and queued checks: Free and Creator allow 1 analysis,
        Pro 2, and Studio 3. Plan refusals include `details.plan_limits` with
        `plan_code`, `plan_name`, `max_duration_us`, `max_concurrent_tasks`, and
        `active_tasks`. Idempotent replays can return an existing analysis at capacity.
        Request budgets are 120 requests and 20 submissions per minute per key,
        and 600 requests per minute per workspace, per API process; replays and
        refusals count too. The upload guard is also per process. Plan concurrency
        and workspace service capacity are shared across API replicas.
      headers:
        Retry-After:
          $ref: "#/components/headers/RetryAfter"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
  schemas:
    AnalysisPrice:
      type: [object, "null"]
      description: Null when the level is not priced. Credits per started second of video, with a minimum charge.
      properties:
        credits_per_second: { type: integer, minimum: 0 }
        minimum_credits: { type: integer, minimum: 0 }
    AnalysisStatus:
      type: string
      enum: [fetching, preparing, analyzing, completed, completed_with_gaps, failed, canceled]
    AnalysisOptions:
      type: object
      required: [max_credits]
      additionalProperties: false
      properties:
        max_credits:
          type: integer
          minimum: 0
          maximum: 1000000000
          description: The most the analysis may spend. It is started only if the whole clip's price is at most this.
        client_reference:
          type: string
          minLength: 1
          maxLength: 128
          description: Your own reference, echoed on the analysis and its event.
        mode:
          const: analysis
          description: Legacy API mode, defaulting to analysis. Use analysis_level to select Standard or Max.
        analysis_level:
          type: string
          enum: [standard, max]
          default: standard
          description: Standard is the full analysis at 1 credit/second with a 10-credit minimum. Max adds focused physics review at 5 credits/second with a 150-credit minimum. Installation prices are returned by GET /v1/balance.
        filename:
          type: string
          minLength: 1
          maxLength: 255
        project_id:
          type: string
          description: Defaults to the workspace's API project.
    AnalysisURLRequest:
      allOf:
        - $ref: "#/components/schemas/AnalysisOptions"
      required: [video_url, max_credits]
      properties:
        video_url:
          type: string
          format: uri
          maxLength: 2048
          description: An https URL on a public host, without credentials. Artlint deletes it once downloaded.
    AnalysisResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/Analysis"
    Analysis:
      type: object
      required:
        [
          id,
          status,
          stage,
          client_reference,
          mode,
          max_credits,
          credits,
          source,
          media,
          summary,
          error,
          project_id,
          asset_version_id,
          run_id,
          created_at,
          updated_at,
          completed_at,
          links,
        ]
      properties:
        id: { type: string, pattern: "^ana_[0-9a-f]{32}$" }
        status:
          $ref: "#/components/schemas/AnalysisStatus"
        stage:
          type: string
          enum: [fetching, preparing, starting, queued, analyzing, verifying, finalizing, done]
        client_reference: { type: [string, "null"] }
        mode: { const: analysis }
        analysis_level: { type: string, enum: [standard, max] }
        max_credits: { type: integer }
        credits:
          $ref: "#/components/schemas/Credits"
        balance:
          $ref: "#/components/schemas/Balance"
        source:
          type: object
          required: [type, filename]
          properties:
            type: { type: string, enum: [upload, url] }
            filename: { type: string }
            host: { type: string, description: "The URL's host, for a video fetched from a URL." }
            byte_size: { type: integer }
            sha256: { type: string }
        media:
          type: [object, "null"]
          properties:
            duration_us: { type: integer }
            width: { type: integer }
            height: { type: integer }
            fps: { type: number, description: The average frame rate. }
            has_audio: { type: boolean }
        summary:
          oneOf:
            - $ref: "#/components/schemas/Summary"
            - type: "null"
        error:
          oneOf:
            - $ref: "#/components/schemas/AnalysisError"
            - type: "null"
        reason:
          type: string
          enum: [requested, deleted]
          description: Why a canceled analysis was canceled.
        project_id: { type: string }
        asset_version_id: { type: [string, "null"] }
        run_id: { type: [string, "null"] }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        completed_at: { type: [string, "null"], format: date-time }
        links:
          type: object
          required: [self]
          properties:
            self: { type: string, format: uri }
            findings: { type: string, format: uri }
    Credits:
      type: object
      required: [quoted, charged, refunded]
      properties:
        quoted: { type: [integer, "null"], description: "The whole clip's price, once prepared." }
        charged:
          {
            type: [integer, "null"],
            description: "What the check was charged; 0 with unlimited credits, or when no check started.",
          }
        refunded:
          {
            type: [integer, "null"],
            description: What was given back because the check delivered no report.,
          }
    Balance:
      type: object
      required: [credits_remaining, unlimited, as_of]
      properties:
        credits_remaining: { type: [integer, "null"], description: Null when unlimited. }
        unlimited: { type: boolean }
        as_of: { type: string, format: date-time }
    Summary:
      type: object
      required: [findings_total, by_severity, coverage_gaps]
      properties:
        findings_total: { type: integer }
        by_severity:
          type: object
          properties:
            critical: { type: integer }
            major: { type: integer }
            minor: { type: integer }
            info: { type: integer }
        coverage_gaps:
          type: array
          items:
            type: object
            required: [capability_id, dispositions]
            properties:
              capability_id: { type: string }
              dispositions: { type: array, items: { type: string } }
    AnalysisError:
      type: object
      required: [code, message]
      properties:
        code:
          type: string
          description: |
            Download: source_unreachable, source_http_error, source_too_large, source_timeout, source_blocked, unsupported_container.
            Storing: storage_unavailable, upload_allowance_exceeded.
            Preparation: no_video_stream, multiple_video_streams, multiple_audio_programs, audio_format_unsupported,
            resolution_exceeds_limit, hdr_unsupported, rotation_unsupported, interlaced_unsupported, format_changes_midstream,
            invalid_timestamps, frame_count_exceeds_limit, duration_exceeds_limit, plan_duration_limit, decode_failed, preparation_failed.
            Start: max_credits_exceeded, credits_insufficient, not_priced, storage_limit_exceeded, source_expired,
            workspace_erased, plan_duration_limit, plan_concurrency_limit, start_unavailable. Check: analysis_failed, internal_error.
        message: { type: string }
        details: { type: object }
    CompactFinding:
      type: object
      required: [finding_id, category, severity, title, start_us, end_us]
      properties:
        finding_id: { type: string }
        category: { type: string }
        severity: { type: string, enum: [critical, major, minor, info] }
        title: { type: string, maxLength: 160 }
        start_us: { type: [integer, "null"] }
        end_us: { type: [integer, "null"] }
    Finding:
      type: object
      description: A report finding (report schema 1.3).
      required: [finding_id, category, severity, title]
      properties:
        finding_id: { type: string }
        category: { type: string }
        severity: { type: string }
        evidence_level: { type: string }
        title: { type: string }
        description: { type: string }
        source_spans:
          type: array
          items:
            type: object
            properties:
              asset_version_id: { type: string }
              start_us: { type: integer }
              end_us: { type: integer }
        evidence_ids: { type: array, items: { type: string } }
    # Disabled developer API surface; retained for a future restore.
    #     Report:
    #       type: object
    #       description: The immutable report, schema 1.3.
    #       required: [schema_version, report_id, run_id, mode, status, findings, coverage, evidence]
    #       properties:
    #         schema_version: { type: string }
    #         report_id: { type: string }
    #         run_id: { type: string }
    #         mode: { type: string }
    #         status: { type: string }
    #         findings:
    #           type: array
    #           items:
    #             $ref: "#/components/schemas/Finding"
    #         coverage: { type: array, items: { type: object } }
    #         evidence: { type: array, items: { type: object } }
    AnalysisEvent:
      type: object
      required: [id, type, created_at, data]
      properties:
        id:
          {
            type: string,
            description: "The event's ID, also sent as webhook-id. Deduplicate on it.",
          }
        type:
          {
            type: string,
            enum: [artlint.analysis.completed, artlint.analysis.failed, artlint.analysis.canceled],
          }
        created_at: { type: string, format: date-time }
        data:
          type: object
          description: 'The analysis as it ended, or {"scrubbed": true} once its video was erased.'
          properties:
            analysis_id: { type: string }
            workspace_id: { type: string }
            client_reference: { type: [string, "null"] }
            status: { $ref: "#/components/schemas/AnalysisStatus" }
            mode: { const: analysis }
            analysis_level: { type: string, enum: [standard, max] }
            credits: { $ref: "#/components/schemas/Credits" }
            balance: { $ref: "#/components/schemas/Balance" }
            asset_version_id: { type: [string, "null"] }
            run_id: { type: [string, "null"] }
            summary: { $ref: "#/components/schemas/Summary" }
            findings:
              type: array
              items:
                $ref: "#/components/schemas/CompactFinding"
            findings_truncated: { type: boolean }
            error: { $ref: "#/components/schemas/AnalysisError" }
            reason: { type: string, enum: [requested, deleted] }
            links:
              type: object
              properties:
                analysis: { type: string, format: uri }
                findings: { type: string, format: uri }
    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code: { type: string }
            message: { type: string }
            details:
              {
                type: object,
                description: "For invalid_request, `field` names the option or header at fault.",
              }
            request_id: { type: string, description: The request's X-Request-Id. }
