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

# 360 Viewer & Hotspoter

> Tool to anchor points of interest onto named parts of a vehicle across a set of images.

- Allows you to request a point of interest on a named part of the vehicle.
- Proposes an x,y position on each image where the requested part is visible.
- Offers the possibility of anchoring several points across a whole series of photos of
  the same vehicle.

Give it a list of images and a part — `bonnet` — and it returns, per image, a coordinate
where the hotspot belongs. Images that do not show the part come back with `visible:
false` and a null point rather than with a guess.

For a visible part, the anchor is the pixel furthest from the segmented part boundary.
This keeps the marker inside the panel, including on irregular or concave shapes. The
response also includes the part bounding box and model confidence for each image.

This returns geometry, not a rendered view. The 360 viewer is the consumer of that
geometry, not this endpoint. Anything that can draw a marker on a photo can use the
output. Billed per image submitted.




## OpenAPI

````yaml /openapi.yaml post /v1/render/hotspots
openapi: 3.1.0
info:
  title: CarHub API
  version: '2026-08-01'
  summary: >-
    Automotive AI as a service — 24 inference endpoints, one asynchronous
    contract.
  description: >
    CarHub exposes automotive computer-vision and pricing models as plain HTTP
    endpoints.

    Every model is priced and rate-limited on its own — nothing requires
    adopting the rest.


    ## Two API surfaces


    | Surface | Prefix | Authentication |

    |---|---|---|

    | Inference (the product) | `/v1/…` | API key — `Authorization: Bearer
    chk_live_…` / `chk_test_…` |

    | Management (the dashboard) | `/mgmt/v1/…` | Short-lived JWT —
    `Authorization: Bearer <jwt>` |


    ## Everything is a job


    Every inference endpoint is `POST` and **asynchronous by default**: it
    answers `202` with a

    [`Job`](#/components/schemas/Job) in status `queued`. Collect the result by
    polling

    `GET /v1/jobs/{id}` or by subscribing to the `job.succeeded` / `job.failed`
    webhooks.


    Fast endpoints additionally accept `?wait=true`, which holds the connection
    until the job

    settles (30 s ceiling) and answers `200` with the same `Job` object in
    status `succeeded`.

    Video, 360 and bundle endpoints reject `wait` with `400 wait_not_supported`
    — their work

    outlives any reasonable request timeout.


    ## Inputs


    Every endpoint accepts two request encodings for the same operation:


    * `multipart/form-data` — the binary directly in the request (`image`,
    `audio`, `video` or
      `images[]` depending on the endpoint). Simplest path, capped at 10 MB per file.
    * `application/json` — `{"input_ref": "upl_…"}` (or `input_refs` for
    multi-asset endpoints)
      referencing an upload created through `POST /v1/uploads`. Required for videos and batches.

    Endpoint parameters are identical in both encodings.


    ## Idempotency


    Send `Idempotency-Key` on any `POST`. A replayed key returns the original
    response and never

    bills twice; reusing a key with a different payload is a `409
    idempotency_key_reuse`.


    ## Test mode


    Keys prefixed `chk_test_` run the entire pipeline — auth, rate limiting, job
    lifecycle,

    ledger, signed webhooks — against a fixture backend instead of a GPU. Test
    jobs carry

    `livemode: false` and draw on a separate, freely rechargeable test credit
    balance.

    Drive the fixture backend per request with `X-Carhub-Test-Scenario`.
  termsOfService: https://trycarhub.com/terms
  contact:
    name: CarHub API support
    url: https://docs.trycarhub.com
    email: api@trycarhub.com
  license:
    name: Proprietary
    identifier: LicenseRef-CarHub-Commercial
servers:
  - url: https://api.trycarhub.com
    description: Production (EU)
security:
  - ApiKeyAuth: []
tags:
  - name: A — Understand the vehicle
    description: >-
      Identity, attributes and a clean capture — everything downstream reads
      from here.
  - name: B — Damage & condition
    description: Find it, then grade it, so your own rules can act on the result.
  - name: C — Parts
    description: >-
      Turns "there is damage" into "there is damage on this component" — what
      makes every downstream estimate defensible.
  - name: D — Money
    description: Valuation and repair cost, computed from what was actually observed.
  - name: E — Image production
    description: >-
      Exposed as raw infrastructure, not a batch retouching service. Call it per
      asset, in your own pipeline.
  - name: Inspections
    description: The eight-stage bundle, billed as a single blended inspection.
  - name: Jobs
    description: Status, results and history of asynchronous work.
  - name: Uploads
    description: Presigned storage for videos and batches.
  - name: Management
    description: >-
      Organisation, API keys, usage, billing and outgoing webhooks. JWT
      authenticated.
  - name: Internal
    description: Inbound callbacks from the inference plane. Never called by customers.
externalDocs:
  description: CarHub developer documentation
  url: https://docs.trycarhub.com
paths:
  /v1/render/hotspots:
    post:
      tags:
        - E — Image production
      summary: 360 Viewer & Hotspoter
      description: >
        Tool to anchor points of interest onto named parts of a vehicle across a
        set of images.


        - Allows you to request a point of interest on a named part of the
        vehicle.

        - Proposes an x,y position on each image where the requested part is
        visible.

        - Offers the possibility of anchoring several points across a whole
        series of photos of
          the same vehicle.

        Give it a list of images and a part — `bonnet` — and it returns, per
        image, a coordinate

        where the hotspot belongs. Images that do not show the part come back
        with `visible:

        false` and a null point rather than with a guess.


        For a visible part, the anchor is the pixel furthest from the segmented
        part boundary.

        This keeps the marker inside the panel, including on irregular or
        concave shapes. The

        response also includes the part bounding box and model confidence for
        each image.


        This returns geometry, not a rendered view. The 360 viewer is the
        consumer of that

        geometry, not this endpoint. Anything that can draw a marker on a photo
        can use the

        output. Billed per image submitted.
      operationId: anchorHotspots
      parameters:
        - $ref: '#/components/parameters/Wait'
        - $ref: '#/components/parameters/IdempotencyKey'
        - $ref: '#/components/parameters/TestScenario'
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              allOf:
                - $ref: '#/components/schemas/MultipartImagesInput'
                - $ref: '#/components/schemas/ViewerHotspotsParams'
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/JsonMultiInput'
                - $ref: '#/components/schemas/ViewerHotspotsParams'
      responses:
        '200':
          description: Job completed synchronously (`wait=true`).
          headers:
            Request-Id:
              $ref: '#/components/headers/RequestId'
            Idempotent-Replayed:
              $ref: '#/components/headers/IdempotentReplayed'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ViewerHotspotsJob'
        '202':
          description: Job accepted, credits reserved, work queued.
          headers:
            Request-Id:
              $ref: '#/components/headers/RequestId'
            Location:
              $ref: '#/components/headers/JobLocation'
            Idempotent-Replayed:
              $ref: '#/components/headers/IdempotentReplayed'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ViewerHotspotsJob'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/InsufficientCredits'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
components:
  parameters:
    Wait:
      name: wait
      in: query
      required: false
      description: >
        Hold the connection until the job settles and answer `200` with the
        completed job instead

        of `202` with a queued one. Available on fast endpoints only; the
        ceiling is 30 s, after

        which the job keeps running and the call answers `202` as usual.
      schema:
        type: boolean
        default: false
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: >
        Client-generated key (a UUID is ideal) making this `POST` safe to retry.
        The first request

        is executed and its response stored for 24 h; any replay with the same
        key returns that

        stored response with `Idempotent-Replayed: true` and bills nothing.
        Reusing a key with a

        different payload is a `409 idempotency_key_reuse`.
      schema:
        type: string
        minLength: 8
        maxLength: 255
      examples:
        uuid:
          value: 8b1f1c2e-9d3a-4a51-9f0c-1c9a2f6d7b44
    TestScenario:
      name: X-Carhub-Test-Scenario
      in: header
      required: false
      description: >
        Steers the fixture backend. Ignored by live keys, `400
        test_scenario_not_supported` if the

        scenario is unknown to the endpoint. Common catalogue:


        | Scenario | Effect |

        |---|---|

        | *(absent)* | Deterministic success — the endpoint's `default` fixture.
        |

        | `empty_result` | Success with nothing detected: empty lists, null
        readings. |

        | `low_confidence` | Success with every confidence below 0.5. |

        | `model_error` | Job settles `failed`; the reservation is released. |

        | `slow` | Completion after ~30 s — makes `wait=true` time out. |

        | `insufficient_credits` | `402` at admission, no job created. |


        Some endpoints add their own scenarios; unsupported common scenarios
        (for example

        `empty_result` on a generation endpoint) are rejected rather than
        silently ignored.
      schema:
        type: string
        examples:
          - empty_result
          - low_confidence
          - model_error
          - slow
          - insufficient_credits
  schemas:
    MultipartImagesInput:
      type: object
      required:
        - images
      properties:
        images:
          type: array
          description: Repeat the `images` field once per file. 10 MB each.
          minItems: 1
          maxItems: 30
          items:
            type: string
            format: binary
    ViewerHotspotsParams:
      type: object
      required:
        - parts
      properties:
        parts:
          type: array
          description: The parts to anchor. One group per part comes back in the result.
          minItems: 1
          maxItems: 20
          items:
            $ref: '#/components/schemas/PartCode'
        min_confidence:
          type: number
          minimum: 0
          maximum: 1
          default: 0.3
          description: >-
            Below this, an image is reported as not showing the part rather than
            guessed at.
    JsonMultiInput:
      type: object
      required:
        - input_refs
      properties:
        input_refs:
          type: array
          minItems: 1
          maxItems: 30
          items:
            $ref: '#/components/schemas/UploadRef'
    ViewerHotspotsJob:
      allOf:
        - $ref: '#/components/schemas/Job'
        - type: object
          properties:
            result:
              oneOf:
                - $ref: '#/components/schemas/ViewerHotspotsResult'
                - type: 'null'
    PartCode:
      type: string
      description: >
        Stable identifier of a vehicle part, shared by every endpoint that names
        one. Snake case,

        side-suffixed where a vehicle has two (`door_front_left`). The list
        grows with the part

        taxonomy, so treat an unknown code as opaque rather than rejecting it.
      examples:
        - front_bumper
        - rear_bumper
        - bonnet
        - tailgate
        - roof
        - windscreen
        - grille
        - door_front_left
        - door_rear_right
        - wing_front_left
        - quarter_panel_rear_right
        - headlight_left
        - taillight_right
        - mirror_left
        - wheel_front_left
        - sill_left
        - dashboard
        - seat_front_left
    UploadRef:
      type: string
      description: Reference returned by `POST /v1/uploads`.
      pattern: ^upl_[1-9A-HJ-NP-Za-km-z]{8,32}$
      examples:
        - upl_7Qm2hV9tXbLd
    Job:
      type: object
      title: Job
      description: >
        The unit of work of the API. Every inference call creates one, and every
        result is read

        back from one — synchronously through `wait=true`, or later through `GET
        /v1/jobs/{id}`

        or a webhook.


        `result` is populated only in `succeeded`, `error` only in `failed` /
        `expired`.

        `stages` is present on inspection jobs only.
      required:
        - id
        - object
        - endpoint
        - status
        - livemode
        - created
        - started_at
        - completed_at
        - model_version
        - credits
        - result
        - error
      properties:
        id:
          $ref: '#/components/schemas/JobIdString'
        object:
          type: string
          const: job
        endpoint:
          $ref: '#/components/schemas/EndpointSlug'
        status:
          $ref: '#/components/schemas/JobStatus'
        livemode:
          type: boolean
          description: >-
            `false` when the job was created with a `chk_test_` key: fixture
            result, test credits.
        created:
          $ref: '#/components/schemas/Timestamp'
        started_at:
          description: When the job left the queue. Null while `queued`.
          oneOf:
            - $ref: '#/components/schemas/Timestamp'
            - type: 'null'
        completed_at:
          description: When the job settled. Null until then.
          oneOf:
            - $ref: '#/components/schemas/Timestamp'
            - type: 'null'
        model_version:
          type:
            - string
            - 'null'
          description: >-
            Exact model build that produced the result, as `<model>@<release
            date>`. Null until the job starts. Pin your regression tests to it.
          examples:
            - plate-reader@2026-08-01
        credits:
          $ref: '#/components/schemas/JobCredits'
        result:
          description: >-
            Endpoint-specific payload, null until the job succeeds. See each
            endpoint's `200` / `202` schema for its exact shape.
          oneOf:
            - type: object
            - type: 'null'
        error:
          description: Populated when `status` is `failed` or `expired`, null otherwise.
          oneOf:
            - $ref: '#/components/schemas/JobError'
            - type: 'null'
        stages:
          type: array
          description: >-
            Per-stage progress. Present on `inspections` jobs only, absent
            everywhere else.
          items:
            $ref: '#/components/schemas/JobStage'
        test_scenario:
          type: string
          description: >-
            The `X-Carhub-Test-Scenario` that produced this job. Present on
            test-mode jobs that were steered by one, absent otherwise — a live
            job never carries it.
          examples:
            - low_confidence
    ViewerHotspotsResult:
      type: object
      title: viewer.hotspots result
      description: >-
        Geometry, not a rendered view: for each requested part, where on each
        image the marker belongs. Images that do not show the part come back
        with `visible: false` and a null point rather than with a guess.
      required:
        - image_count
        - hotspots
      properties:
        image_count:
          type: integer
          minimum: 0
          description: Images submitted — the billable unit count of the call.
        hotspots:
          type: array
          description: One group per requested part, in the order they were requested.
          items:
            $ref: '#/components/schemas/HotspotGroup'
    ErrorResponse:
      type: object
      title: Error response
      description: >-
        The single error envelope of the API. Every non-2xx response on every
        route group has exactly this shape.
      required:
        - error
      additionalProperties: false
      properties:
        error:
          $ref: '#/components/schemas/Error'
    JobIdString:
      type: string
      description: Job identifier — `job_` followed by a base58 string.
      pattern: ^job_[1-9A-HJ-NP-Za-km-z]{8,32}$
      examples:
        - job_3ZxK9pQr7TnW
    EndpointSlug:
      type: string
      description: >-
        Dotted slug of a CarHub endpoint, the identifier used for jobs and
        usage.
      enum:
        - vehicle.analyze
        - vehicle.segment
        - vehicle.identify
        - vin.read
        - plate.read
        - document.read
        - dashboard.read
        - engine.detect
        - video.onboard
        - damage.detect
        - damage.severity
        - tire.read
        - tire.detect
        - parts.recognize
        - parts.segment
        - parts.decompose
        - pricing.vehicle
        - pricing.repair
        - render.background
        - render.enhance
        - render.plate
        - render.360
        - render.video
        - viewer.hotspots
        - inspections
    JobStatus:
      type: string
      description: >
        Lifecycle of a job. `queued` → `processing` → one of `succeeded` |
        `failed`; `expired`

        when it was never picked up in time, `canceled` when it was cancelled
        before starting.

        The three settled states are terminal.
      enum:
        - queued
        - processing
        - succeeded
        - failed
        - expired
        - canceled
    Timestamp:
      type: integer
      format: int64
      description: Epoch seconds, UTC.
      examples:
        - 1755300000
    JobCredits:
      type: object
      description: >
        Credit accounting for the job. One credit equals one USD cent.
        `reserved` is held at

        admission and `charged` is the real cost after settlement; unused
        credits are released.
      required:
        - reserved
        - charged
      properties:
        reserved:
          type: integer
          minimum: 0
          examples:
            - 20
        charged:
          type:
            - integer
            - 'null'
          minimum: 0
          description: Null while the job is unsettled.
          examples:
            - 20
    JobError:
      type: object
      description: Why a job settled in `failed` or `expired`.
      required:
        - type
        - code
        - message
      properties:
        type:
          type: string
          enum:
            - processing_error
            - invalid_request
            - internal
        code:
          type: string
          examples:
            - model_inference_failed
            - unreadable_input
            - upstream_timeout
            - job_expired
        message:
          type: string
    JobStage:
      type: object
      title: Inspection stage
      description: >
        One of the eight stages of an inspection. Each stage is a real child job
        running on the

        same queue and the same workers as a direct call, so `job_id` is
        readable on its own

        through `GET /v1/jobs/{id}` and carries that stage's full,
        endpoint-specific result.
      required:
        - stage
        - endpoint
        - name
        - status
        - started_at
        - completed_at
        - job_id
        - error
      properties:
        stage:
          type: integer
          minimum: 1
          maximum: 8
          description: Position in the pipeline, 1-based.
        endpoint:
          allOf:
            - $ref: '#/components/schemas/EndpointSlug'
          description: >-
            The endpoint this stage runs — the same one you could have called
            directly.
        name:
          type: string
          description: >-
            Stable name of the stage, independent of which endpoint currently
            implements it — branch on this rather than on `endpoint` if you want
            your UI to survive a pipeline change.
          enum:
            - onboard_walkaround
            - isolate_vehicle
            - decompose_parts
            - detect_damage
            - grade_severity
            - price_repair
            - render_listing
            - anchor_hotspots
        model:
          type: string
          description: Model backing this stage.
          examples:
            - video-onboarding
            - car-segmentation
            - damage-detection
        status:
          $ref: '#/components/schemas/JobStatus'
        started_at:
          oneOf:
            - $ref: '#/components/schemas/Timestamp'
            - type: 'null'
        completed_at:
          oneOf:
            - $ref: '#/components/schemas/Timestamp'
            - type: 'null'
        job_id:
          description: Child job that ran the stage. Null before the stage starts.
          oneOf:
            - $ref: '#/components/schemas/JobIdString'
            - type: 'null'
        error:
          oneOf:
            - $ref: '#/components/schemas/JobError'
            - type: 'null'
        result:
          description: >-
            The stage's own result, inlined once it has succeeded. Same shape as
            the result of the endpoint named in `endpoint`. Absent while the
            stage is unsettled.
          type: object
        skipped:
          type: boolean
          description: >-
            True when the stage was excluded by the request's `stages`
            parameter.
          default: false
    HotspotGroup:
      type: object
      required:
        - part
        - found_in
        - images
      properties:
        part:
          $ref: '#/components/schemas/PartRef'
        found_in:
          type: integer
          minimum: 0
          description: Number of images in which the part was located.
        images:
          type: array
          description: One entry per submitted image, in submission order.
          items:
            $ref: '#/components/schemas/HotspotAnchor'
    Error:
      type: object
      required:
        - type
        - code
        - message
        - param
        - doc_url
        - request_id
      properties:
        type:
          type: string
          description: >-
            Broad class of the failure — enough to branch on without matching
            codes.
          enum:
            - invalid_request
            - authentication
            - permission
            - rate_limit
            - insufficient_credits
            - not_found
            - conflict
            - processing_error
            - internal
        code:
          type: string
          description: >-
            Stable machine-readable identifier, narrower than `type`. New codes
            may appear; treat an unknown code as its `type`.
          examples:
            - missing_input
            - invalid_api_key
            - rate_limit_exceeded
        message:
          type: string
          description: >-
            English, human-readable, safe to log. Never localised, never for end
            users.
        param:
          type:
            - string
            - 'null'
          description: >-
            The offending field or header, when the failure can be attributed to
            one.
        doc_url:
          type: string
          format: uri
          description: Documentation page for this exact code.
          examples:
            - https://docs.trycarhub.com/errors/missing_input
        request_id:
          type: string
          description: >-
            Identifier of the failing request, mirrored in the `Request-Id`
            header.
          examples:
            - req_2Kd8xQmPvL4T
    PartRef:
      type: object
      description: A part, by code and display label.
      required:
        - code
        - label
      properties:
        code:
          $ref: '#/components/schemas/PartCode'
        label:
          type: string
          description: English display name.
          examples:
            - Front bumper
    HotspotAnchor:
      type: object
      required:
        - index
        - visible
        - point
        - bbox
        - confidence
      properties:
        index:
          type: integer
          minimum: 0
          description: Zero-based index of the submitted image.
        visible:
          type: boolean
        point:
          description: >-
            Where to anchor the marker, in the pixel coordinates of that image.
            Null when the part is not visible.
          oneOf:
            - $ref: '#/components/schemas/Point'
            - type: 'null'
        bbox:
          description: >-
            Extent of the part in that image, for a marker that needs to size
            itself.
          oneOf:
            - $ref: '#/components/schemas/BBox'
            - type: 'null'
        confidence:
          oneOf:
            - $ref: '#/components/schemas/Confidence'
            - type: 'null'
    Point:
      type: object
      title: Point
      description: A pixel coordinate in the submitted image.
      required:
        - x
        - 'y'
      properties:
        x:
          type: integer
        'y':
          type: integer
    BBox:
      type: object
      title: Bounding box
      description: >-
        Axis-aligned box in pixels of the submitted image, origin at the
        top-left corner.
      required:
        - x
        - 'y'
        - width
        - height
      properties:
        x:
          type: integer
          description: Left edge, in pixels.
        'y':
          type: integer
          description: Top edge, in pixels.
        width:
          type: integer
          minimum: 0
          description: Width, in pixels.
        height:
          type: integer
          minimum: 0
          description: Height, in pixels.
      examples:
        - x: 120
          'y': 340
          width: 180
          height: 48
    Confidence:
      type: number
      description: Model confidence, 0 to 1. Values below 0.5 warrant a human look.
      minimum: 0
      maximum: 1
      examples:
        - 0.94
  headers:
    RequestId:
      description: Unique identifier of this request. Quote it in any support conversation.
      schema:
        type: string
        examples:
          - req_2Kd8xQmPvL4T
    IdempotentReplayed:
      description: >-
        `true` when this response was replayed from a previous request carrying
        the same `Idempotency-Key`. Absent when the request was executed for
        real.
      schema:
        type: boolean
    JobLocation:
      description: Absolute URL of the created job.
      schema:
        type: string
        format: uri
        examples:
          - https://api.trycarhub.com/v1/jobs/job_3ZxK9pQr7TnW
    RetryAfter:
      description: Seconds to wait before retrying.
      schema:
        type: integer
        minimum: 0
        examples:
          - 12
    RateLimitLimit:
      description: Requests allowed in the current window for this endpoint and plan.
      schema:
        type: integer
        examples:
          - 120
    RateLimitRemaining:
      description: Requests left in the current window.
      schema:
        type: integer
        examples:
          - 0
    RateLimitReset:
      description: Epoch second at which the current window resets.
      schema:
        type: integer
        examples:
          - 1755300060
  responses:
    BadRequest:
      description: >
        Malformed or unusable request — bad JSON, missing or oversized file,
        unknown parameter,

        `wait=true` on an endpoint that does not support it.
      headers:
        Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            missingInput:
              summary: No input supplied
              value:
                error:
                  type: invalid_request
                  code: missing_input
                  message: Provide either a multipart `image` field or an `input_ref`.
                  param: image
                  doc_url: https://docs.trycarhub.com/errors/missing_input
                  request_id: req_2Kd8xQmPvL4T
            waitUnsupported:
              summary: wait=true on a long-running endpoint
              value:
                error:
                  type: invalid_request
                  code: wait_not_supported
                  message: >-
                    This endpoint is long-running; poll the job or use a webhook
                    instead.
                  param: wait
                  doc_url: https://docs.trycarhub.com/errors/wait_not_supported
                  request_id: req_2Kd8xQmPvL4T
    Unauthorized:
      description: Missing, malformed, revoked or expired credential.
      headers:
        Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            invalidKey:
              value:
                error:
                  type: authentication
                  code: invalid_api_key
                  message: The provided API key is invalid or has been revoked.
                  param: null
                  doc_url: https://docs.trycarhub.com/errors/invalid_api_key
                  request_id: req_2Kd8xQmPvL4T
    InsufficientCredits:
      description: >
        The organisation's balance cannot cover the reservation for this call.
        Nothing was

        queued and nothing was charged. Top up with `POST
        /mgmt/v1/billing/checkout` — or, in

        test mode, `POST /mgmt/v1/billing/test-credits`.
      headers:
        Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            emptyCreditBalances:
              value:
                error:
                  type: insufficient_credits
                  code: insufficient_credits
                  message: Credit balance is 3 minor units; this call reserves 8.
                  param: null
                  doc_url: https://docs.trycarhub.com/errors/insufficient_credits
                  request_id: req_2Kd8xQmPvL4T
    Forbidden:
      description: >-
        Authenticated but not allowed: key scope excludes this endpoint family,
        role too low for a management route, or the resource belongs to another
        organisation.
      headers:
        Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            outOfScope:
              value:
                error:
                  type: permission
                  code: scope_not_granted
                  message: This API key is not scoped for the `render` endpoint family.
                  param: null
                  doc_url: https://docs.trycarhub.com/errors/scope_not_granted
                  request_id: req_2Kd8xQmPvL4T
    NotFound:
      description: No such resource, or it is not visible to this credential.
      headers:
        Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            unknownJob:
              value:
                error:
                  type: not_found
                  code: job_not_found
                  message: No job with id `job_3ZxK9pQr7TnW`.
                  param: id
                  doc_url: https://docs.trycarhub.com/errors/job_not_found
                  request_id: req_2Kd8xQmPvL4T
    Conflict:
      description: >-
        The request contradicts the current state — an idempotency key replayed
        with a different body, or an upload reference already consumed.
      headers:
        Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            idempotencyReuse:
              value:
                error:
                  type: conflict
                  code: idempotency_key_reuse
                  message: >-
                    This Idempotency-Key was already used with a different
                    request body.
                  param: Idempotency-Key
                  doc_url: https://docs.trycarhub.com/errors/idempotency_key_reuse
                  request_id: req_2Kd8xQmPvL4T
    RateLimited:
      description: Too many requests for this endpoint and plan. Back off and retry.
      headers:
        Request-Id:
          $ref: '#/components/headers/RequestId'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            tooFast:
              value:
                error:
                  type: rate_limit
                  code: rate_limit_exceeded
                  message: >-
                    Rate limit of 120 requests per minute exceeded for
                    `plate.read`.
                  param: null
                  doc_url: https://docs.trycarhub.com/errors/rate_limit_exceeded
                  request_id: req_2Kd8xQmPvL4T
    InternalError:
      description: >-
        Something broke on our side. The request was not billed. Retry with the
        same `Idempotency-Key`; if it persists, quote `request_id`.
      headers:
        Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            internal:
              value:
                error:
                  type: internal
                  code: internal_error
                  message: An unexpected error occurred.
                  param: null
                  doc_url: https://docs.trycarhub.com/errors/internal_error
                  request_id: req_2Kd8xQmPvL4T
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      bearerFormat: CarHub API key
      description: >
        Organisation API key, sent as `Authorization: Bearer chk_live_…`.


        * `chk_live_…` — real inference, real credits, `livemode: true`.

        * `chk_test_…` — identical pipeline against the fixture backend, test
        credits,
          `livemode: false`.

        Keys are stored hashed; only the prefix (`chk_live_a1b2…`) is ever
        displayed again. A key

        may be scoped to a subset of endpoint families — calling outside its
        scopes is a `403`.

````