openapi: 3.1.0
info:
  title: OrbitalReg REST API
  description: OrbitalReg is an enterprise artifact registry. This document covers the v1 REST surface that the web UI, the `orbital` CLI, the Terraform provider, and the Kubernetes operator share. Per-format wire protocols (Maven repo layout, npm registry, Docker Registry v2, etc.) are documented separately in `docs/PACKAGE-FORMATS-ROADMAP.md` — those endpoints follow the upstream specs verbatim.
  version: 2026.1.2-154-ga084ad0-dirty
  contact:
    name: OrbitalReg
    url: https://orbitalreg.io
  license:
    name: Proprietary
servers:
  - url: https://{host}
    description: OrbitalReg deployment (substitute {host})
tags:
  - name: Health
    description: Liveness, readiness and metrics probes.
  - name: Public
    description: Unauthenticated endpoints consumed by the login screen, the customer-facing block page and the maintenance banner.
  - name: Auth
    description: 'Session lifecycle: SAML / local / OIDC sign-in and the OIDC token-exchange endpoints used by GitLab CI and the Kubernetes operator.'
  - name: Repos
    description: Repository CRUD. Each repo has a single format (Maven, npm, …) and lives inside one project.
  - name: Projects
    description: Project CRUD plus per-project license policy, quarantine settings and license-aggregation views.
  - name: Artifacts
    description: Generic upload/download surface that backs the `orbital push` / `orbital pull` CLI verbs.
  - name: Tokens
    description: Personal-access-token issue and revoke.
  - name: Search
    description: Cross-repo trigram search powering the topbar search field.
  - name: Promotions
    description: Move artifacts between repos with the CVE / license / signing gates applied at promotion time.
  - name: Build-Info
    description: 'JFrog-compatible build records: producing artifacts, consumed dependencies, CycloneDX SBOM export, build-keyed promotion.'
  - name: Detection
    description: Scan jobs, findings, suppressions and SLA-breach views.
  - name: Trash
    description: Soft-deleted repos and artifacts with a 30-day recovery window.
  - name: Me
    description: Current-user profile, SSH keys and notification preferences.
  - name: Advisories
    description: OSV / NVD advisory match views.
  - name: Backstage
    description: Backstage catalog bridge — Locations YAML, Component YAML, JSON browse view.
  - name: CNPG
    description: CloudNativePG ImageCatalog mirror — generated CR pulled by GitOps.
  - name: Geo-Sync
    description: Cross-instance replication ingest + outbox follow.
  - name: Self-Check
    description: Post-upgrade health probes consumed by `orbital check`.
  - name: Admin
    description: Operator-only endpoints — version, audit log, KPIs.
  - name: Admin/Security
    description: Security blocks, reverse-dependency search.
  - name: Admin/Branding
    description: Logo upload + delete.
  - name: Admin/Banners
    description: Scheduled customer banners.
  - name: Admin/Notifications
    description: SMTP, Teams and Slack notification channels.
  - name: Admin/Permissions
    description: Per-project role assignments.
  - name: Admin/Quotas
    description: Per-project storage quotas.
  - name: Admin/Backup
    description: S3 mirror status and backup-verification audit.
  - name: Admin/Imports
    description: JFrog / Nexus / GitHub / GitLab / Azure / AWS / GCP migration importers, plus bulk CSV/tarball import.
  - name: Admin/Webhooks
    description: Outbound webhook subscriptions.
  - name: Admin/Audit
    description: Admin audit log read.
  - name: Admin/License
    description: Licence envelope upload + status.
  - name: Admin/OIDC
    description: OIDC trust policies for GitLab CI and Kubernetes operator token-exchange.
  - name: Admin/Sigstore
    description: Sigstore (cosign keyless) trust policies.
  - name: Admin/Retention
    description: Org-wide retention policy view + audit.
  - name: Admin/PullGate
    description: Pull-gate policy engine.
  - name: Admin/CNPG
    description: CloudNativePG mirror admin surface.
  - name: Admin/Geo-Sync
    description: Cross-instance replication peer admin + failover.
  - name: Admin/Network
    description: Air-gapped egress allow-list + audit tail.
paths:
  /health/live:
    get:
      tags:
        - Health
      summary: Liveness probe
      description: Returns 200 once the process is past the binary's main(). Used by Kubernetes livenessProbe.
      operationId: healthLive
      responses:
        "200":
          description: Liveness ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
      security:
        - {}
  /health/ready:
    get:
      tags:
        - Health
      summary: Readiness probe
      description: Reports DB / Redis / S3 reachability. Returns 503 if any backing dependency is unreachable. Used by Kubernetes readinessProbe.
      operationId: healthReady
      responses:
        "200":
          description: All dependencies reachable
          content:
            application/json:
              schema:
                type: object
                properties:
                  postgres:
                    type: boolean
                  redis:
                    type: boolean
                  s3:
                    type: boolean
        "503":
          description: One or more backing dependencies are unreachable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
        - {}
  /metrics:
    get:
      tags:
        - Health
      summary: Prometheus metrics
      description: Prometheus exposition format. Scrape from a `ServiceMonitor`.
      operationId: healthMetrics
      responses:
        "200":
          description: Prometheus exposition
          content:
            text/plain:
              schema:
                type: string
      security:
        - {}
  /api/public-config:
    get:
      tags:
        - Public
      summary: Public deployment configuration
      description: Unauthenticated read of branding, version, the active customer banner and the air-gapped egress flags. The login screen polls this on first paint.
      operationId: publicConfig
      responses:
        "200":
          description: Public config envelope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicConfig'
      security:
        - {}
  /api/maintenance:
    get:
      tags:
        - Public
      summary: Maintenance-mode banner
      description: Returns whether maintenance mode is on, and if so, the operator-supplied banner text. The frontend renders the banner above the topbar.
      operationId: publicMaintenance
      responses:
        "200":
          description: Maintenance state
          content:
            application/json:
              schema:
                type: object
                properties:
                  active:
                    type: boolean
                  message:
                    type: string
                  started_at:
                    type: string
                    format: date-time
      security:
        - {}
  /branding/logo:
    get:
      tags:
        - Public
      summary: Branded logo bytes
      description: Returns the operator-uploaded PNG/JPEG/SVG logo. Cached aggressively via ETag + Cache-Control.
      operationId: publicLogo
      responses:
        "200":
          description: Logo bytes
          content:
            image/*:
              schema:
                type: string
                format: binary
        "404":
          description: No logo configured
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
        - {}
  /blocked/{id}:
    get:
      tags:
        - Public
      summary: Block-details landing page
      description: HTML page surfaced from the X-Orbital-Block-Details-URL header on a 403 from the artifact-serve hot path. Includes admin-authored block reason and customer message.
      operationId: publicBlocked
      parameters:
        - name: id
          in: path
          description: Security-block UUID
          required: true
          schema:
            type: string
      responses:
        "200":
          description: HTML block-details page
          content:
            text/html:
              schema:
                type: string
        "404":
          description: Block id not found or revoked
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
        - {}
  /auth/whoami:
    get:
      tags:
        - Auth
      summary: Current user
      description: Returns the session principal + roles. Frontend uses this to decide which sidebar sections to render. Returns 401 with no body if not signed in.
      operationId: authWhoami
      responses:
        "200":
          description: Session principal
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Whoami'
        "401":
          description: Not signed in
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /auth/login:
    get:
      tags:
        - Auth
      summary: Initiate SAML sign-in
      description: 302 to the configured IdP. Skipped when AUTH_DEV_BYPASS is set.
      operationId: authSAMLLogin
      responses:
        "302":
          description: Redirect to IdP
      security:
        - {}
  /auth/logout:
    get:
      tags:
        - Auth
      summary: End session
      description: Clears the session cookie and (for SAML) emits a SLO request to the IdP.
      operationId: authLogout
      responses:
        "302":
          description: Redirect to /login
  /auth/local/login:
    post:
      tags:
        - Auth
      summary: Local break-glass sign-in
      description: Email + password login when SAML / OIDC are misconfigured. Off by default — bootstrap via `ORBITALREG_BOOTSTRAP_ADMIN_EMAIL` / `ORBITALREG_BOOTSTRAP_ADMIN_PASSWORD`.
      operationId: authLocalLogin
      requestBody:
        description: Credentials
        content:
          application/json:
            schema:
              type: object
              properties:
                email:
                  type: string
                password:
                  type: string
              required:
                - email
                - password
        required: true
      responses:
        "204":
          description: Session cookie set
        "401":
          description: Invalid credentials
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
        - {}
  /auth/local/logout:
    post:
      tags:
        - Auth
      summary: End local session
      operationId: authLocalLogout
      responses:
        "204":
          description: Session cleared
  /auth/oidc/login:
    get:
      tags:
        - Auth
      summary: Initiate OIDC sign-in
      description: 302 to the configured OIDC issuer's authorization endpoint.
      operationId: authOIDCLogin
      responses:
        "302":
          description: Redirect to issuer
      security:
        - {}
  /auth/oidc/callback:
    get:
      tags:
        - Auth
      summary: OIDC callback
      description: Issuer redirects the browser here with an authorization code; OrbitalReg exchanges it for an ID token and mints a session cookie.
      operationId: authOIDCCallback
      parameters:
        - name: code
          in: query
          description: Authorization code
          schema:
            type: string
        - name: state
          in: query
          description: Opaque state cookie correlator
          schema:
            type: string
      responses:
        "302":
          description: Redirect to /
        "400":
          description: State / nonce mismatch
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
        - {}
  /api/oidc/exchange/gitlab:
    post:
      tags:
        - Auth
      summary: GitLab CI OIDC token exchange
      description: Trades a GitLab-issued `id_token` for a short-lived OrbitalReg service-account token. Used by `.gitlab-ci.yml` jobs to publish artifacts without storing a PAT.
      operationId: oidcExchangeGitLab
      requestBody:
        description: OIDC token-exchange request
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OIDCExchangeRequest'
        required: true
      responses:
        "200":
          description: Service-account token
          content:
            application/json:
              schema:
                type: object
                properties:
                  token:
                    type: string
                  expires_at:
                    type: string
                    format: date-time
        "400":
          description: Invalid request shape
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "401":
          description: Token verification failed (audience / issuer / subject mismatch)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
        - {}
  /api/oidc/exchange/kubernetes:
    post:
      tags:
        - Auth
      summary: Kubernetes operator OIDC token exchange
      description: Trades a projected Kubernetes ServiceAccount JWT for a short-lived OrbitalReg service-account token. Used by the in-cluster operator (PRODUCT-ROADMAP item 23 Phase F).
      operationId: oidcExchangeKubernetes
      requestBody:
        description: OIDC token-exchange request
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OIDCExchangeRequest'
        required: true
      responses:
        "200":
          description: Service-account token
          content:
            application/json:
              schema:
                type: object
                properties:
                  token:
                    type: string
                  expires_at:
                    type: string
                    format: date-time
        "400":
          description: Invalid request shape
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "401":
          description: Token verification failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
        - {}
  /api/v1/repos:
    get:
      tags:
        - Repos
      summary: List repositories
      description: Lists every repository the caller can see. Supports `?project_id=`, `?slug=`, `?format=` filters.
      operationId: repoList
      parameters:
        - name: project_id
          in: query
          description: Filter by project UUID
          schema:
            type: string
            format: uuid
        - name: slug
          in: query
          description: Filter by repo slug
          schema:
            type: string
        - name: format
          in: query
          description: Filter by package format
          schema:
            type: string
      responses:
        "200":
          description: Repository list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Repo'
    post:
      tags:
        - Repos
      summary: Create repository
      operationId: repoCreate
      requestBody:
        description: Repository definition
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RepoCreate'
        required: true
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Repo'
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "409":
          description: Slug collision within the project
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/v1/repos/{id}:
    get:
      tags:
        - Repos
      summary: Get repository
      operationId: repoGet
      parameters:
        - name: id
          in: path
          description: Repository UUID
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Repository
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Repo'
        "404":
          description: Not found or not visible
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    patch:
      tags:
        - Repos
      summary: Update repository
      description: Partial update; only fields explicitly present in the body are written. `id` and `projectId` are immutable.
      operationId: repoUpdate
      parameters:
        - name: id
          in: path
          description: Repository UUID
          required: true
          schema:
            type: string
      requestBody:
        description: Patch
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RepoPatch'
        required: true
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Repo'
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "403":
          description: Caller is not maintainer-or-higher
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    delete:
      tags:
        - Repos
      summary: Delete repository
      description: Soft-deletes the repository. The trash reaper purges artifacts after 30 days.
      operationId: repoDelete
      parameters:
        - name: id
          in: path
          description: Repository UUID
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Deleted
        "403":
          description: Caller is not maintainer-or-higher
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/v1/repos/{id}/artifacts:
    get:
      tags:
        - Repos
      summary: List artifacts in a repository
      operationId: repoListArtifacts
      parameters:
        - name: id
          in: path
          description: Repository UUID
          required: true
          schema:
            type: string
        - name: limit
          in: query
          description: Page size (max 1000)
          schema:
            type: integer
            format: int32
        - name: cursor
          in: query
          description: Opaque pagination cursor
          schema:
            type: string
      responses:
        "200":
          description: Page of artifacts
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/Artifact'
                  next_cursor:
                    type: string
  /api/v1/repos/{id}/stats:
    get:
      tags:
        - Repos
      summary: Per-repository stats dashboard
      description: Daily upload + pull counts for the trailing 90 days, top consumers (maintainer-or-higher only), top artifacts.
      operationId: repoStats
      parameters:
        - name: id
          in: path
          description: Repository UUID
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Stats envelope
          content:
            application/json:
              schema:
                type: object
  /api/v1/projects:
    get:
      tags:
        - Projects
      summary: List projects
      operationId: projectList
      parameters:
        - name: slug
          in: query
          description: Filter by slug
          schema:
            type: string
      responses:
        "200":
          description: Projects
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Project'
    post:
      tags:
        - Projects
      summary: Create project
      operationId: projectCreate
      requestBody:
        description: Project definition
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProjectCreate'
        required: true
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Project'
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "409":
          description: Slug collision
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/v1/projects/{id}:
    get:
      tags:
        - Projects
      summary: Get project
      operationId: projectGet
      parameters:
        - name: id
          in: path
          description: Project UUID
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Project
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Project'
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    patch:
      tags:
        - Projects
      summary: Update project
      description: In-place rename / description update. Slug rename is supported and preserves the project UUID, so retention policies and audit FKs stay stable.
      operationId: projectUpdate
      parameters:
        - name: id
          in: path
          description: Project UUID
          required: true
          schema:
            type: string
      requestBody:
        description: Patch
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProjectPatch'
        required: true
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Project'
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "403":
          description: Caller is not maintainer-or-higher
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "409":
          description: Slug collision
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    delete:
      tags:
        - Projects
      summary: Delete project
      operationId: projectDelete
      parameters:
        - name: id
          in: path
          description: Project UUID
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Deleted
        "403":
          description: Caller is not org_admin
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "409":
          description: Project still has repos
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/v1/projects/{id}/licenses:
    get:
      tags:
        - Projects
      summary: Project license aggregation
      description: Per-license counts across every artifact in the project. Powers the License-Compliance dashboard.
      operationId: projectLicensesAggregate
      parameters:
        - name: id
          in: path
          description: Project UUID
          required: true
          schema:
            type: string
      responses:
        "200":
          description: License rollup
          content:
            application/json:
              schema:
                type: object
  /api/v1/projects/{id}/license-policy:
    get:
      tags:
        - Projects
      summary: Read license policy
      operationId: projectLicensePolicyGet
      parameters:
        - name: id
          in: path
          description: Project UUID
          required: true
          schema:
            type: string
      responses:
        "200":
          description: License policy
          content:
            application/json:
              schema:
                type: object
    put:
      tags:
        - Projects
      summary: Upsert license policy
      description: Replaces the project's license-policy row. Maintainer-or-higher.
      operationId: projectLicensePolicyPut
      parameters:
        - name: id
          in: path
          description: Project UUID
          required: true
          schema:
            type: string
      requestBody:
        description: Policy
        content:
          application/json:
            schema:
              type: object
        required: true
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                type: object
        "403":
          description: Caller is not maintainer-or-higher
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/v1/projects/{id}/quarantine-settings:
    get:
      tags:
        - Projects
      summary: Read auto-quarantine settings
      operationId: projectQuarantineGet
      parameters:
        - name: id
          in: path
          description: Project UUID
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Settings
          content:
            application/json:
              schema:
                type: object
    put:
      tags:
        - Projects
      summary: Update auto-quarantine settings
      operationId: projectQuarantinePut
      parameters:
        - name: id
          in: path
          description: Project UUID
          required: true
          schema:
            type: string
      requestBody:
        description: Settings
        content:
          application/json:
            schema:
              type: object
        required: true
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                type: object
  /api/v1/projects/{project_id}/service_accounts:
    get:
      tags:
        - Projects
      summary: List service accounts
      operationId: saList
      parameters:
        - name: project_id
          in: path
          description: Project UUID
          required: true
          schema:
            type: string
        - name: name
          in: query
          description: Filter by SA name
          schema:
            type: string
      responses:
        "200":
          description: Service accounts
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
    post:
      tags:
        - Projects
      summary: Create service account
      operationId: saCreate
      parameters:
        - name: project_id
          in: path
          description: Project UUID
          required: true
          schema:
            type: string
      requestBody:
        description: SA definition
        content:
          application/json:
            schema:
              type: object
        required: true
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                type: object
  /api/v1/projects/{project_id}/service_accounts/{id}:
    patch:
      tags:
        - Projects
      summary: Update service account
      operationId: saUpdate
      parameters:
        - name: project_id
          in: path
          description: Project UUID
          required: true
          schema:
            type: string
        - name: id
          in: path
          description: Service-account UUID
          required: true
          schema:
            type: string
      requestBody:
        description: Patch
        content:
          application/json:
            schema:
              type: object
        required: true
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                type: object
    delete:
      tags:
        - Projects
      summary: Delete service account
      operationId: saDelete
      parameters:
        - name: project_id
          in: path
          description: Project UUID
          required: true
          schema:
            type: string
        - name: id
          in: path
          description: Service-account UUID
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Deleted
  /api/v1/artifacts/by-path:
    get:
      tags:
        - Artifacts
      summary: Look up artifact metadata by repo + path
      description: Returns the same row that `GET /artifacts/{repo_id}/{path}` would resolve before redirecting to S3, but as JSON metadata instead of a 302 to a presigned URL. Use this when a caller needs the artifact's format-specific extracted metadata (Maven GAV, npm name+version, PyPI filetype, …) without downloading the bytes. Soft-deleted artifacts return 410 Gone so callers can distinguish 'in trash; restorable' from 'never existed'.
      operationId: artifactByPath
      parameters:
        - name: repo_id
          in: query
          description: Repository UUID
          required: true
          schema:
            type: string
            format: uuid
        - name: path
          in: query
          description: Artifact path inside the repo
          required: true
          schema:
            type: string
        - name: version
          in: query
          description: Pin to a specific version; default = latest
          schema:
            type: string
      responses:
        "200":
          description: Artifact metadata
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ArtifactMetadata'
        "400":
          description: Missing repo_id or path query param
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "404":
          description: No artifact at (repo_id, path)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "410":
          description: Artifact or its repo is soft-deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/v1/artifacts/{repo_id}/{path}:
    get:
      tags:
        - Artifacts
      summary: Download artifact bytes
      description: Generic streaming download. Backs `orbital pull`. Returns a 302 to a presigned S3 URL when the deployment's S3 endpoint is browser-reachable.
      operationId: artifactPull
      parameters:
        - name: repo_id
          in: path
          description: Repository UUID
          required: true
          schema:
            type: string
        - name: path
          in: path
          description: Path inside the repo (URL-escaped)
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Artifact bytes
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "302":
          description: Redirect to presigned S3
        "403":
          description: Blocked by security policy
          headers:
            X-Orbital-Block-Id:
              description: Block UUID
              schema:
                type: string
            X-Orbital-Block-Reason:
              description: Operator-supplied reason
              schema:
                type: string
            X-Orbital-Block-Details-URL:
              description: Public block landing page
              schema:
                type: string
          content:
            text/plain:
              schema:
                type: string
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    put:
      tags:
        - Artifacts
      summary: Upload artifact bytes
      description: Generic streaming upload. Backs `orbital push`. The body is the artifact bytes; metadata files (maven-metadata.xml, index.yaml, ...) trigger the per-format adapter's re-index.
      operationId: artifactPush
      parameters:
        - name: repo_id
          in: path
          description: Repository UUID
          required: true
          schema:
            type: string
        - name: path
          in: path
          description: Path inside the repo
          required: true
          schema:
            type: string
        - name: Content-Length
          in: header
          description: Upload size in bytes
          schema:
            type: integer
            format: int64
      requestBody:
        description: Artifact bytes
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
        required: true
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Artifact'
        "402":
          description: Licence inactive — uploads disabled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "403":
          description: Blocked by security policy
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "413":
          description: Quota exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    delete:
      tags:
        - Artifacts
      summary: Soft-delete artifact
      operationId: artifactDelete
      parameters:
        - name: repo_id
          in: path
          description: Repository UUID
          required: true
          schema:
            type: string
        - name: path
          in: path
          description: Path inside the repo
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Soft-deleted; recoverable from /trash for 30 days
  /api/v1/artifact-deps:
    get:
      tags:
        - Artifacts
      summary: Dependency tree for an artifact
      operationId: artifactDeps
      parameters:
        - name: artifact_id
          in: query
          description: Root artifact UUID
          schema:
            type: string
            format: uuid
        - name: depth
          in: query
          description: Max walk depth (default 5)
          schema:
            type: integer
            format: int32
      responses:
        "200":
          description: Dependency tree
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DependencyTreeNode'
  /api/v1/artifact-builds/{id}:
    get:
      tags:
        - Artifacts
      summary: Build provenance for an artifact
      description: Returns the most-recent CI builds that produced this artifact. Project-visibility-gated so a sha256 shared across pipelines doesn't leak cross-project usage.
      operationId: artifactBuilds
      parameters:
        - name: id
          in: path
          description: Artifact UUID
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Build summaries
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/BuildInfoSummary'
  /api/v1/artifacts-sbom:
    get:
      tags:
        - Artifacts
      summary: Read artifact SBOM
      operationId: artifactSBOM
      parameters:
        - name: artifact_id
          in: query
          description: Artifact UUID
          schema:
            type: string
            format: uuid
        - name: format
          in: query
          description: 'Output format: cyclonedx | spdx'
          schema:
            type: string
            enum:
              - cyclonedx
              - spdx
      responses:
        "200":
          description: SBOM document
          content:
            application/json:
              schema:
                type: object
  /api/v1/tokens:
    get:
      tags:
        - Tokens
      summary: List my tokens
      description: Lists the caller's personal access tokens. Plaintext bytes are surfaced ONLY at issue time — the list view returns metadata + last-used timestamps.
      operationId: tokenList
      responses:
        "200":
          description: Tokens
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Token'
    post:
      tags:
        - Tokens
      summary: Issue a token
      description: Mints a new PAT. The plaintext value is returned exactly once. Scopes are bundle-driven (Pull / Publish / Admin) with optional format and repo restrictions.
      operationId: tokenIssue
      requestBody:
        description: Issue request
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TokenIssue'
        required: true
      responses:
        "201":
          description: Issued (plaintext returned once)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenIssueResult'
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/v1/tokens/{id}:
    delete:
      tags:
        - Tokens
      summary: Revoke token
      operationId: tokenRevoke
      parameters:
        - name: id
          in: path
          description: Token UUID
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Revoked
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/v1/search:
    get:
      tags:
        - Search
      summary: Cross-repo search
      description: Trigram-indexed search across artifact paths and repo names. Powers the topbar search field. Results are flat; the `kind` discriminator picks `artifact` vs `repo`.
      operationId: search
      parameters:
        - name: q
          in: query
          description: Query
          schema:
            type: string
        - name: limit
          in: query
          description: Max results (default 50)
          schema:
            type: integer
            format: int32
      responses:
        "200":
          description: Search results
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SearchResult'
  /api/v1/promotions:
    get:
      tags:
        - Promotions
      summary: List promotions
      operationId: promotionList
      parameters:
        - name: status
          in: query
          description: 'Filter by status: pending | executed | failed'
          schema:
            type: string
            enum:
              - pending
              - executed
              - failed
      responses:
        "200":
          description: Promotions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Promotion'
    post:
      tags:
        - Promotions
      summary: Create promotion
      operationId: promotionCreate
      requestBody:
        description: Promotion
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PromotionCreate'
        required: true
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Promotion'
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/v1/promotions/{id}/execute:
    post:
      tags:
        - Promotions
      summary: Execute pending promotion
      description: Runs the CVE / license / signing gates against the source artifact. On gate-block, the promotion row records the failure reason and the response 422s with the same reason.
      operationId: promotionExecute
      parameters:
        - name: id
          in: path
          description: Promotion UUID
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Executed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Promotion'
        "422":
          description: Blocked by promotion gate
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/build/:
    put:
      tags:
        - Build-Info
      summary: Submit build info (JFrog-compatible)
      description: Accepts a JFrog-compatible build-info JSON body. Used by `orbital build-info publish` and any CI lane that already speaks the JFrog shape.
      operationId: buildInfoSubmit
      requestBody:
        description: JFrog build-info envelope
        content:
          application/json:
            schema:
              type: object
        required: true
      responses:
        "204":
          description: Stored
        "400":
          description: Schema error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/v1/projects/{id}/build-info:
    get:
      tags:
        - Build-Info
      summary: List builds in a project
      operationId: buildInfoList
      parameters:
        - name: id
          in: path
          description: Project UUID
          required: true
          schema:
            type: string
        - name: name
          in: query
          description: Filter by build name
          schema:
            type: string
      responses:
        "200":
          description: Build list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/BuildInfoSummary'
  /api/v1/projects/{id}/build-info/{name}/{number}:
    get:
      tags:
        - Build-Info
      summary: Get build detail
      operationId: buildInfoGet
      parameters:
        - name: id
          in: path
          description: Project UUID
          required: true
          schema:
            type: string
        - name: name
          in: path
          description: Build name
          required: true
          schema:
            type: string
        - name: number
          in: path
          description: Build number
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Build detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BuildInfo'
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/v1/projects/{id}/build-info/{name}/{number}/promote:
    post:
      tags:
        - Build-Info
      summary: Promote every artifact in a build
      description: Walks the build's produced artifacts and creates one promotion row per (source repo, target repo) pair. `require_approval=true` keeps every spawned row pending so it routes through the CVE-policy-gated execute endpoint.
      operationId: buildInfoPromote
      parameters:
        - name: id
          in: path
          description: Project UUID
          required: true
          schema:
            type: string
        - name: name
          in: path
          description: Build name
          required: true
          schema:
            type: string
        - name: number
          in: path
          description: Build number
          required: true
          schema:
            type: string
      requestBody:
        description: Promotion request
        content:
          application/json:
            schema:
              type: object
              properties:
                target_repo_id:
                  type: string
                  format: uuid
                require_approval:
                  type: boolean
                note:
                  type: string
        required: true
      responses:
        "200":
          description: Spawned promotions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Promotion'
  /api/v1/projects/{id}/build-info/dependency-search:
    get:
      tags:
        - Build-Info
      summary: Reverse-dependency search
      description: Answers "which builds consumed log4j-2.14.1?". Project-scoped to keep cross-project provenance from leaking.
      operationId: buildInfoDepSearch
      parameters:
        - name: id
          in: path
          description: Project UUID
          required: true
          schema:
            type: string
        - name: q
          in: query
          description: Query string
          schema:
            type: string
        - name: mode
          in: query
          description: 'Match mode: coord | prefix | sha256'
          schema:
            type: string
            enum:
              - coord
              - prefix
              - sha256
      responses:
        "200":
          description: Builds matching
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/BuildInfoSummary'
  /api/v1/projects/{id}/build-info/{name}/{number}/sbom:
    get:
      tags:
        - Build-Info
      summary: Export build SBOM
      description: CycloneDX 1.5 BOM aggregating produced artifacts and consumed dependencies with synthesised purls + SHA-256 hashes.
      operationId: buildInfoSBOM
      parameters:
        - name: id
          in: path
          description: Project UUID
          required: true
          schema:
            type: string
        - name: name
          in: path
          description: Build name
          required: true
          schema:
            type: string
        - name: number
          in: path
          description: Build number
          required: true
          schema:
            type: string
      responses:
        "200":
          description: CycloneDX SBOM
          content:
            application/json:
              schema:
                type: object
  /api/v1/detection/findings:
    get:
      tags:
        - Detection
      summary: List scan findings
      operationId: detectionFindings
      parameters:
        - name: artifact_id
          in: query
          description: Filter by artifact UUID
          schema:
            type: string
            format: uuid
        - name: severity
          in: query
          description: Min severity
          schema:
            type: string
            enum:
              - low
              - medium
              - high
              - critical
        - name: status
          in: query
          description: open | suppressed | resolved
          schema:
            type: string
      responses:
        "200":
          description: Findings
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ScanFinding'
  /api/v1/detection/findings/{id}/suppress:
    post:
      tags:
        - Detection
      summary: Suppress finding
      operationId: detectionSuppress
      parameters:
        - name: id
          in: path
          description: Finding UUID
          required: true
          schema:
            type: string
      requestBody:
        description: Suppression
        content:
          application/json:
            schema:
              type: object
              properties:
                until:
                  type: string
                  format: date-time
                reason:
                  type: string
        required: true
      responses:
        "204":
          description: Suppressed
  /api/v1/me:
    get:
      tags:
        - Me
      summary: Read my profile
      operationId: meGet
      responses:
        "200":
          description: Profile
          content:
            application/json:
              schema:
                type: object
    patch:
      tags:
        - Me
      summary: Update my profile
      operationId: meUpdate
      requestBody:
        description: Profile patch
        content:
          application/json:
            schema:
              type: object
        required: true
      responses:
        "200":
          description: Profile
          content:
            application/json:
              schema:
                type: object
  /api/v1/me/ssh-keys:
    get:
      tags:
        - Me
      summary: List my SSH keys
      operationId: meSSHList
      responses:
        "200":
          description: Keys
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
    post:
      tags:
        - Me
      summary: Add SSH key
      operationId: meSSHAdd
      requestBody:
        description: Key
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                key:
                  type: string
        required: true
      responses:
        "201":
          description: Added
          content:
            application/json:
              schema:
                type: object
  /api/v1/me/ssh-keys/{id}:
    delete:
      tags:
        - Me
      summary: Remove SSH key
      operationId: meSSHRemove
      parameters:
        - name: id
          in: path
          description: Key UUID
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Removed
  /api/v1/trash:
    get:
      tags:
        - Trash
      summary: List soft-deleted artifacts and repos
      operationId: trashList
      responses:
        "200":
          description: Trash entries
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
  /api/v1/trash/{id}/restore:
    post:
      tags:
        - Trash
      summary: Restore from trash
      operationId: trashRestore
      parameters:
        - name: id
          in: path
          description: Trash entry UUID
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Restored
        "410":
          description: Past 30-day window — purged
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/v1/advisories:
    get:
      tags:
        - Advisories
      summary: List advisory matches
      operationId: advisoriesList
      parameters:
        - name: artifact_id
          in: query
          description: Filter by artifact UUID
          schema:
            type: string
            format: uuid
        - name: severity
          in: query
          description: Min severity
          schema:
            type: string
      responses:
        "200":
          description: Matches
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
  /api/v1/backstage/catalog-info:
    get:
      tags:
        - Backstage
      summary: Backstage catalog YAML
      description: Multi-document YAML stream of Location + Component entities. Configure Backstage's static-location plugin to poll this URL.
      operationId: backstageCatalog
      responses:
        "200":
          description: Catalog YAML
          content:
            application/yaml:
              schema:
                type: string
  /api/v1/backstage/component:
    get:
      tags:
        - Backstage
      summary: Backstage Component YAML
      operationId: backstageComponent
      parameters:
        - name: repo_id
          in: query
          description: Repository UUID
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Component YAML
          content:
            application/yaml:
              schema:
                type: string
  /api/v1/backstage/components:
    get:
      tags:
        - Backstage
      summary: Backstage components (JSON)
      description: JSON sibling of `/backstage/catalog-info` consumed by the standalone OrbitalRegCatalogPage browse view in the Backstage plugin.
      operationId: backstageComponentsJSON
      responses:
        "200":
          description: Components
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
  /api/v1/cnpg/catalog.yaml:
    get:
      tags:
        - CNPG
      summary: Generated CNPG ImageCatalog CR
      description: Read-only generated ClusterImageCatalog CR. A GitOps repo can pull this with a service-account token and apply with `kubectl apply -f`.
      operationId: cnpgCatalogYAML
      responses:
        "200":
          description: ClusterImageCatalog CR
          content:
            application/yaml:
              schema:
                type: string
  /api/v1/geo/ingest:
    head:
      tags:
        - Geo-Sync
      summary: Test-connection probe
      description: Used by the admin "Test connection" button on the peer config form. Returns 204 when the bearer token resolves to an org_admin SA.
      operationId: geoIngestProbe
      responses:
        "204":
          description: Reachable
    post:
      tags:
        - Geo-Sync
      summary: Ingest replication events
      description: Inbound side of the cross-instance replication channel. Accepts an array of events and returns a per-decision count envelope (applied / skipped / conflict / loop / error).
      operationId: geoIngest
      requestBody:
        description: Outbox events
        content:
          application/json:
            schema:
              type: array
              items:
                type: object
        required: true
      responses:
        "200":
          description: Decision counts
          content:
            application/json:
              schema:
                type: object
        "401":
          description: Bearer token rejected or not org_admin
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/v1/geo/outbox/since:
    get:
      tags:
        - Geo-Sync
      summary: Read remote outbox tail
      description: Read by remote pull peers — returns outbox rows with id > the supplied cursor.
      operationId: geoOutboxSince
      parameters:
        - name: cursor
          in: query
          description: Last seen outbox id
          schema:
            type: integer
            format: int64
        - name: limit
          in: query
          description: Page size (default 200)
          schema:
            type: integer
            format: int32
      responses:
        "200":
          description: Outbox rows
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
  /api/v1/check/migration-status:
    get:
      tags:
        - Self-Check
      summary: Schema-migrations status
      description: Returns the current schema_migrations row (version + dirty flag). A dirty=true response means a forward migration crashed mid-flight and the binary needs manual repair before it should serve write traffic.
      operationId: checkMigrationStatus
      responses:
        "200":
          description: Migration status
          content:
            application/json:
              schema:
                type: object
                properties:
                  version:
                    type: integer
                    format: int64
                  dirty:
                    type: boolean
                  present:
                    type: boolean
  /api/v1/check/run-suite:
    get:
      tags:
        - Self-Check
      summary: Run the post-upgrade probe suite
      description: Drives every probe in the requested suites and returns a typed report envelope (probes + summary). The `orbital check` CLI is the canonical consumer — a CI script can also `curl … | jq '.summary.error'` to gate a deploy.
      operationId: checkRunSuite
      parameters:
        - name: suites
          in: query
          description: Comma-separated suite list (one of core, security, scanners, storage, importers, replication; default = all)
          schema:
            type: string
        - name: quick
          in: query
          description: Skip the slow probes (storage round-trip, audit-write smoke). Default false.
          schema:
            type: boolean
      responses:
        "200":
          description: Probe report
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint:
                    type: string
                  version:
                    type: string
                  build_time:
                    type: string
                  commit:
                    type: string
                  suites:
                    type: array
                    items:
                      type: string
                  quick:
                    type: boolean
                  started_at:
                    type: string
                    format: date-time
                  probes:
                    type: array
                    items:
                      type: object
                      properties:
                        suite:
                          type: string
                        name:
                          type: string
                        status:
                          type: string
                          enum:
                            - ok
                            - warn
                            - error
                            - skipped
                        message:
                          type: string
                        duration_ms:
                          type: integer
                          format: int64
                        details:
                          type: object
                  summary:
                    type: object
                    properties:
                      ok:
                        type: integer
                        format: int32
                      warn:
                        type: integer
                        format: int32
                      error:
                        type: integer
                        format: int32
                      skipped:
                        type: integer
                        format: int32
  /api/admin/version:
    get:
      tags:
        - Admin
      summary: Build version + commit + build time
      operationId: adminVersion
      responses:
        "200":
          description: Version envelope
          content:
            application/json:
              schema:
                type: object
                properties:
                  version:
                    type: string
                  git_commit:
                    type: string
                  built_at:
                    type: string
                    format: date-time
                  go_version:
                    type: string
  /api/admin/kpis:
    get:
      tags:
        - Admin
      summary: Admin overview KPIs
      operationId: adminKPIs
      responses:
        "200":
          description: KPI envelope
          content:
            application/json:
              schema:
                type: object
  /api/admin/security:
    get:
      tags:
        - Admin/Security
      summary: List security blocks
      operationId: adminSecurityList
      responses:
        "200":
          description: Blocks
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SecurityBlock'
    post:
      tags:
        - Admin/Security
      summary: Create security block
      operationId: adminSecurityCreate
      requestBody:
        description: Block
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SecurityBlockCreate'
        required: true
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SecurityBlock'
  /api/admin/security/{id}:
    patch:
      tags:
        - Admin/Security
      summary: Update block metadata
      description: 'Mutable: reason / customer_message / cve_id / blocked_until / active. Match-expression fields force destroy/recreate to keep the audit-log invariant intact.'
      operationId: adminSecurityPatch
      parameters:
        - name: id
          in: path
          description: Block UUID
          required: true
          schema:
            type: string
      requestBody:
        description: Patch
        content:
          application/json:
            schema:
              type: object
        required: true
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SecurityBlock'
    delete:
      tags:
        - Admin/Security
      summary: Revoke block
      operationId: adminSecurityRevoke
      parameters:
        - name: id
          in: path
          description: Block UUID
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Revoked
  /api/admin/security/pulls:
    get:
      tags:
        - Admin/Security
      summary: Reverse-dependency pull search
      description: '"Which devs pulled this vulnerable artifact?" — joins `artifact_pulls` with `scan_findings`.'
      operationId: adminSecurityPulls
      parameters:
        - name: artifact_id
          in: query
          description: Filter by artifact UUID
          schema:
            type: string
            format: uuid
        - name: since
          in: query
          description: Pulls since timestamp
          schema:
            type: string
            format: date-time
      responses:
        "200":
          description: Pull rows
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
  /api/admin/branding/logo:
    post:
      tags:
        - Admin/Branding
      summary: Upload logo
      description: Accepts PNG / JPEG / SVG. The handler content-sniffs the bytes, decodes PNG/JPEG via image.DecodeConfig to enforce a 16x16…4096x1024 envelope, and sanitises SVG against script vectors.
      operationId: adminBrandingUpload
      requestBody:
        description: Logo bytes
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
        required: true
      responses:
        "204":
          description: Stored
        "400":
          description: Invalid format / dimensions
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    delete:
      tags:
        - Admin/Branding
      summary: Remove logo
      operationId: adminBrandingDelete
      responses:
        "204":
          description: Removed
  /api/admin/banners:
    get:
      tags:
        - Admin/Banners
      summary: List scheduled banners
      operationId: adminBannerList
      responses:
        "200":
          description: Banners
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Banner'
    post:
      tags:
        - Admin/Banners
      summary: Schedule banner
      operationId: adminBannerCreate
      requestBody:
        description: Banner
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Banner'
        required: true
      responses:
        "201":
          description: Scheduled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Banner'
  /api/admin/banners/{id}:
    delete:
      tags:
        - Admin/Banners
      summary: Remove banner
      operationId: adminBannerDelete
      parameters:
        - name: id
          in: path
          description: Banner UUID
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Removed
  /api/admin/notifications:
    get:
      tags:
        - Admin/Notifications
      summary: Read SMTP settings
      operationId: adminNotificationsGet
      responses:
        "200":
          description: SMTP settings
          content:
            application/json:
              schema:
                type: object
    put:
      tags:
        - Admin/Notifications
      summary: Update SMTP settings
      operationId: adminNotificationsPut
      requestBody:
        description: SMTP settings
        content:
          application/json:
            schema:
              type: object
        required: true
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                type: object
  /api/admin/notifications/test:
    post:
      tags:
        - Admin/Notifications
      summary: Send test email
      operationId: adminNotificationsTest
      requestBody:
        description: Test target
        content:
          application/json:
            schema:
              type: object
              properties:
                to:
                  type: string
        required: true
      responses:
        "204":
          description: Queued
  /api/admin/notification-channels:
    get:
      tags:
        - Admin/Notifications
      summary: List notification channels
      operationId: adminChannelList
      responses:
        "200":
          description: Channels
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/NotificationChannel'
    post:
      tags:
        - Admin/Notifications
      summary: Create notification channel
      operationId: adminChannelCreate
      requestBody:
        description: Channel
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NotificationChannel'
        required: true
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotificationChannel'
  /api/admin/notification-channels/{id}:
    delete:
      tags:
        - Admin/Notifications
      summary: Delete notification channel
      operationId: adminChannelDelete
      parameters:
        - name: id
          in: path
          description: Channel UUID
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Deleted
  /api/admin/permissions:
    get:
      tags:
        - Admin/Permissions
      summary: List per-project permissions
      operationId: adminPermissionsList
      parameters:
        - name: project_id
          in: query
          description: Filter by project UUID
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Permissions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Permission'
    post:
      tags:
        - Admin/Permissions
      summary: Grant permission
      operationId: adminPermissionsGrant
      requestBody:
        description: Permission
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Permission'
        required: true
      responses:
        "201":
          description: Granted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Permission'
  /api/admin/permissions/{id}:
    delete:
      tags:
        - Admin/Permissions
      summary: Revoke permission
      operationId: adminPermissionsRevoke
      parameters:
        - name: id
          in: path
          description: Permission UUID
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Revoked
  /api/admin/quotas:
    get:
      tags:
        - Admin/Quotas
      summary: List quotas
      operationId: adminQuotasList
      responses:
        "200":
          description: Quotas
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Quota'
  /api/admin/quotas/{project_id}:
    put:
      tags:
        - Admin/Quotas
      summary: Set quota
      operationId: adminQuotasSet
      parameters:
        - name: project_id
          in: path
          description: Project UUID
          required: true
          schema:
            type: string
      requestBody:
        description: Quota
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Quota'
        required: true
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Quota'
  /api/admin/backup:
    get:
      tags:
        - Admin/Backup
      summary: Backup mirror status
      operationId: adminBackupStatus
      responses:
        "200":
          description: Status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BackupStatus'
  /api/admin/backup-verifications:
    get:
      tags:
        - Admin/Backup
      summary: Backup-verification audit
      operationId: adminBackupVerifications
      responses:
        "200":
          description: Verification runs
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/BackupVerification'
  /api/admin/imports:
    get:
      tags:
        - Admin/Imports
      summary: List import jobs
      description: Backs the AdminImportsHub. The `?source_type=` filter switches between the JFrog / Nexus / GitHub / GitLab / Azure / AWS / GCP tabs.
      operationId: adminImportsList
      parameters:
        - name: source_type
          in: query
          description: 'Filter: jfrog | nexus | github | gitlab | azure | awscodeartifact | gcparhybrid'
          schema:
            type: string
      responses:
        "200":
          description: Import jobs
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ImportJob'
    post:
      tags:
        - Admin/Imports
      summary: Create import job
      operationId: adminImportsCreate
      requestBody:
        description: Import job
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ImportJob'
        required: true
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ImportJob'
  /api/admin/imports/{id}:
    delete:
      tags:
        - Admin/Imports
      summary: Cancel import job
      operationId: adminImportsCancel
      parameters:
        - name: id
          in: path
          description: Import job UUID
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Cancelled
  /api/admin/bulk-imports:
    get:
      tags:
        - Admin/Imports
      summary: List bulk-import jobs
      operationId: adminBulkImportsList
      responses:
        "200":
          description: Bulk import jobs
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
    post:
      tags:
        - Admin/Imports
      summary: Submit bulk import
      description: Accepts a manifest CSV (Phase 1) or a tarball (Phase 2). The dispatcher runs queue workers backed by FOR UPDATE SKIP LOCKED.
      operationId: adminBulkImportsSubmit
      requestBody:
        description: CSV or tarball bytes
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
        required: true
      responses:
        "202":
          description: Accepted
          content:
            application/json:
              schema:
                type: object
  /api/admin/webhooks:
    get:
      tags:
        - Admin/Webhooks
      summary: List webhook subscriptions
      operationId: adminWebhookList
      responses:
        "200":
          description: Subscriptions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/WebhookSubscription'
    post:
      tags:
        - Admin/Webhooks
      summary: Create webhook subscription
      description: 'The HMAC shared secret is write-only — list responses return only `has_secret: bool`.'
      operationId: adminWebhookCreate
      requestBody:
        description: Subscription
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookSubscriptionCreate'
        required: true
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookSubscription'
  /api/admin/webhooks/{id}:
    patch:
      tags:
        - Admin/Webhooks
      summary: Update subscription
      operationId: adminWebhookPatch
      parameters:
        - name: id
          in: path
          description: Subscription UUID
          required: true
          schema:
            type: string
      requestBody:
        description: Patch
        content:
          application/json:
            schema:
              type: object
        required: true
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookSubscription'
    delete:
      tags:
        - Admin/Webhooks
      summary: Delete subscription
      operationId: adminWebhookDelete
      parameters:
        - name: id
          in: path
          description: Subscription UUID
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Deleted
  /api/admin/audit:
    get:
      tags:
        - Admin/Audit
      summary: Read audit log
      operationId: adminAuditList
      parameters:
        - name: actor
          in: query
          description: Filter by actor email
          schema:
            type: string
        - name: since
          in: query
          description: ISO8601 lower bound
          schema:
            type: string
            format: date-time
        - name: limit
          in: query
          description: Page size (default 200)
          schema:
            type: integer
            format: int32
      responses:
        "200":
          description: Audit events
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AuditEvent'
  /api/admin/license:
    get:
      tags:
        - Admin/License
      summary: Read licence status
      description: Always reachable — never gated by the licence-expiry middleware so an operator can recover from an inactive licence.
      operationId: adminLicenseGet
      responses:
        "200":
          description: Licence envelope
          content:
            application/json:
              schema:
                type: object
    put:
      tags:
        - Admin/License
      summary: Upload licence envelope
      operationId: adminLicensePut
      requestBody:
        description: Signed envelope
        content:
          application/json:
            schema:
              type: object
        required: true
      responses:
        "204":
          description: Accepted
        "400":
          description: Invalid signature / shape
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/admin/license/audit-export:
    get:
      tags:
        - Admin/License
      summary: Signed audit-export envelope
      description: Returns an Ed25519-signed snapshot of the installation's identity + footprint (install_id, fingerprints, artifact counters). Customers send the file to support@orbitalreg.com on request; the licence-issuer verifies it offline. See EULA §Audit & Verification.
      operationId: adminLicenseAuditExport
      responses:
        "200":
          description: text/plain envelope (ORBREG-AUDIT-<…>.<…>)
        "500":
          description: Build / sign failure
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/admin/oidc/policies:
    get:
      tags:
        - Admin/OIDC
      summary: List OIDC trust policies
      operationId: adminOIDCList
      responses:
        "200":
          description: Policies
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/OIDCTrustPolicy'
    post:
      tags:
        - Admin/OIDC
      summary: Create OIDC trust policy
      operationId: adminOIDCCreate
      requestBody:
        description: Policy
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OIDCTrustPolicy'
        required: true
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OIDCTrustPolicy'
  /api/admin/oidc/policies/{id}:
    patch:
      tags:
        - Admin/OIDC
      summary: Update OIDC trust policy
      operationId: adminOIDCPatch
      parameters:
        - name: id
          in: path
          description: Policy UUID
          required: true
          schema:
            type: string
      requestBody:
        description: Patch
        content:
          application/json:
            schema:
              type: object
        required: true
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OIDCTrustPolicy'
    delete:
      tags:
        - Admin/OIDC
      summary: Delete OIDC trust policy
      operationId: adminOIDCDelete
      parameters:
        - name: id
          in: path
          description: Policy UUID
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Deleted
  /api/admin/sigstore/policies:
    get:
      tags:
        - Admin/Sigstore
      summary: List Sigstore trust policies
      operationId: adminSigstoreList
      responses:
        "200":
          description: Policies
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SigstoreTrustPolicy'
    post:
      tags:
        - Admin/Sigstore
      summary: Create Sigstore trust policy
      operationId: adminSigstoreCreate
      requestBody:
        description: Policy
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SigstoreTrustPolicy'
        required: true
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SigstoreTrustPolicy'
  /api/admin/sigstore/policies/{id}:
    delete:
      tags:
        - Admin/Sigstore
      summary: Delete Sigstore trust policy
      operationId: adminSigstoreDelete
      parameters:
        - name: id
          in: path
          description: Policy UUID
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Deleted
  /api/admin/retention:
    get:
      tags:
        - Admin/Retention
      summary: Org-wide retention overview
      description: Every retention policy across every repo, the recent retention_audit tail, and the sweeper-health summary.
      operationId: adminRetentionList
      responses:
        "200":
          description: Retention overview
          content:
            application/json:
              schema:
                type: object
  /api/admin/repos/{id}/retention:
    get:
      tags:
        - Admin/Retention
      summary: List per-repo retention policies
      operationId: adminRetentionRepoList
      parameters:
        - name: id
          in: path
          description: Repository UUID
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Policies
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/RetentionPolicy'
    post:
      tags:
        - Admin/Retention
      summary: Create retention policy
      operationId: adminRetentionCreate
      parameters:
        - name: id
          in: path
          description: Repository UUID
          required: true
          schema:
            type: string
      requestBody:
        description: Policy
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RetentionPolicy'
        required: true
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RetentionPolicy'
  /api/admin/pull-gate:
    get:
      tags:
        - Admin/PullGate
      summary: Read pull-gate engine settings
      operationId: adminPullGateGet
      responses:
        "200":
          description: Settings + policies envelope
          content:
            application/json:
              schema:
                type: object
                properties:
                  settings:
                    $ref: '#/components/schemas/PullGateSettings'
                  policies:
                    type: array
                    items:
                      $ref: '#/components/schemas/PullGatePolicy'
  /api/admin/pull-gate/settings:
    put:
      tags:
        - Admin/PullGate
      summary: Update pull-gate settings
      operationId: adminPullGateSettingsPut
      requestBody:
        description: Settings
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PullGateSettings'
        required: true
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PullGateSettings'
  /api/admin/pull-gate/policies:
    post:
      tags:
        - Admin/PullGate
      summary: Create pull-gate policy
      operationId: adminPullGatePolicyCreate
      requestBody:
        description: Policy
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PullGatePolicy'
        required: true
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PullGatePolicy'
  /api/admin/pull-gate/policies/{id}:
    delete:
      tags:
        - Admin/PullGate
      summary: Delete pull-gate policy
      operationId: adminPullGatePolicyDelete
      parameters:
        - name: id
          in: path
          description: Policy UUID
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Deleted
  /api/admin/pull-gate/audit:
    get:
      tags:
        - Admin/PullGate
      summary: Pull-gate audit tail
      operationId: adminPullGateAudit
      parameters:
        - name: decision
          in: query
          description: allow | block | bypass
          schema:
            type: string
      responses:
        "200":
          description: Audit rows
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
  /api/admin/pull-gate/simulate:
    post:
      tags:
        - Admin/PullGate
      summary: Simulation preview
      description: Runs the engine against the most-recent 200 artifacts in simulate mode so the admin UI can answer "if enabled now, X% would be blocked".
      operationId: adminPullGateSimulate
      responses:
        "200":
          description: Simulation report
          content:
            application/json:
              schema:
                type: object
  /api/admin/cnpg:
    get:
      tags:
        - Admin/CNPG
      summary: CNPG mirror settings + catalog
      operationId: adminCNPGGet
      responses:
        "200":
          description: Settings + rows envelope
          content:
            application/json:
              schema:
                type: object
  /api/admin/cnpg/settings:
    put:
      tags:
        - Admin/CNPG
      summary: Update CNPG mirror settings
      operationId: adminCNPGSettingsPut
      requestBody:
        description: Settings
        content:
          application/json:
            schema:
              type: object
        required: true
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                type: object
  /api/admin/cnpg/sync:
    post:
      tags:
        - Admin/CNPG
      summary: Trigger manual sync
      operationId: adminCNPGSync
      responses:
        "202":
          description: Sync started
          content:
            application/json:
              schema:
                type: object
  /api/admin/geo:
    get:
      tags:
        - Admin/Geo-Sync
      summary: Geo settings + peers + outbox tail
      operationId: adminGeoGet
      responses:
        "200":
          description: Geo envelope
          content:
            application/json:
              schema:
                type: object
                properties:
                  settings:
                    $ref: '#/components/schemas/GeoSettings'
                  peers:
                    type: array
                    items:
                      $ref: '#/components/schemas/GeoPeer'
  /api/admin/geo/settings:
    put:
      tags:
        - Admin/Geo-Sync
      summary: Update geo settings
      operationId: adminGeoSettingsPut
      requestBody:
        description: Settings
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GeoSettings'
        required: true
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GeoSettings'
  /api/admin/geo/peers:
    post:
      tags:
        - Admin/Geo-Sync
      summary: Add peer
      operationId: adminGeoPeerCreate
      requestBody:
        description: Peer
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GeoPeer'
        required: true
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GeoPeer'
  /api/admin/geo/peers/{id}:
    patch:
      tags:
        - Admin/Geo-Sync
      summary: Update peer
      operationId: adminGeoPeerPatch
      parameters:
        - name: id
          in: path
          description: Peer UUID
          required: true
          schema:
            type: string
      requestBody:
        description: Patch
        content:
          application/json:
            schema:
              type: object
        required: true
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GeoPeer'
    delete:
      tags:
        - Admin/Geo-Sync
      summary: Remove peer
      operationId: adminGeoPeerDelete
      parameters:
        - name: id
          in: path
          description: Peer UUID
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Removed
  /api/admin/geo/peers/{id}/pause:
    post:
      tags:
        - Admin/Geo-Sync
      summary: Pause peer
      description: Toggles `enabled` to FALSE while preserving the per-peer cursor so a maintenance window doesn't replay the entire outbox on resume.
      operationId: adminGeoPeerPause
      parameters:
        - name: id
          in: path
          description: Peer UUID
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Paused
  /api/admin/geo/peers/{id}/resume:
    post:
      tags:
        - Admin/Geo-Sync
      summary: Resume peer
      operationId: adminGeoPeerResume
      parameters:
        - name: id
          in: path
          description: Peer UUID
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Resumed
  /api/admin/geo/peers/{id}/flip:
    post:
      tags:
        - Admin/Geo-Sync
      summary: Flip peer direction
      description: Swap push <-> pull for one-click promotion / demotion. Bidir is rejected with 400 because the worker already runs both tracks.
      operationId: adminGeoPeerFlip
      parameters:
        - name: id
          in: path
          description: Peer UUID
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Flipped
        "400":
          description: Direction is bidir
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/admin/geo/apply-audit:
    get:
      tags:
        - Admin/Geo-Sync
      summary: Receive-side apply audit
      operationId: adminGeoApplyAudit
      responses:
        "200":
          description: Apply rows
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
  /api/admin/network:
    get:
      tags:
        - Admin/Network
      summary: Read air-gapped network settings
      operationId: adminNetworkGet
      responses:
        "200":
          description: Network settings
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NetworkSettings'
    put:
      tags:
        - Admin/Network
      summary: Update network settings
      operationId: adminNetworkPut
      requestBody:
        description: Settings
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NetworkSettings'
        required: true
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NetworkSettings'
  /api/admin/network/audit:
    get:
      tags:
        - Admin/Network
      summary: Egress audit tail
      operationId: adminNetworkAudit
      responses:
        "200":
          description: Egress rows
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
components:
  schemas:
    Repo:
      type: object
      properties:
        id:
          type: string
          format: uuid
        projectId:
          type: string
          format: uuid
        slug:
          type: string
        name:
          type: string
        kind:
          type: string
          enum:
            - local
            - remote
            - virtual
        format:
          type: string
          description: 'Package format: maven | npm | pypi | helm | docker | …'
        upstreamUrl:
          type: string
        members:
          type: array
          description: 'For virtual repos: ordered member repo UUIDs.'
          items:
            type: string
            format: uuid
      required:
        - id
        - projectId
        - slug
        - name
        - kind
        - format
    RepoCreate:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
        slug:
          type: string
        name:
          type: string
        kind:
          type: string
          enum:
            - local
            - remote
            - virtual
        format:
          type: string
        upstreamUrl:
          type: string
        members:
          type: array
          items:
            type: string
            format: uuid
      required:
        - projectId
        - slug
        - name
        - kind
        - format
    RepoPatch:
      type: object
      properties:
        name:
          type: string
        slug:
          type: string
        upstreamUrl:
          type: string
        members:
          type: array
          items:
            type: string
            format: uuid
    Project:
      type: object
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
        name:
          type: string
        description:
          type: string
        created_at:
          type: string
          format: date-time
      required:
        - id
        - slug
        - name
    ProjectCreate:
      type: object
      properties:
        slug:
          type: string
        name:
          type: string
        description:
          type: string
      required:
        - slug
        - name
    ProjectPatch:
      type: object
      properties:
        slug:
          type: string
        name:
          type: string
        description:
          type: string
    Artifact:
      type: object
      properties:
        id:
          type: string
          format: uuid
        repo_id:
          type: string
          format: uuid
        path:
          type: string
        version:
          type: string
        size:
          type: integer
          format: int64
        sha256:
          type: string
        content_type:
          type: string
        created_at:
          type: string
          format: date-time
      required:
        - id
        - repo_id
        - path
        - size
        - sha256
    ArtifactMetadata:
      type: object
      properties:
        id:
          type: string
          format: uuid
        repo_id:
          type: string
          format: uuid
        path:
          type: string
        version:
          type: string
        size:
          type: integer
          format: int64
        sha256:
          type: string
        content_type:
          type: string
        metadata:
          type: object
          description: Format-specific extracted fields (Maven GAV, npm name+version, PyPI filetype, …). Empty object when the upload path didn't run a format adapter.
        uploaded_at:
          type: string
          format: date-time
      required:
        - id
        - repo_id
        - path
        - size
        - sha256
        - metadata
        - uploaded_at
    Token:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        scopes:
          type: array
          items:
            type: string
        format_filter:
          type: array
          items:
            type: string
        repo_filter:
          type: array
          items:
            type: string
            format: uuid
        last_used_at:
          type: string
          format: date-time
        expires_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
      required:
        - id
        - name
        - scopes
        - created_at
    TokenIssue:
      type: object
      properties:
        name:
          type: string
        bundle:
          type: string
          enum:
            - pull
            - publish
            - admin
        format_filter:
          type: array
          items:
            type: string
        repo_filter:
          type: array
          items:
            type: string
            format: uuid
        expires_in_days:
          type: integer
          format: int32
      required:
        - name
        - bundle
    TokenIssueResult:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        plaintext:
          type: string
          description: The orbr_… token string. Returned exactly once at issue time.
        scopes:
          type: array
          items:
            type: string
        expires_at:
          type: string
          format: date-time
    SearchResult:
      type: object
      properties:
        kind:
          type: string
          enum:
            - artifact
            - repo
        id:
          type: string
          format: uuid
        label:
          type: string
        sublabel:
          type: string
        href:
          type: string
    Promotion:
      type: object
      properties:
        id:
          type: string
          format: uuid
        src_repo_id:
          type: string
          format: uuid
        dst_repo_id:
          type: string
          format: uuid
        artifact_id:
          type: string
          format: uuid
        status:
          type: string
          enum:
            - pending
            - executed
            - failed
        failure_reason:
          type: string
        created_at:
          type: string
          format: date-time
        executed_at:
          type: string
          format: date-time
    PromotionCreate:
      type: object
      properties:
        src_repo_id:
          type: string
          format: uuid
        dst_repo_id:
          type: string
          format: uuid
        artifact_id:
          type: string
          format: uuid
        require_approval:
          type: boolean
        note:
          type: string
      required:
        - src_repo_id
        - dst_repo_id
        - artifact_id
    BuildInfo:
      type: object
      properties:
        name:
          type: string
        number:
          type: string
        started:
          type: string
          format: date-time
        vcs_url:
          type: string
        vcs_revision:
          type: string
        modules:
          type: array
          items:
            type: object
        properties:
          type: object
          additionalProperties:
            type: string
    BuildInfoSummary:
      type: object
      properties:
        project_id:
          type: string
          format: uuid
        name:
          type: string
        number:
          type: string
        started:
          type: string
          format: date-time
        vcs_revision:
          type: string
    DependencyTreeNode:
      type: object
      properties:
        artifact_id:
          type: string
          format: uuid
        coord:
          type: string
        children:
          type: array
          items:
            $ref: '#/components/schemas/DependencyTreeNode'
    ScanFinding:
      type: object
      properties:
        id:
          type: string
          format: uuid
        artifact_id:
          type: string
          format: uuid
        scanner:
          type: string
        rule_id:
          type: string
        severity:
          type: string
          enum:
            - low
            - medium
            - high
            - critical
        status:
          type: string
          enum:
            - open
            - suppressed
            - resolved
        first_seen_at:
          type: string
          format: date-time
        suppressed_until:
          type: string
          format: date-time
    SecurityBlock:
      type: object
      properties:
        id:
          type: string
          format: uuid
        block_type:
          type: string
          enum:
            - path
            - sha256
            - repo
            - auto_cve
        pattern:
          type: string
        repo_id:
          type: string
          format: uuid
        reason:
          type: string
        customer_message:
          type: string
        cve_id:
          type: string
        blocked_until:
          type: string
          format: date-time
        active:
          type: boolean
        created_at:
          type: string
          format: date-time
    SecurityBlockCreate:
      type: object
      properties:
        block_type:
          type: string
          enum:
            - path
            - sha256
            - repo
            - auto_cve
        pattern:
          type: string
        repo_id:
          type: string
          format: uuid
        reason:
          type: string
        customer_message:
          type: string
        cve_id:
          type: string
        blocked_until:
          type: string
          format: date-time
      required:
        - block_type
        - reason
    WebhookSubscription:
      type: object
      properties:
        id:
          type: string
          format: uuid
        url:
          type: string
        events:
          type: array
          items:
            type: string
        has_secret:
          type: boolean
        active:
          type: boolean
        created_at:
          type: string
          format: date-time
    WebhookSubscriptionCreate:
      type: object
      properties:
        url:
          type: string
        events:
          type: array
          items:
            type: string
        secret:
          type: string
          description: HMAC shared secret. Write-only — list responses omit this.
      required:
        - url
        - events
    NotificationChannel:
      type: object
      properties:
        id:
          type: string
          format: uuid
        kind:
          type: string
          enum:
            - teams
            - slack
            - webhook
        name:
          type: string
        url:
          type: string
        active:
          type: boolean
    AuditEvent:
      type: object
      properties:
        id:
          type: string
          format: uuid
        actor:
          type: string
        verb:
          type: string
        target_kind:
          type: string
        target_id:
          type: string
        detail:
          type: object
        at:
          type: string
          format: date-time
    PublicConfig:
      type: object
      properties:
        help_url:
          type: string
        logo_url:
          type: string
        version:
          type: string
        active_banner:
          $ref: '#/components/schemas/Banner'
        branding_colors:
          type: object
          additionalProperties:
            type: string
        network:
          type: object
          description: Air-gapped egress flags.
    Whoami:
      type: object
      properties:
        email:
          type: string
        name:
          type: string
        roles:
          type: array
          items:
            type: string
        is_admin:
          type: boolean
        auth_method:
          type: string
          enum:
            - saml
            - local
            - oidc
            - dev_bypass
    OIDCExchangeRequest:
      type: object
      properties:
        id_token:
          type: string
          description: OIDC ID-token JWT. Verified against the trusted issuer's JWKS.
        audience:
          type: string
      required:
        - id_token
    Banner:
      type: object
      properties:
        id:
          type: string
          format: uuid
        message:
          type: string
        severity:
          type: string
          enum:
            - info
            - warning
            - critical
        starts_at:
          type: string
          format: date-time
        ends_at:
          type: string
          format: date-time
    Quota:
      type: object
      properties:
        project_id:
          type: string
          format: uuid
        storage_bytes_limit:
          type: integer
          format: int64
        storage_bytes_used:
          type: integer
          format: int64
    Permission:
      type: object
      properties:
        id:
          type: string
          format: uuid
        project_id:
          type: string
          format: uuid
        principal_email:
          type: string
        role:
          type: string
          enum:
            - reader
            - writer
            - maintainer
            - owner
    OIDCTrustPolicy:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        issuer:
          type: string
        audience:
          type: string
        subject_pattern:
          type: string
        service_account_id:
          type: string
          format: uuid
        token_ttl_sec:
          type: integer
          format: int32
    SigstoreTrustPolicy:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        identity_pattern:
          type: string
        issuer_pattern:
          type: string
        fulcio_root_ca:
          type: string
        rekor_url:
          type: string
    PullGateSettings:
      type: object
      properties:
        enabled:
          type: boolean
        sync_mode:
          type: string
          enum:
            - pre_scan_cache
            - block_until_scan
            - optimistic_with_quarantine
    PullGatePolicy:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        scope_kind:
          type: string
          enum:
            - global
            - project
            - repo
        scope_id:
          type: string
          format: uuid
        min_severity:
          type: string
          enum:
            - low
            - medium
            - high
            - critical
        require_signed:
          type: boolean
        allow_licenses:
          type: array
          items:
            type: string
        deny_licenses:
          type: array
          items:
            type: string
    GeoPeer:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        endpoint:
          type: string
        direction:
          type: string
          enum:
            - push
            - pull
            - bidir
        enabled:
          type: boolean
        bandwidth_kbps:
          type: integer
          format: int64
        pull_interval_sec:
          type: integer
          format: int32
        pull_batch_size:
          type: integer
          format: int32
    GeoSettings:
      type: object
      properties:
        enabled:
          type: boolean
        instance_id:
          type: string
          format: uuid
    NetworkSettings:
      type: object
      properties:
        allow_service_pings:
          type: boolean
        allow_webhooks:
          type: boolean
        allow_telemetry:
          type: boolean
        allow_sigstore_rekor:
          type: boolean
        allow_osv:
          type: boolean
        allow_otel:
          type: boolean
    RetentionPolicy:
      type: object
      properties:
        id:
          type: string
          format: uuid
        repo_id:
          type: string
          format: uuid
        name:
          type: string
        max_age_days:
          type: integer
          format: int32
        keep_per_coord:
          type: integer
          format: int32
        enabled:
          type: boolean
        created_at:
          type: string
          format: date-time
    BackupStatus:
      type: object
      properties:
        primary_endpoint:
          type: string
        backup_endpoint:
          type: string
        mode:
          type: string
          enum:
            - sync
            - async
        pending_writes:
          type: integer
          format: int64
        failed_writes:
          type: integer
          format: int64
        last_recovered_at:
          type: string
          format: date-time
    BackupVerification:
      type: object
      properties:
        id:
          type: string
          format: uuid
        started_at:
          type: string
          format: date-time
        completed_at:
          type: string
          format: date-time
        status:
          type: string
          enum:
            - running
            - passed
            - failed
        detail:
          type: string
    ImportJob:
      type: object
      properties:
        id:
          type: string
          format: uuid
        source_type:
          type: string
          enum:
            - jfrog
            - nexus
            - github
            - gitlab
            - azure
            - awscodeartifact
            - gcparhybrid
        source_endpoint:
          type: string
        target_project_id:
          type: string
          format: uuid
        status:
          type: string
          enum:
            - pending
            - running
            - paused
            - completed
            - failed
            - cancelled
        started_at:
          type: string
          format: date-time
        completed_at:
          type: string
          format: date-time
        detail:
          type: string
    Error:
      type: object
      properties:
        error:
          type: string
        reason:
          type: string
        detail:
          type: string
  securitySchemes:
    bearerAuth:
      type: http
      description: Personal access tokens are issued via `POST /api/v1/tokens`. Service-account tokens are issued via the project-scoped `POST /api/v1/projects/{project_id}/service_accounts/{id}/tokens` endpoint.
      scheme: bearer
      bearerFormat: OrbitalReg PAT (orbr_*) or service-account token (orbsa_*)
    sessionCookie:
      type: apiKey
      description: Session cookie minted by the SAML / local / OIDC sign-in flow. The web UI uses this exclusively; programmatic clients should prefer `bearerAuth`.
      name: orbitalreg_session
      in: cookie
security:
  - bearerAuth: []
  - sessionCookie: []
