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

# Full vehicle inspection (bundle)

> Chains the full pipeline in one call and bills as a single blended inspection rather than
as separate metered calls.

| # | Stage | Model |
|---|---|---|
| 1 | Onboard walkaround | `video-onboarding` |
| 2 | Isolate the vehicle | `car-segmentation` |
| 3 | Decompose parts | `parts-decomposition` |
| 4 | Detect damage | `damage-detection` |
| 5 | Grade severity | `severity-estimation` |
| 6 | Price the repair | `reparation-pricing` |
| 7 | Render for listing | `image-backgrounding` |
| 8 | Anchor hotspots | `viewer-hotspoter` |

The returned job carries a `stages[]` array — the only endpoint that does — so progress is
observable long before the aggregated `result` exists. Each stage also emits an
`inspection.stage_completed` webhook.

Minutes-long by construction: `wait=true` is rejected.




## OpenAPI

````yaml /openapi.yaml post /v1/inspections
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/inspections:
    post:
      tags:
        - Inspections
      summary: Full vehicle inspection (bundle)
      description: >
        Chains the full pipeline in one call and bills as a single blended
        inspection rather than

        as separate metered calls.


        | # | Stage | Model |

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

        | 1 | Onboard walkaround | `video-onboarding` |

        | 2 | Isolate the vehicle | `car-segmentation` |

        | 3 | Decompose parts | `parts-decomposition` |

        | 4 | Detect damage | `damage-detection` |

        | 5 | Grade severity | `severity-estimation` |

        | 6 | Price the repair | `reparation-pricing` |

        | 7 | Render for listing | `image-backgrounding` |

        | 8 | Anchor hotspots | `viewer-hotspoter` |


        The returned job carries a `stages[]` array — the only endpoint that
        does — so progress is

        observable long before the aggregated `result` exists. Each stage also
        emits an

        `inspection.stage_completed` webhook.


        Minutes-long by construction: `wait=true` is rejected.
      operationId: createInspection
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
        - $ref: '#/components/parameters/TestScenario'
      requestBody:
        required: true
        description: >-
          Supply either a walkaround `video` (stage 1 indexes it into frames) or
          a set of `images` — at least one of the two is required.
        content:
          multipart/form-data:
            schema:
              allOf:
                - $ref: '#/components/schemas/MultipartInspectionInput'
                - $ref: '#/components/schemas/InspectionParams'
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/JsonInspectionInput'
                - $ref: '#/components/schemas/InspectionParams'
            examples:
              walkaround:
                summary: Walkaround video, French workshop rate
                value:
                  input_ref: upl_9KdP2sVzQm4T
                  currency: EUR
                  country: FR
                  labour_rate: 6500
                  background: studio_white
                  hotspot_parts:
                    - bonnet
                    - front_bumper
      responses:
        '202':
          description: Inspection accepted; the eight stages are 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/InspectionJob'
        '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:
    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:
    MultipartInspectionInput:
      type: object
      description: Supply a walkaround `video`, a set of `images`, or both.
      properties:
        video:
          type: string
          format: binary
        images:
          type: array
          minItems: 0
          maxItems: 30
          items:
            type: string
            format: binary
    InspectionParams:
      type: object
      properties:
        stages:
          type: array
          description: >-
            Restrict the pipeline to these stages. Omitted, all eight run.
            Excluded stages are reported as `skipped` and are not billed.
          items:
            type: string
            enum:
              - onboard_walkaround
              - isolate_vehicle
              - decompose_parts
              - detect_damage
              - grade_severity
              - price_repair
              - render_listing
              - anchor_hotspots
        vehicle:
          oneOf:
            - $ref: '#/components/schemas/VehicleHint'
            - type: 'null'
        currency:
          type: string
          pattern: ^[A-Z]{3}$
        country:
          type: string
          pattern: ^[A-Z]{2}$
        labour_rate:
          type: integer
          minimum: 0
          description: Workshop hourly rate in minor units, for the repair pricing stage.
        tax_rate_pct:
          type: number
          minimum: 0
        background:
          type: string
          description: Background preset for the listing renders of stage 7.
          enum:
            - studio_white
            - studio_grey
            - studio_gradient
            - showroom
            - outdoor
            - transparent
            - blur
          default: studio_white
        hotspot_parts:
          type: array
          description: Parts to anchor in stage 8.
          items:
            $ref: '#/components/schemas/PartCode'
    JsonInspectionInput:
      type: object
      description: >-
        `input_ref` is the walkaround video, `input_refs` a set of stills. At
        least one of the two is required.
      anyOf:
        - required:
            - input_ref
        - required:
            - input_refs
      properties:
        input_ref:
          $ref: '#/components/schemas/UploadRef'
        input_refs:
          type: array
          minItems: 1
          maxItems: 30
          items:
            $ref: '#/components/schemas/UploadRef'
    InspectionJob:
      allOf:
        - $ref: '#/components/schemas/Job'
        - type: object
          description: >-
            An inspection job. Unlike every other job it carries `stages`, and
            its `result` only materialises when the eighth stage settles.
          required:
            - stages
          properties:
            result:
              oneOf:
                - $ref: '#/components/schemas/InspectionResult'
                - type: 'null'
    VehicleHint:
      type: object
      description: What you already know about the vehicle. Every field is optional.
      properties:
        make:
          type: string
        model:
          type: string
        version:
          type: string
        year:
          type: integer
        mileage_km:
          type: integer
          minimum: 0
        fuel:
          type: string
          enum:
            - petrol
            - diesel
            - hybrid
            - plug_in_hybrid
            - electric
            - lpg
            - other
        gearbox:
          type: string
          enum:
            - manual
            - automatic
        vin:
          type: string
        plate:
          type: string
    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
    InspectionResult:
      type: object
      title: inspections result
      description: >-
        The aggregated outcome of the eight stages: what was captured, what the
        vehicle is made of, what is wrong with it, what fixing it costs, and the
        assets to publish it with. Each stage's raw output stays readable on its
        own child job.
      required:
        - coverage
        - condition
        - parts
        - damages
        - repair_estimate
        - listing_images
        - hotspots
      properties:
        coverage:
          allOf:
            - $ref: '#/components/schemas/InspectionCoverage'
          description: What stage 1 managed to capture.
        condition:
          $ref: '#/components/schemas/InspectionCondition'
        parts:
          type: array
          description: >-
            Parts identified on the vehicle, flagged when a damage was
            attributed to them.
          items:
            $ref: '#/components/schemas/InspectedPart'
        damages:
          type: array
          description: Damages found and graded, worst first.
          items:
            $ref: '#/components/schemas/InspectedDamage'
        repair_estimate:
          $ref: '#/components/schemas/InspectionEstimate'
        listing_images:
          type: array
          description: Publication-ready renders produced by stage 7.
          items:
            $ref: '#/components/schemas/RenderedAsset'
        hotspots:
          type: array
          description: Markers anchored by stage 8, ready to overlay on `listing_images`.
          items:
            $ref: '#/components/schemas/InspectionHotspot'
    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
    InspectionCoverage:
      type: object
      required:
        - captured_steps
        - total_steps
        - missing_steps
      properties:
        captured_steps:
          type: integer
          minimum: 0
        total_steps:
          type: integer
          minimum: 0
        missing_steps:
          type: array
          items:
            type: string
    InspectionCondition:
      type: object
      required:
        - overall_grade
        - damage_count
        - severity_breakdown
      properties:
        overall_grade:
          type:
            - string
            - 'null'
          description: A (as new) to E (trade only), derived from the graded damages.
          enum:
            - A
            - B
            - C
            - D
            - E
            - null
        damage_count:
          type: integer
          minimum: 0
        severity_breakdown:
          $ref: '#/components/schemas/SeverityBreakdown'
    InspectedPart:
      type: object
      required:
        - code
        - label
        - damaged
      properties:
        code:
          $ref: '#/components/schemas/PartCode'
        label:
          type: string
        damaged:
          type: boolean
    InspectedDamage:
      type: object
      required:
        - id
        - part
        - type
        - severity
        - recommended_action
        - image_index
        - bbox
        - confidence
      properties:
        id:
          type: string
          examples:
            - dmg_1
        part:
          oneOf:
            - $ref: '#/components/schemas/PartRef'
            - type: 'null'
        type:
          type: string
          examples:
            - dent
        severity:
          type: string
          enum:
            - minor
            - moderate
            - severe
        recommended_action:
          type: string
          enum:
            - none
            - monitor
            - polish
            - smart_repair
            - repair
            - replace
        image_index:
          type:
            - integer
            - 'null'
          minimum: 0
          description: >-
            Which frame the damage was seen on. Null for damages merged across
            frames.
        bbox:
          oneOf:
            - $ref: '#/components/schemas/BBox'
            - type: 'null'
        confidence:
          $ref: '#/components/schemas/Confidence'
    InspectionEstimate:
      type: object
      description: >-
        Condensed repair estimate. The full line-by-line version is on the stage
        6 job.
      required:
        - currency
        - parts_total
        - labour_total
        - paint_total
        - total
        - labour_hours
        - line_count
      properties:
        currency:
          type: string
          pattern: ^[A-Z]{3}$
        parts_total:
          $ref: '#/components/schemas/Money'
        labour_total:
          $ref: '#/components/schemas/Money'
        paint_total:
          $ref: '#/components/schemas/Money'
        total:
          $ref: '#/components/schemas/Money'
        labour_hours:
          type: number
          minimum: 0
        line_count:
          type: integer
          minimum: 0
    RenderedAsset:
      type: object
      title: Rendered asset
      description: >-
        A file produced by a generation endpoint, served from a signed URL that
        expires. Download and re-host anything you intend to keep — the object
        is also subject to your organisation's storage TTL.
      required:
        - url
        - expires_at
        - format
      properties:
        url:
          type: string
          format: uri
        expires_at:
          $ref: '#/components/schemas/Timestamp'
        format:
          type: string
          enum:
            - jpg
            - png
            - webp
            - mp4
            - webm
        width:
          type:
            - integer
            - 'null'
          minimum: 1
        height:
          type:
            - integer
            - 'null'
          minimum: 1
        bytes:
          type:
            - integer
            - 'null'
          minimum: 0
    InspectionHotspot:
      type: object
      required:
        - part
        - image_index
        - point
        - confidence
      properties:
        part:
          $ref: '#/components/schemas/PartRef'
        image_index:
          type: integer
          minimum: 0
        point:
          $ref: '#/components/schemas/Point'
        confidence:
          $ref: '#/components/schemas/Confidence'
    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
    SeverityBreakdown:
      type: object
      required:
        - minor
        - moderate
        - severe
      properties:
        minor:
          type: integer
          minimum: 0
        moderate:
          type: integer
          minimum: 0
        severe:
          type: integer
          minimum: 0
    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
    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
    Money:
      type: object
      title: Money
      description: >-
        An amount in the **minor unit** of its currency (cents for EUR), as a
        signed integer. Never a float: `149900` EUR is €1 499,00 and an
        adjustment may be negative.
      required:
        - amount
        - currency
      properties:
        amount:
          type: integer
          examples:
            - 149900
        currency:
          type: string
          description: ISO 4217, uppercase.
          pattern: ^[A-Z]{3}$
          examples:
            - EUR
    Point:
      type: object
      title: Point
      description: A pixel coordinate in the submitted image.
      required:
        - x
        - 'y'
      properties:
        x:
          type: integer
        'y':
          type: integer
  headers:
    RequestId:
      description: Unique identifier of this request. Quote it in any support conversation.
      schema:
        type: string
        examples:
          - req_2Kd8xQmPvL4T
    JobLocation:
      description: Absolute URL of the created job.
      schema:
        type: string
        format: uri
        examples:
          - https://api.trycarhub.com/v1/jobs/job_3ZxK9pQr7TnW
    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
    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`.

````