openapi: 3.1.0
info:
  title: imgsrv API
  version: v1
  description: Contract for the implemented imgsrv v1 upload and catalog API.
servers:
  - url: /
paths:
  /streams/v1/index.json:
    get:
      operationId: getSimpleStreamsIndex
      summary: Get the Incus Simple Streams index document.
      responses:
        '200':
          description: Simple Streams index document.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /streams/v1/images.json:
    get:
      operationId: getSimpleStreamsImages
      summary: Get the Incus Simple Streams image product document.
      responses:
        '200':
          description: Incus-compatible Simple Streams product document.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/auth/roles:
    get:
      operationId: listAuthRoles
      summary: List built-in auth roles.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Built-in auth roles.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthRoleList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/auth/principals:
    get:
      operationId: listAuthPrincipals
      summary: List auth principals.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Auth principals.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthPrincipalList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '503':
          $ref: '#/components/responses/Problem'
    post:
      operationId: createAuthPrincipal
      summary: Create an auth principal.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateAuthPrincipalRequest'
      responses:
        '201':
          description: Auth principal created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthPrincipal'
        '400':
          $ref: '#/components/responses/Problem'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/auth/principals/{principal_id}:
    get:
      operationId: getAuthPrincipal
      summary: Get one auth principal.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/AuthPrincipalID'
      responses:
        '200':
          description: Auth principal.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthPrincipal'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/auth/principals/{principal_id}/roles/{role_id}:
    put:
      operationId: assignAuthPrincipalRole
      summary: Assign a role to an auth principal.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/AuthPrincipalID'
        - $ref: '#/components/parameters/AuthRoleID'
      responses:
        '204':
          description: Role assigned.
        '400':
          $ref: '#/components/responses/Problem'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
    delete:
      operationId: unassignAuthPrincipalRole
      summary: Remove a role from an auth principal.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/AuthPrincipalID'
        - $ref: '#/components/parameters/AuthRoleID'
      responses:
        '204':
          description: Role removed.
        '400':
          $ref: '#/components/responses/Problem'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/auth/principals/{principal_id}/api-tokens:
    get:
      operationId: listAuthPrincipalAPITokens
      summary: List API tokens for one auth principal.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/AuthPrincipalID'
      responses:
        '200':
          description: API-token metadata.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthAPITokenList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
    post:
      operationId: issueAuthPrincipalAPIToken
      summary: Issue an API token for an auth principal.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/AuthPrincipalID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IssueAuthAPITokenRequest'
      responses:
        '201':
          description: API token issued. The plaintext bearer token is returned only in this response.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthAPIToken'
        '400':
          $ref: '#/components/responses/Problem'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/auth/api-tokens/{token_id}:
    delete:
      operationId: revokeAuthAPIToken
      summary: Revoke an API token.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/AuthAPITokenID'
      responses:
        '204':
          description: API token revoked.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/auth/oidc-provisioning-rules:
    get:
      operationId: listOIDCProvisioningRules
      summary: List OIDC provisioning rules.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: OIDC provisioning rules.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OIDCProvisioningRuleList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '503':
          $ref: '#/components/responses/Problem'
    post:
      operationId: createOIDCProvisioningRule
      summary: Create an OIDC provisioning rule.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SaveOIDCProvisioningRuleRequest'
      responses:
        '201':
          description: OIDC provisioning rule created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OIDCProvisioningRule'
        '400':
          $ref: '#/components/responses/Problem'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/auth/oidc-provisioning-rules/{rule_id}:
    get:
      operationId: getOIDCProvisioningRule
      summary: Get one OIDC provisioning rule.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/OIDCProvisioningRuleID'
      responses:
        '200':
          description: OIDC provisioning rule.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OIDCProvisioningRule'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
    put:
      operationId: updateOIDCProvisioningRule
      summary: Replace one OIDC provisioning rule.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/OIDCProvisioningRuleID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SaveOIDCProvisioningRuleRequest'
      responses:
        '200':
          description: OIDC provisioning rule updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OIDCProvisioningRule'
        '400':
          $ref: '#/components/responses/Problem'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
    delete:
      operationId: deleteOIDCProvisioningRule
      summary: Delete one OIDC provisioning rule.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/OIDCProvisioningRuleID'
      responses:
        '204':
          description: OIDC provisioning rule deleted.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/auth/oidc-provisioning-rules/{rule_id}/reconciliation:
    get:
      operationId: previewOIDCProvisioningRuleReconciliation
      summary: Preview OIDC provisioning rule role cleanup.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/OIDCProvisioningRuleID'
      responses:
        '200':
          description: Principals that would be reconciled.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OIDCProvisioningRuleReconciliation'
        '400':
          $ref: '#/components/responses/Problem'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '503':
          $ref: '#/components/responses/Problem'
    post:
      operationId: reconcileOIDCProvisioningRule
      summary: Remove rule-granted roles from existing principals.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/OIDCProvisioningRuleID'
      responses:
        '200':
          description: Principals reconciled.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OIDCProvisioningRuleReconciliation'
        '400':
          $ref: '#/components/responses/Problem'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/uploads:
    post:
      operationId: beginUpload
      summary: Begin an upload session.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BeginUploadRequest'
      responses:
        '200':
          description: Trusted digest already exists; returned ready upload session was not newly created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadSession'
        '201':
          description: Upload session created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadSession'
        '400':
          $ref: '#/components/responses/Problem'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/blobs/{digest}:
    get:
      operationId: openBlob
      summary: Read a trusted CAS blob.
      parameters:
        - $ref: '#/components/parameters/BlobDigest'
        - name: Range
          in: header
          required: false
          schema:
            type: string
          description: >
            One supported byte range: `bytes=start-end`, `bytes=start-`, or
            `bytes=-suffix_length`. Multi-range requests are rejected.
        - name: If-None-Match
          in: header
          required: false
          schema:
            type: string
        - name: If-Modified-Since
          in: header
          required: false
          schema:
            type: string
        - name: If-Range
          in: header
          required: false
          schema:
            type: string
      responses:
        '200':
          description: Full blob body.
          headers:
            Accept-Ranges:
              schema:
                type: string
            Cache-Control:
              schema:
                type: string
            Content-Length:
              schema:
                type: integer
            Content-Type:
              schema:
                type: string
            ETag:
              schema:
                type: string
            Last-Modified:
              schema:
                type: string
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        '206':
          description: Partial blob body for one satisfied range.
          headers:
            Accept-Ranges:
              schema:
                type: string
            Cache-Control:
              schema:
                type: string
            Content-Length:
              schema:
                type: integer
            Content-Range:
              schema:
                type: string
            Content-Type:
              schema:
                type: string
            ETag:
              schema:
                type: string
            Last-Modified:
              schema:
                type: string
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        '304':
          description: Cache validator matched current blob representation.
          headers:
            Accept-Ranges:
              schema:
                type: string
            Cache-Control:
              schema:
                type: string
            ETag:
              schema:
                type: string
            Last-Modified:
              schema:
                type: string
        '400':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '416':
          description: Requested range was invalid, unsupported, or unsatisfiable.
          headers:
            Content-Range:
              schema:
                type: string
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
    head:
      operationId: headBlob
      summary: Read trusted CAS blob metadata without a response body.
      parameters:
        - $ref: '#/components/parameters/BlobDigest'
        - name: If-None-Match
          in: header
          required: false
          schema:
            type: string
        - name: If-Modified-Since
          in: header
          required: false
          schema:
            type: string
      responses:
        '200':
          description: Blob metadata.
          headers:
            Accept-Ranges:
              schema:
                type: string
            Cache-Control:
              schema:
                type: string
            Content-Length:
              schema:
                type: integer
            Content-Type:
              schema:
                type: string
            ETag:
              schema:
                type: string
            Last-Modified:
              schema:
                type: string
        '304':
          description: Cache validator matched current blob representation.
          headers:
            Accept-Ranges:
              schema:
                type: string
            Cache-Control:
              schema:
                type: string
            ETag:
              schema:
                type: string
            Last-Modified:
              schema:
                type: string
        '400':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/uploads/{upload_id}:
    get:
      operationId: getUpload
      summary: Get upload session status.
      parameters:
        - $ref: '#/components/parameters/UploadID'
      responses:
        '200':
          description: Current upload session state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadSession'
        '400':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/uploads/{upload_id}/parts/{part_number}:
    put:
      operationId: putUploadPart
      summary: Upload or replace one multipart upload part.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/UploadID'
        - name: part_number
          in: path
          required: true
          schema:
            type: integer
            minimum: 1
            maximum: 10000
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        '200':
          description: Upload part accepted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadPart'
        '400':
          $ref: '#/components/responses/Problem'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/uploads/{upload_id}/complete:
    post:
      operationId: completeUpload
      summary: Complete an upload session.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/UploadID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CompleteUploadRequest'
      responses:
        '200':
          description: Upload session completed or already terminally advanced.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadSession'
        '400':
          $ref: '#/components/responses/Problem'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/uploads/{upload_id}/abort:
    post:
      operationId: abortUpload
      summary: Abort an upload session.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/UploadID'
      responses:
        '200':
          description: Upload session aborted or already aborted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadSession'
        '400':
          $ref: '#/components/responses/Problem'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/images:
    get:
      operationId: listImages
      summary: List image namespaces with published versions.
      responses:
        '200':
          description: Public image namespaces in stable order.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ImageList'
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
    post:
      operationId: createImage
      summary: Create an image namespace.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateImageRequest'
      responses:
        '201':
          description: Image namespace created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Image'
        '400':
          $ref: '#/components/responses/Problem'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          $ref: '#/components/responses/Problem'
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/images/{name}:
    get:
      operationId: getImage
      summary: Get one image namespace with a published version.
      parameters:
        - $ref: '#/components/parameters/ImageName'
      responses:
        '200':
          description: Public image namespace.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Image'
        '400':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/images/{name}/versions:
    get:
      operationId: listVersions
      summary: List published versions for an image.
      parameters:
        - $ref: '#/components/parameters/ImageName'
      responses:
        '200':
          description: Published image versions in stable order.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VersionList'
        '400':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
    post:
      operationId: createDraftVersion
      summary: Create a draft image version.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/ImageName'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateDraftVersionRequest'
      responses:
        '201':
          description: Draft image version created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ImageVersion'
        '400':
          $ref: '#/components/responses/Problem'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/images/{name}/versions/{version}:
    get:
      operationId: getVersionManifest
      summary: Get an exact draft or published version manifest.
      parameters:
        - $ref: '#/components/parameters/ImageName'
        - $ref: '#/components/parameters/ImageVersion'
      responses:
        '200':
          description: Exact image version manifest.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Manifest'
        '400':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/images/{name}/versions/{version}/artifacts:
    get:
      operationId: listArtifacts
      summary: List primary artifacts for an exact published version.
      parameters:
        - $ref: '#/components/parameters/ImageName'
        - $ref: '#/components/parameters/ImageVersion'
      responses:
        '200':
          description: Published primary artifacts in stable order.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ArtifactList'
        '400':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
    post:
      operationId: addArtifact
      summary: Add a primary artifact to a draft version.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/ImageName'
        - $ref: '#/components/parameters/ImageVersion'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddArtifactRequest'
      responses:
        '201':
          description: Artifact declaration created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Artifact'
        '400':
          $ref: '#/components/responses/Problem'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/images/{name}/versions/{version}/artifacts/{artifact_id}:
    get:
      operationId: getArtifact
      summary: Get one primary artifact for an exact published version.
      parameters:
        - $ref: '#/components/parameters/ImageName'
        - $ref: '#/components/parameters/ImageVersion'
        - $ref: '#/components/parameters/ArtifactID'
      responses:
        '200':
          description: Published primary artifact.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Artifact'
        '400':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
    delete:
      operationId: deleteArtifact
      summary: Delete a primary artifact from a draft version.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/ImageName'
        - $ref: '#/components/parameters/ImageVersion'
        - $ref: '#/components/parameters/ArtifactID'
      responses:
        '204':
          description: Artifact declaration deleted.
        '400':
          $ref: '#/components/responses/Problem'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/images/{name}/versions/{version}/artifacts/{artifact_id}/download:
    get:
      operationId: openArtifactDownload
      summary: Download a published artifact blob.
      parameters:
        - $ref: '#/components/parameters/ImageName'
        - $ref: '#/components/parameters/ImageVersion'
        - $ref: '#/components/parameters/ArtifactID'
        - $ref: '#/components/parameters/Range'
        - $ref: '#/components/parameters/IfNoneMatch'
        - $ref: '#/components/parameters/IfModifiedSince'
        - $ref: '#/components/parameters/IfRange'
      responses:
        '200':
          $ref: '#/components/responses/BlobOK'
        '206':
          $ref: '#/components/responses/BlobPartial'
        '304':
          $ref: '#/components/responses/BlobNotModified'
        '400':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '416':
          $ref: '#/components/responses/RangeNotSatisfiable'
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
    head:
      operationId: headArtifactDownload
      summary: Read published artifact blob metadata without a response body.
      parameters:
        - $ref: '#/components/parameters/ImageName'
        - $ref: '#/components/parameters/ImageVersion'
        - $ref: '#/components/parameters/ArtifactID'
        - $ref: '#/components/parameters/IfNoneMatch'
        - $ref: '#/components/parameters/IfModifiedSince'
      responses:
        '200':
          $ref: '#/components/responses/BlobHeadOK'
        '304':
          $ref: '#/components/responses/BlobNotModified'
        '400':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/images/{name}/versions/{version}/artifacts/{artifact_id}/attachments:
    post:
      operationId: addAttachment
      summary: Add an attachment to a draft artifact.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/ImageName'
        - $ref: '#/components/parameters/ImageVersion'
        - $ref: '#/components/parameters/ArtifactID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddAttachmentRequest'
      responses:
        '201':
          description: Attachment declaration created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Attachment'
        '400':
          $ref: '#/components/responses/Problem'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/images/{name}/versions/{version}/artifacts/{artifact_id}/attachments/{attachment_id}:
    delete:
      operationId: deleteAttachment
      summary: Delete an attachment from a draft artifact.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/ImageName'
        - $ref: '#/components/parameters/ImageVersion'
        - $ref: '#/components/parameters/ArtifactID'
        - $ref: '#/components/parameters/AttachmentID'
      responses:
        '204':
          description: Attachment declaration deleted.
        '400':
          $ref: '#/components/responses/Problem'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/images/{name}/versions/{version}/artifacts/{artifact_id}/attachments/{attachment_id}/download:
    get:
      operationId: openAttachmentDownload
      summary: Download a published artifact attachment blob.
      parameters:
        - $ref: '#/components/parameters/ImageName'
        - $ref: '#/components/parameters/ImageVersion'
        - $ref: '#/components/parameters/ArtifactID'
        - $ref: '#/components/parameters/AttachmentID'
        - $ref: '#/components/parameters/Range'
        - $ref: '#/components/parameters/IfNoneMatch'
        - $ref: '#/components/parameters/IfModifiedSince'
        - $ref: '#/components/parameters/IfRange'
      responses:
        '200':
          $ref: '#/components/responses/BlobOK'
        '206':
          $ref: '#/components/responses/BlobPartial'
        '304':
          $ref: '#/components/responses/BlobNotModified'
        '400':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '416':
          $ref: '#/components/responses/RangeNotSatisfiable'
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
    head:
      operationId: headAttachmentDownload
      summary: Read published attachment blob metadata without a response body.
      parameters:
        - $ref: '#/components/parameters/ImageName'
        - $ref: '#/components/parameters/ImageVersion'
        - $ref: '#/components/parameters/ArtifactID'
        - $ref: '#/components/parameters/AttachmentID'
        - $ref: '#/components/parameters/IfNoneMatch'
        - $ref: '#/components/parameters/IfModifiedSince'
      responses:
        '200':
          $ref: '#/components/responses/BlobHeadOK'
        '304':
          $ref: '#/components/responses/BlobNotModified'
        '400':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/images/{name}/versions/{version}/publish:
    post:
      operationId: publishVersion
      summary: Publish a draft image version.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/ImageName'
        - $ref: '#/components/parameters/ImageVersion'
      responses:
        '202':
          description: Publish job queued.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublishJob'
        '400':
          $ref: '#/components/responses/Problem'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/publish-jobs/{job_id}:
    get:
      operationId: getPublishJob
      summary: Get a publish job.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/PublishJobID'
      responses:
        '200':
          description: Publish job status and ordered steps.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublishJob'
        '400':
          $ref: '#/components/responses/Problem'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/Problem'
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/publish-jobs/{job_id}/retry:
    post:
      operationId: retryPublishJob
      summary: Retry a failed publish job.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/PublishJobID'
      responses:
        '202':
          description: Publish job requeued.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublishJob'
        '400':
          $ref: '#/components/responses/Problem'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/images/{name}/aliases:
    get:
      operationId: listAliases
      summary: List image aliases.
      parameters:
        - $ref: '#/components/parameters/ImageName'
      responses:
        '200':
          description: Image aliases in stable order.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AliasList'
        '400':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/images/{name}/aliases/{alias}:
    put:
      operationId: putAlias
      summary: Create or move an image alias to a published version.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/ImageName'
        - $ref: '#/components/parameters/AliasName'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PutAliasRequest'
      responses:
        '200':
          description: Image alias created or moved.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Alias'
        '400':
          $ref: '#/components/responses/Problem'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
    get:
      operationId: getAlias
      summary: Get one image alias.
      parameters:
        - $ref: '#/components/parameters/ImageName'
        - $ref: '#/components/parameters/AliasName'
      responses:
        '200':
          description: Image alias.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Alias'
        '400':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
    delete:
      operationId: deleteAlias
      summary: Delete one image alias.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/ImageName'
        - $ref: '#/components/parameters/AliasName'
      responses:
        '204':
          description: Image alias deleted.
        '400':
          $ref: '#/components/responses/Problem'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/Problem'
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
  /v1/images/{name}/refs/{ref}:
    get:
      operationId: resolveManifest
      summary: Resolve a published manifest by exact version or alias.
      parameters:
        - $ref: '#/components/parameters/ImageName'
        - $ref: '#/components/parameters/ImageRef'
      responses:
        '200':
          description: Published image version manifest.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Manifest'
        '400':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '500':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
  parameters:
    UploadID:
      name: upload_id
      in: path
      required: true
      schema:
        $ref: '#/components/schemas/UploadID'
    OIDCProvisioningRuleID:
      name: rule_id
      in: path
      required: true
      schema:
        type: string
        minLength: 1
    AuthPrincipalID:
      name: principal_id
      in: path
      required: true
      schema:
        type: string
        minLength: 1
    AuthRoleID:
      name: role_id
      in: path
      required: true
      schema:
        type: string
        enum:
          - auth-manager
          - content-writer
    AuthAPITokenID:
      name: token_id
      in: path
      required: true
      schema:
        type: string
        minLength: 1
    BlobDigest:
      name: digest
      in: path
      required: true
      schema:
        $ref: '#/components/schemas/Digest'
    ImageName:
      name: name
      in: path
      required: true
      schema:
        type: string
        pattern: '^[a-z0-9][a-z0-9._-]{0,127}$'
    ImageVersion:
      name: version
      in: path
      required: true
      schema:
        type: string
        pattern: '^[A-Za-z0-9][A-Za-z0-9._+:-]{0,127}$'
    PublishJobID:
      name: job_id
      in: path
      required: true
      schema:
        $ref: '#/components/schemas/PublishJobID'
    AliasName:
      name: alias
      in: path
      required: true
      schema:
        type: string
        pattern: '^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$'
    ImageRef:
      name: ref
      in: path
      required: true
      schema:
        type: string
        pattern: '^[A-Za-z0-9][A-Za-z0-9._+:-]{0,127}$'
    ArtifactID:
      name: artifact_id
      in: path
      required: true
      schema:
        $ref: '#/components/schemas/ArtifactID'
    AttachmentID:
      name: attachment_id
      in: path
      required: true
      schema:
        $ref: '#/components/schemas/AttachmentID'
    Range:
      name: Range
      in: header
      required: false
      schema:
        type: string
      description: >
        One supported byte range: `bytes=start-end`, `bytes=start-`, or
        `bytes=-suffix_length`. Multi-range requests are rejected.
    IfNoneMatch:
      name: If-None-Match
      in: header
      required: false
      schema:
        type: string
    IfModifiedSince:
      name: If-Modified-Since
      in: header
      required: false
      schema:
        type: string
    IfRange:
      name: If-Range
      in: header
      required: false
      schema:
        type: string
  responses:
    Problem:
      description: RFC 9457 problem details.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    Unauthorized:
      description: Missing, malformed, unknown, or revoked bearer token.
      headers:
        WWW-Authenticate:
          schema:
            type: string
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    Forbidden:
      description: Authenticated bearer token is not authorized for the requested action.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    BlobOK:
      description: Full blob body.
      headers:
        Accept-Ranges:
          schema:
            type: string
        Cache-Control:
          schema:
            type: string
        Content-Length:
          schema:
            type: integer
        Content-Type:
          schema:
            type: string
        ETag:
          schema:
            type: string
        Last-Modified:
          schema:
            type: string
      content:
        application/octet-stream:
          schema:
            type: string
            format: binary
    BlobPartial:
      description: Partial blob body for one satisfied range.
      headers:
        Accept-Ranges:
          schema:
            type: string
        Cache-Control:
          schema:
            type: string
        Content-Length:
          schema:
            type: integer
        Content-Range:
          schema:
            type: string
        Content-Type:
          schema:
            type: string
        ETag:
          schema:
            type: string
        Last-Modified:
          schema:
            type: string
      content:
        application/octet-stream:
          schema:
            type: string
            format: binary
    BlobHeadOK:
      description: Blob metadata.
      headers:
        Accept-Ranges:
          schema:
            type: string
        Cache-Control:
          schema:
            type: string
        Content-Length:
          schema:
            type: integer
        Content-Type:
          schema:
            type: string
        ETag:
          schema:
            type: string
        Last-Modified:
          schema:
            type: string
    BlobNotModified:
      description: Cache validator matched current blob representation.
      headers:
        Accept-Ranges:
          schema:
            type: string
        Cache-Control:
          schema:
            type: string
        ETag:
          schema:
            type: string
        Last-Modified:
          schema:
            type: string
    RangeNotSatisfiable:
      description: Requested range was invalid, unsupported, or unsatisfiable.
      headers:
        Content-Range:
          schema:
            type: string
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
  schemas:
    Digest:
      type: string
      pattern: '^sha256:[0-9a-f]{64}$'
      examples:
        - sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
    UploadID:
      type: string
      format: uuid
    PublishJobID:
      type: string
      format: uuid
    ArtifactID:
      type: string
      format: uuid
    AttachmentID:
      type: string
      format: uuid
    AuthRole:
      type: object
      required:
        - id
        - display_name
        - description
        - actions
      properties:
        id:
          type: string
          enum:
            - auth-manager
            - content-writer
        display_name:
          type: string
        description:
          type: string
        actions:
          type: array
          items:
            type: string
      additionalProperties: false
    AuthRoleList:
      type: object
      required:
        - roles
      properties:
        roles:
          type: array
          items:
            $ref: '#/components/schemas/AuthRole'
      additionalProperties: false
    CreateAuthPrincipalRequest:
      type: object
      required:
        - kind
        - display_name
      properties:
        kind:
          type: string
          enum:
            - user
            - service
        display_name:
          type: string
          minLength: 1
        attributes:
          type: object
          additionalProperties: true
      additionalProperties: false
    AuthPrincipal:
      type: object
      required:
        - id
        - kind
        - display_name
        - role_ids
      properties:
        id:
          type: string
        kind:
          type: string
          enum:
            - user
            - service
        display_name:
          type: string
        attributes:
          type: object
          additionalProperties: true
        role_ids:
          type: array
          items:
            type: string
      additionalProperties: false
    AuthPrincipalList:
      type: object
      required:
        - principals
      properties:
        principals:
          type: array
          items:
            $ref: '#/components/schemas/AuthPrincipal'
      additionalProperties: false
    IssueAuthAPITokenRequest:
      type: object
      required:
        - expires_at
      properties:
        name:
          type: string
        expires_at:
          type: string
          format: date-time
      additionalProperties: false
    AuthAPIToken:
      type: object
      required:
        - id
        - principal_id
        - name
        - expires_at
      properties:
        id:
          type: string
        principal_id:
          type: string
        name:
          type: string
        expires_at:
          type: string
          format: date-time
        last_used_at:
          type: string
          format: date-time
        revoked_at:
          type: string
          format: date-time
        plaintext:
          type: string
          description: Full bearer token secret. Present only in issue responses.
      additionalProperties: false
    AuthAPITokenList:
      type: object
      required:
        - api_tokens
      properties:
        api_tokens:
          type: array
          items:
            $ref: '#/components/schemas/AuthAPIToken'
      additionalProperties: false
    SaveOIDCProvisioningRuleRequest:
      type: object
      required:
        - display_name
        - issuer_url
        - audience
        - condition
      properties:
        id:
          type: string
          minLength: 1
        display_name:
          type: string
        issuer_url:
          type: string
          format: uri
        audience:
          type: string
          minLength: 1
        forwarded_claims:
          type: array
          items:
            type: string
            minLength: 1
        condition:
          type: string
          minLength: 1
          maxLength: 4096
        enabled:
          type: boolean
      additionalProperties: false
    OIDCProvisioningRule:
      type: object
      required:
        - id
        - display_name
        - issuer_url
        - audience
        - condition
        - assign_role_ids
        - enabled
      properties:
        id:
          type: string
        display_name:
          type: string
        issuer_url:
          type: string
          format: uri
        audience:
          type: string
        forwarded_claims:
          type: array
          items:
            type: string
        condition:
          type: string
        assign_role_ids:
          type: array
          items:
            type: string
        enabled:
          type: boolean
      additionalProperties: false
    OIDCProvisioningRuleList:
      type: object
      required:
        - rules
      properties:
        rules:
          type: array
          items:
            $ref: '#/components/schemas/OIDCProvisioningRule'
      additionalProperties: false
    OIDCProvisioningRuleReconciliation:
      type: object
      required:
        - rule_id
        - unassign_role_ids
        - principals
        - applied
      properties:
        rule_id:
          type: string
        unassign_role_ids:
          type: array
          items:
            type: string
        principals:
          type: array
          items:
            $ref: '#/components/schemas/AuthPrincipal'
        applied:
          type: boolean
      additionalProperties: false
    UploadState:
      type: string
      enum:
        - created
        - uploading
        - completed
        - ingesting
        - ready
        - failed
        - aborted
    ImageVersionState:
      type: string
      enum:
        - draft
        - publishing
        - published
    PublishJobState:
      type: string
      enum:
        - queued
        - running
        - succeeded
        - failed
    PublishStepState:
      type: string
      enum:
        - queued
        - running
        - succeeded
        - failed
        - skipped
    ArtifactFormat:
      type: string
      enum:
        - raw
        - raw.gz
        - qcow2
    BeginUploadRequest:
      type: object
      required:
        - expected_digest
        - expected_size_bytes
      properties:
        expected_digest:
          $ref: '#/components/schemas/Digest'
        expected_size_bytes:
          type: integer
          format: int64
          minimum: 0
        media_type_hint:
          type: string
          minLength: 1
        filename_hint:
          type: string
          minLength: 1
      additionalProperties: false
    CompleteUploadRequest:
      type: object
      required:
        - parts
      properties:
        parts:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/CompleteUploadPart'
      additionalProperties: false
    CompleteUploadPart:
      type: object
      required:
        - number
        - etag
        - size_bytes
      properties:
        number:
          type: integer
          minimum: 1
          maximum: 10000
        etag:
          type: string
          minLength: 1
        size_bytes:
          type: integer
          format: int64
          minimum: 0
      additionalProperties: false
    UploadSession:
      type: object
      required:
        - id
        - expected_digest
        - expected_size_bytes
        - state
        - expires_at
      properties:
        id:
          $ref: '#/components/schemas/UploadID'
        expected_digest:
          $ref: '#/components/schemas/Digest'
        expected_size_bytes:
          type: integer
          format: int64
          minimum: 0
        state:
          $ref: '#/components/schemas/UploadState'
        expires_at:
          type: string
          format: date-time
        media_type_hint:
          type: string
          minLength: 1
        filename_hint:
          type: string
          minLength: 1
      additionalProperties: false
    UploadPart:
      type: object
      required:
        - upload_id
        - part_number
        - etag
        - size_bytes
      properties:
        upload_id:
          $ref: '#/components/schemas/UploadID'
        part_number:
          type: integer
          minimum: 1
          maximum: 10000
        etag:
          type: string
          minLength: 1
        size_bytes:
          type: integer
          format: int64
          minimum: 0
      additionalProperties: false
    CreateImageRequest:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          pattern: '^[a-z0-9][a-z0-9._-]{0,127}$'
        display_name:
          type: string
        description:
          type: string
      additionalProperties: false
    CreateDraftVersionRequest:
      type: object
      required:
        - version
      properties:
        version:
          type: string
          pattern: '^[A-Za-z0-9][A-Za-z0-9._+:-]{0,127}$'
      additionalProperties: false
    PutAliasRequest:
      type: object
      required:
        - version
      properties:
        version:
          type: string
          pattern: '^[A-Za-z0-9][A-Za-z0-9._+:-]{0,127}$'
      additionalProperties: false
    AddArtifactRequest:
      type: object
      required:
        - operating_system
        - architecture
        - format
        - primary_blob_digest
        - primary_blob_size_bytes
        - primary_media_type
      properties:
        variant:
          type: string
          pattern: '^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$'
          default: default
        operating_system:
          type: string
          pattern: '^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$'
        architecture:
          type: string
          pattern: '^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$'
        format:
          $ref: '#/components/schemas/ArtifactFormat'
        primary_blob_digest:
          $ref: '#/components/schemas/Digest'
        primary_blob_size_bytes:
          type: integer
          format: int64
          minimum: 0
        primary_media_type:
          type: string
          minLength: 1
      additionalProperties: false
    AddAttachmentRequest:
      type: object
      required:
        - name
        - media_type
        - blob_digest
        - blob_size_bytes
      properties:
        name:
          type: string
          pattern: '^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$'
        media_type:
          type: string
          minLength: 1
        blob_digest:
          $ref: '#/components/schemas/Digest'
        blob_size_bytes:
          type: integer
          format: int64
          minimum: 0
      additionalProperties: false
    Image:
      type: object
      required:
        - id
        - name
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          pattern: '^[a-z0-9][a-z0-9._-]{0,127}$'
        display_name:
          type: string
        description:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      additionalProperties: false
    ImageList:
      type: object
      description: Image namespaces that have at least one published version.
      required:
        - images
      properties:
        images:
          type: array
          items:
            $ref: '#/components/schemas/Image'
      additionalProperties: false
    ImageVersion:
      type: object
      required:
        - id
        - image_id
        - version
        - state
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        image_id:
          type: string
          format: uuid
        version:
          type: string
          pattern: '^[A-Za-z0-9][A-Za-z0-9._+:-]{0,127}$'
        state:
          $ref: '#/components/schemas/ImageVersionState'
        published_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      additionalProperties: false
    PublishJob:
      type: object
      required:
        - id
        - version_id
        - image_name
        - version
        - state
        - created_at
        - updated_at
        - steps
      properties:
        id:
          $ref: '#/components/schemas/PublishJobID'
        version_id:
          type: string
          format: uuid
        image_name:
          type: string
          pattern: '^[a-z0-9][a-z0-9._-]{0,127}$'
        version:
          type: string
          pattern: '^[A-Za-z0-9][A-Za-z0-9._+:-]{0,127}$'
        state:
          $ref: '#/components/schemas/PublishJobState'
        started_at:
          type: string
          format: date-time
        finished_at:
          type: string
          format: date-time
        failure_message:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        steps:
          type: array
          items:
            $ref: '#/components/schemas/PublishJobStep'
      additionalProperties: false
    PublishJobStep:
      type: object
      required:
        - id
        - job_id
        - name
        - state
        - blocking
        - sequence
        - attempt_count
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        job_id:
          $ref: '#/components/schemas/PublishJobID'
        name:
          type: string
          enum:
            - validate_catalog
            - incus_index
            - finalize_publish
        state:
          $ref: '#/components/schemas/PublishStepState'
        blocking:
          type: boolean
        sequence:
          type: integer
          minimum: 0
        attempt_count:
          type: integer
          minimum: 0
        started_at:
          type: string
          format: date-time
        finished_at:
          type: string
          format: date-time
        failure_message:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      additionalProperties: false
    VersionList:
      type: object
      description: Published image versions for one public image namespace.
      required:
        - versions
      properties:
        versions:
          type: array
          items:
            $ref: '#/components/schemas/ImageVersion'
      additionalProperties: false
    Artifact:
      type: object
      required:
        - id
        - version_id
        - variant
        - operating_system
        - architecture
        - format
        - primary_blob_digest
        - primary_blob_size_bytes
        - primary_media_type
        - created_at
        - updated_at
      properties:
        id:
          $ref: '#/components/schemas/ArtifactID'
        version_id:
          type: string
          format: uuid
        variant:
          type: string
          pattern: '^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$'
        operating_system:
          type: string
          pattern: '^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$'
        architecture:
          type: string
          pattern: '^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$'
        format:
          $ref: '#/components/schemas/ArtifactFormat'
        primary_blob_digest:
          $ref: '#/components/schemas/Digest'
        primary_blob_size_bytes:
          type: integer
          format: int64
          minimum: 0
        primary_media_type:
          type: string
          minLength: 1
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      additionalProperties: false
    ArtifactList:
      type: object
      description: Published primary artifacts for one exact image version.
      required:
        - artifacts
      properties:
        artifacts:
          type: array
          items:
            $ref: '#/components/schemas/Artifact'
      additionalProperties: false
    Attachment:
      type: object
      required:
        - id
        - artifact_id
        - name
        - media_type
        - blob_digest
        - blob_size_bytes
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        artifact_id:
          $ref: '#/components/schemas/ArtifactID'
        name:
          type: string
          pattern: '^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$'
        media_type:
          type: string
          minLength: 1
        blob_digest:
          $ref: '#/components/schemas/Digest'
        blob_size_bytes:
          type: integer
          format: int64
          minimum: 0
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      additionalProperties: false
    Alias:
      type: object
      required:
        - id
        - image_id
        - alias
        - version_id
        - version
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        image_id:
          type: string
          format: uuid
        alias:
          type: string
          pattern: '^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$'
        version_id:
          type: string
          format: uuid
        version:
          type: string
          pattern: '^[A-Za-z0-9][A-Za-z0-9._+:-]{0,127}$'
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      additionalProperties: false
    AliasList:
      type: object
      required:
        - aliases
      properties:
        aliases:
          type: array
          items:
            $ref: '#/components/schemas/Alias'
      additionalProperties: false
    Manifest:
      type: object
      required:
        - image
        - version
        - artifacts
      properties:
        image:
          $ref: '#/components/schemas/Image'
        version:
          $ref: '#/components/schemas/ImageVersion'
        artifacts:
          type: array
          items:
            $ref: '#/components/schemas/ManifestArtifact'
      additionalProperties: false
    ManifestArtifact:
      type: object
      required:
        - artifact
        - attachments
      properties:
        artifact:
          $ref: '#/components/schemas/Artifact'
        attachments:
          type: array
          items:
            $ref: '#/components/schemas/Attachment'
      additionalProperties: false
    Problem:
      type: object
      description: >
        RFC 9457 problem details. The current API emits about:blank for all
        problem types; stable imgsrv-specific type URIs will be added only when
        clients need machine-readable branching beyond the HTTP status code.
      properties:
        type:
          type: string
          format: uri-reference
          default: about:blank
          description: >
            RFC 9457 problem type. Clients should currently classify errors by
            HTTP status and must not parse detail.
        title:
          type: string
        status:
          type: integer
          minimum: 100
          maximum: 599
        detail:
          type: string
        instance:
          type: string
          format: uri-reference
      additionalProperties: true
