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

# Describe an image

> Resolves a tag or a digest to one image, including its raw manifest
document.

The `manifest` field is the parsed JSON, for convenience. It is NOT
suitable for verifying the digest: the digest covers the exact bytes
the client pushed, and a round-trip through a JSON encoder reorders
keys. Fetch the manifest from `/v2` if you need to re-hash it.


<Info>
  Requires the IAM action **`registry:BatchGetImage`**. See [REGISTRY permissions](/registry/permissions) for the full list, what each one covers, and an example policy.
</Info>


## OpenAPI

````yaml /api-reference/specs/registry.yaml get /v1/repositories/{repository_id}/images/{image_reference}
openapi: 3.0.3
info:
  title: Basaltic Registry API
  version: 1.0.0
  description: >
    KMS bindings use `kms_key`: a UUID, account-scoped name, or complete

    `crn:kms:<region>:<account>:key/<name>` in the authenticated account and
    current region. The key must be enabled,

    symmetric, and usable for encryption. Bindings retain the key's immutable

    identity; responses render a ResourceRef as `kms_key`. Invalid or
    foreign-scope CRNs are

    rejected with HTTP 400; missing keys return HTTP 404 on binding or

    cryptographic use. Metadata reads remain available for deleted bindings,

    with a nameless `kms_key` reference retaining the immutable key identity.

    Private container image registries.


    **This spec documents the control plane, not the pull path.** The

    management API uses `registry.<region>.basaltic.sh`; Docker and OCI

    clients use `cr.<region>.basaltic.cloud`. Everything below is the

    `/v1` REST surface — repositories, images, lifecycle policies,

    resource policies — authenticated with a platform OAuth 2.0 bearer token

    or a console session, like every other Basaltic API. Separately, `/v2`

    implements the [OCI Distribution

    Specification](https://github.com/opencontainers/distribution-spec)

    v1.1, which is what `docker`, `podman`, `buildah`, `crane`, `skopeo`,

    and every Kubernetes node's container runtime speak. That surface is

    not described here because it is not ours to describe: its paths,

    status codes, headers, and error codes are fixed by the spec, and a

    client that finds anything else fails.


    **Getting a docker credential.** `POST /v1/authorization-token`

    returns a short-lived password. Feed it to `docker login`; the daemon

    then exchanges it at `/v2/token` for per-repository bearer tokens on

    its own, which is where the actual authorization decision is made.

    The login credential carries no authority of its own — revoking a

    policy takes effect within one bearer token's lifetime (five minutes)

    rather than at the end of a twelve-hour login.


    **The account handle is part of the image reference.** A region

    serves one registry host, so an image lives at

    `cr.<region>.basaltic.cloud/<account_handle>/<repository>:<tag>`. That

    first path component is the only thing separating two tenants'

    `api` repositories, which is why every repository response includes a

    ready-made `uri` rather than leaving clients to assemble one.


    **Everything except a tag is immutable.** A blob is named by the

    sha256 of its bytes and a manifest by the sha256 of its own JSON, so

    pushing the same content twice is a no-op the client detects before it

    uploads a byte. A tag is the one mutable pointer — and a repository

    may forbid even that, with `tag_mutability: immutable`, so a deployed

    digest cannot be swapped under a running fleet.


    **Deleting by digest and deleting by tag are different.** Removing a

    digest removes the manifest and every tag pointing at it. Removing a

    tag leaves the manifest in place, untagged: another tag may still name

    it, and `docker rmi` of one tag must not destroy an image someone else

    is pulling by another. Untagged manifests are what the `untagged`

    lifecycle rule exists to sweep.


    **Layers are shared and counted once.** Blobs are deduplicated across

    every repository in an account, so a repository's `size_bytes` is the

    sum of the distinct blobs its manifests reference — not the sum of its

    images' sizes, which would count every shared base layer once per

    image. Deleting an image does not immediately free space: whether a

    layer is still needed is a question about every other repository in

    the account, and the answer comes from a background sweep.


    **Sharing a repository.** A repository is private to its account

    until you attach a resource policy. `PUT

    /v1/repositories/{id}/policy` is how another account — or, with

    principal `*`, the public — is granted pull access. Cross-account

    access requires BOTH the resource policy and the caller's own IAM

    policy to allow it, so one tenant cannot grant another tenant's

    employee more than that employee's organization permits.
  contact:
    name: Basaltic Support
    email: ping@basaltic.sh
  license:
    name: Proprietary
    url: https://basaltic.sh/terms
servers:
  - url: https://registry.{region}.basaltic.sh
    description: Regional API endpoint
    variables:
      region:
        default: sa-saopaulo-1
        description: Region code
security:
  - BearerAuth: []
tags:
  - name: Authorization
    description: The credential `docker login` consumes
  - name: Repositories
    description: Repository lifecycle and settings, and the images and tags in one
  - name: Policies
    description: Resource policies and lifecycle policies
  - name: Encryption
    description: At-rest encryption of layer bytes
  - name: Pull-through cache
    description: Mirroring an upstream registry's images into a namespace of your own
paths:
  /v1/repositories/{repository_id}/images/{image_reference}:
    parameters:
      - $ref: '#/components/parameters/RepositoryId'
      - $ref: '#/components/parameters/Reference'
    get:
      tags:
        - Repositories
      summary: Describe an image
      description: |
        Resolves a tag or a digest to one image, including its raw manifest
        document.

        The `manifest` field is the parsed JSON, for convenience. It is NOT
        suitable for verifying the digest: the digest covers the exact bytes
        the client pushed, and a round-trip through a JSON encoder reorders
        keys. Fetch the manifest from `/v2` if you need to re-hash it.
      operationId: describeImage
      responses:
        '200':
          description: The image and its manifest.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ImageResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
components:
  parameters:
    RepositoryId:
      name: repository_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
        example: a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d
    Reference:
      name: image_reference
      in: path
      required: true
      description: A tag or a `sha256:` digest.
      schema:
        type: string
        example: v1.4.2
  schemas:
    ImageResponse:
      type: object
      properties:
        image:
          $ref: '#/components/schemas/Image'
        manifest:
          type: object
          description: |
            The manifest document, parsed. NOT suitable for verifying the
            digest — that covers the exact bytes pushed, and a round-trip
            through a JSON encoder reorders keys. Fetch from `/v2` to
            re-hash.
      required:
        - image
    Image:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: The image's row id — the pagination cursor for the list endpoint.
        digest:
          type: string
          description: The sha256 of the manifest document's exact bytes.
          example: >-
            sha256:1f3c9e0b8d7a6f5e4c3b2a190807f6e5d4c3b2a1908f7e6d5c4b3a2019f8e7d6c
        media_type:
          type: string
          description: |
            The OCI or Docker v2 media type. Both vocabularies are stored
            as pushed: containerd emits OCI, older `docker push` emits
            Docker v2, and they are the same documents with different
            labels.
          example: application/vnd.oci.image.manifest.v1+json
        tags:
          type: array
          items:
            type: string
          description: Every tag pointing at this manifest. Empty for an untagged image.
          example:
            - v1.4.2
            - latest
        artifact_type:
          type: string
          description: |
            What kind of thing this is. Distinguishes a container image
            from a Helm chart, an SBOM, or a signature — all of which are
            stored as manifests and would otherwise be indistinguishable.
          example: application/vnd.oci.image.config.v1+json
        config_digest:
          type: string
          description: Absent on a multi-platform index, which has no config of its own.
        subject:
          type: string
          description: |
            The digest this manifest is *about*, set on a signature, SBOM,
            or attestation. What the OCI referrers API resolves.
        manifest_size_bytes:
          type: integer
          format: int64
          description: The manifest document itself.
        size_bytes:
          type: integer
          format: int64
          description: |
            The sum of the distinct blobs this manifest references — what a
            customer means by "how big is this image".
          example: 87654321
        is_index:
          type: boolean
          description: True for a multi-platform image index or Docker manifest list.
        pushed_at:
          type: string
          format: date-time
        last_pulled_at:
          type: string
          format: date-time
          nullable: true
          description: |
            Coarsened to a minute. A fleet of 500 nodes pulling one image
            in a few seconds would otherwise be 500 row updates contending
            on one page, and the lifecycle rule reading this measures in
            days.
      required:
        - id
        - digest
        - media_type
        - tags
        - size_bytes
        - is_index
        - pushed_at
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
            - request_id
          properties:
            code:
              type: string
              description: Error code identifying the type of error
              example: INVALID_INPUT
            message:
              type: string
              description: Human-readable error message
              example: Invalid request parameters
            params:
              type: object
              additionalProperties: true
              description: >-
                Optional non-sensitive values for interpolation of localized
                errors, keyed by error code. Never present for server errors.
              example:
                instances: 2
                pools: 0
            request_id:
              type: string
              format: uuid
              description: Request ID for debugging
              example: 550e8400-e29b-41d4-a716-446655440000
  responses:
    BadRequest:
      description: Invalid request parameters
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: INVALID_INPUT
              message: Invalid request parameters
              request_id: 550e8400-e29b-41d4-a716-446655440000
    Unauthorized:
      description: Authentication required or token invalid
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: UNAUTHORIZED
              message: Authentication required
              request_id: 550e8400-e29b-41d4-a716-446655440000
    Forbidden:
      description: Insufficient permissions
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: ACCESS_DENIED
              message: You don't have permission to perform this action
              request_id: 550e8400-e29b-41d4-a716-446655440000
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: NOT_FOUND
              message: Resource not found
              request_id: 550e8400-e29b-41d4-a716-446655440000
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        An OAuth 2.0 bearer token, sent as `Authorization: Bearer <token>`.
        This is the recommended way to authenticate.

        Get one by exchanging a service account's access key pair at
        `POST /v1/oauth/token` with `grant_type=client_credentials`. It is the
        standard client-credentials grant, so any OAuth-aware library will
        obtain and refresh it for you.

        ```
        curl -s -u "$KEY_ID:$SECRET" -d grant_type=client_credentials \
          https://iam.basaltic.sh/v1/oauth/token
        ```

        Tokens last an hour by default. The same access key pair is separately
        your AWS SigV4 credential for the S3-compatible object endpoint, which
        speaks nothing else.

````

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