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

# Upload a permanent organization asset

> Requires `assets:write`. Upload a file up to 25 MiB. PNG, JPEG, and WebP are accepted
for `logo`, `license_plate`, and `banner`; `misc` accepts any file type and is
downloaded as an attachment. Send `category`, or the legacy `kind` alias. When
both are present, they must match. Banners require `banner_placement`.

For `background`, send the complete Studio multipart package: `image`,
`non_floor_rgba`, `floor_rgba`, `to_ignore_mask`, generated `yaml`, `interior_id`,
and optional JSON-encoded `brands`. All package files count toward storage.
Existing background routes and `custom--<ID>` selectors remain supported.

Space is reserved before writing to the dedicated private assets bucket. Assets
have no automatic expiration and share the existing organization storage quota
with inputs. Partial uploads remain charged until their removal is confirmed.




## OpenAPI

````yaml /openapi.yaml post /v1/assets
openapi: 3.1.0
info:
  title: CarHub API
  version: '2026-10-04'
  summary: >-
    Automotive AI as a service — 23 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_stg_…` / `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 `job.succeeded`, `job.partial`, and
    `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: Jobs
    description: Status, results and history of asynchronous work.
  - name: Uploads
    description: Presigned storage for videos and batches.
  - name: Assets
    description: >-
      Permanent organization artwork and custom background packages in private
      storage.
  - 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/assets:
    post:
      tags:
        - Assets
      summary: Upload a permanent organization asset
      description: >
        Requires `assets:write`. Upload a file up to 25 MiB. PNG, JPEG, and WebP
        are accepted

        for `logo`, `license_plate`, and `banner`; `misc` accepts any file type
        and is

        downloaded as an attachment. Send `category`, or the legacy `kind`
        alias. When

        both are present, they must match. Banners require `banner_placement`.


        For `background`, send the complete Studio multipart package: `image`,

        `non_floor_rgba`, `floor_rgba`, `to_ignore_mask`, generated `yaml`,
        `interior_id`,

        and optional JSON-encoded `brands`. All package files count toward
        storage.

        Existing background routes and `custom--<ID>` selectors remain
        supported.


        Space is reserved before writing to the dedicated private assets bucket.
        Assets

        have no automatic expiration and share the existing organization storage
        quota

        with inputs. Partial uploads remain charged until their removal is
        confirmed.
      operationId: createAsset
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              oneOf:
                - $ref: '#/components/schemas/FlatAssetUpload'
                - $ref: '#/components/schemas/BackgroundAssetUpload'
      responses:
        '201':
          description: Asset uploaded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Asset'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          $ref: '#/components/responses/Conflict'
        '500':
          $ref: '#/components/responses/InternalError'
components:
  schemas:
    FlatAssetUpload:
      type: object
      required:
        - name
        - file
      anyOf:
        - required:
            - category
        - required:
            - kind
      properties:
        category:
          type: string
          enum:
            - license_plate
            - banner
            - logo
            - misc
        kind:
          type: string
          enum:
            - license_plate
            - banner
            - logo
            - misc
          deprecated: true
          description: Legacy alias. If both selectors are sent, they must match.
        name:
          type: string
          minLength: 1
          maxLength: 200
        file:
          type: string
          format: binary
          description: >-
            File up to 25 MiB. Images must be PNG, JPEG, or WebP; misc accepts
            any type.
        banner_placement:
          type: string
          enum:
            - top
            - bottom
          description: Required for category banner.
    BackgroundAssetUpload:
      type: object
      required:
        - name
        - interior_id
        - yaml
        - image
        - non_floor_rgba
        - floor_rgba
        - to_ignore_mask
      anyOf:
        - required:
            - category
        - required:
            - kind
      properties:
        category:
          type: string
          const: background
        kind:
          type: string
          const: background
          deprecated: true
        name:
          type: string
          minLength: 1
          maxLength: 200
        interior_id:
          type: integer
          minimum: 1
        brands:
          type: string
          description: JSON-encoded array of brand names.
          examples:
            - '["BMW"]'
        yaml:
          type: string
          description: Generated Studio background YAML.
        image:
          type: string
          format: binary
        non_floor_rgba:
          type: string
          format: binary
        floor_rgba:
          type: string
          format: binary
        to_ignore_mask:
          type: string
          format: binary
    Asset:
      type: object
      description: >-
        Permanent organization asset. Scene size includes all image layers and
        YAML. Only available assets have signed links and can be selected for
        new jobs.
      required:
        - id
        - object
        - category
        - kind
        - name
        - content_type
        - size_bytes
        - total_size_bytes
        - state
        - created
      properties:
        id:
          type: string
          format: uuid
          description: Asset UUID, including for custom background scenes.
        object:
          type: string
          const: asset
        category:
          $ref: '#/components/schemas/AssetCategory'
        kind:
          allOf:
            - $ref: '#/components/schemas/AssetCategory'
          deprecated: true
          description: Legacy alias of category, retained for existing clients.
        name:
          type: string
        content_type:
          type: string
        size_bytes:
          type: integer
          minimum: 0
          description: Total bytes in the asset package.
        total_size_bytes:
          type: integer
          minimum: 0
          description: Total bytes including all scene layers and YAML.
        state:
          $ref: '#/components/schemas/AssetState'
        created:
          $ref: '#/components/schemas/Timestamp'
        banner_placement:
          type:
            - string
            - 'null'
          enum:
            - top
            - bottom
            - null
        background_id:
          type:
            - integer
            - 'null'
          description: Historical numeric scene ID used in custom--<ID> selectors.
        link:
          type: string
          description: >-
            Short-lived signed preview URL. Empty or omitted for unavailable
            assets; misc files use attachment downloads.
        download_url:
          type: string
          description: >-
            Short-lived signed download URL. Uses attachment Content-Disposition
            for misc files. Empty or omitted when unavailable.
        files:
          type: array
          description: >-
            Background package files returned by GET for an available scene.
            Includes all layers and YAML with individual signed download URLs.
            Omitted when unavailable.
          items:
            type: object
            required:
              - path
              - size_bytes
              - download_url
            properties:
              path:
                type: string
                description: Relative path within the background package.
              size_bytes:
                type: integer
                minimum: 0
              download_url:
                type: string
                format: uri
    AssetCategory:
      type: string
      enum:
        - background
        - license_plate
        - banner
        - logo
        - misc
    AssetState:
      type: string
      enum:
        - available
        - uploading
        - deleting
        - delete_failed
        - deleted
    Timestamp:
      type: integer
      format: int64
      description: Epoch seconds, UTC.
      examples:
        - 1755300000
    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'
    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
        quota_bytes:
          type: integer
          description: Organisation storage limit for `storage_quota_exceeded`.
        stored_bytes:
          type: integer
        reserved_bytes:
          type: integer
        available_bytes:
          type: integer
        requested_bytes:
          type: integer
        oldest_deletable_ref:
          type: string
        oldest_deletable_at:
          type: string
          format: date-time
  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
    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
    Conflict:
      description: >-
        The request contradicts the current state — an idempotency key replayed
        with a different body, or an input storage reservation that exceeds the
        organisation quota.
      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
    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
  headers:
    RequestId:
      description: Unique identifier of this request. Quote it in any support conversation.
      schema:
        type: string
        examples:
          - req_2Kd8xQmPvL4T
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      bearerFormat: CarHub API key
      description: >
        Organisation API key, sent as `Authorization: Bearer chk_live_…`.


        * `chk_live_…` — production inference, live credits, `livemode: true`,
        `is_staging: false`.

        * `chk_stg_…` — real inference classified as staging, the same live
        credits, `livemode: true`, `is_staging: 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`.

````

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