# Generated from apps/psp/api/openapi/openapi.yaml. That file is the source of truth.
# Do not edit public/openapi.yaml or public/openapi.json by hand. Run pnpm --filter psp-landing publish-agent-files.
openapi: 3.1.0
info:
  title: Cedar Merchant API
  version: 0.0.0
  description: Customer-facing REST API. API Client and API Key resources are Access projections.
jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema
servers:
  - url: https://api.usecedar.co
security:
  - bearerAuth: []
tags:
  - name: Health
  - name: API Clients
  - name: API Keys
paths:
  /health:
    get:
      operationId: getHealth
      tags: [Health]
      summary: Health
      security: []
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Health"
  /v1/api_clients:
    get:
      operationId: listApiClients
      tags: [API Clients]
      summary: List API Clients for the authenticated Organization
      responses:
        "200":
          description: API Clients, including revoked
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ApiClient"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
    post:
      operationId: createApiClient
      tags: [API Clients]
      summary: Create an API Client
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateApiClientRequest"
      responses:
        "200":
          description: Created API Client
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiClient"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /v1/api_clients/{api_client_id}:
    parameters:
      - $ref: "#/components/parameters/ApiClientId"
    get:
      operationId: getApiClient
      tags: [API Clients]
      summary: Get an API Client
      responses:
        "200":
          description: API Client, including revoked
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiClient"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
    patch:
      operationId: updateApiClient
      tags: [API Clients]
      summary: Update API Client roles
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateApiClientRequest"
      responses:
        "200":
          description: Updated API Client
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiClient"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
    delete:
      operationId: revokeApiClient
      tags: [API Clients]
      summary: Revoke an API Client
      responses:
        "200":
          description: Revoked API Client
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiClient"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
  /v1/api_clients/{api_client_id}/api_keys:
    parameters:
      - $ref: "#/components/parameters/ApiClientId"
    get:
      operationId: listApiKeys
      tags: [API Keys]
      summary: List API Keys for an API Client
      parameters:
        - name: environment
          in: query
          required: false
          schema:
            $ref: "#/components/schemas/ApiKeyEnvironment"
      responses:
        "200":
          description: API Keys, including revoked
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ApiKey"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
    post:
      operationId: createApiKey
      tags: [API Keys]
      summary: Create an API Key
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateApiKeyRequest"
      responses:
        "200":
          description: Issued API Key. Secret is returned once.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IssuedApiKey"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /v1/api_clients/{api_client_id}/api_keys/{api_key_id}:
    parameters:
      - $ref: "#/components/parameters/ApiClientId"
      - $ref: "#/components/parameters/ApiKeyId"
    get:
      operationId: getApiKey
      tags: [API Keys]
      summary: Get an API Key
      responses:
        "200":
          description: API Key, including revoked. Secret is not returned.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiKey"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
    delete:
      operationId: revokeApiKey
      tags: [API Keys]
      summary: Revoke an API Key
      responses:
        "200":
          description: Revoked API Key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiKey"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
  /v1/api_clients/{api_client_id}/api_keys/{api_key_id}/rotate:
    parameters:
      - $ref: "#/components/parameters/ApiClientId"
      - $ref: "#/components/parameters/ApiKeyId"
    post:
      operationId: rotateApiKey
      tags: [API Keys]
      summary: Rotate an API Key secret
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RotateApiKeyRequest"
      responses:
        "200":
          description: Issued API Key. New secret is returned once.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IssuedApiKey"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: API Key secret. Authorization Bearer, not the apk_ public id.
  parameters:
    ApiClientId:
      name: api_client_id
      in: path
      required: true
      schema:
        $ref: "#/components/schemas/ApiClientId"
    ApiKeyId:
      name: api_key_id
      in: path
      required: true
      schema:
        $ref: "#/components/schemas/ApiKeyId"
  schemas:
    Health:
      type: object
      additionalProperties: false
      required: [ok]
      properties:
        ok:
          type: boolean
    ApiClientId:
      type: string
      pattern: "^apc_[A-Za-z0-9]+$"
    ApiKeyId:
      type: string
      pattern: "^apk_[A-Za-z0-9]+$"
    ApiKeyEnvironment:
      type: string
      enum: [test, live]
    ApiClientRoles:
      type: array
      minItems: 1
      items:
        type: string
        enum: [admin, developer, finance, viewer]
    ApiClient:
      type: object
      additionalProperties: false
      required: [id, organization_id, roles, revoked_at]
      properties:
        id:
          $ref: "#/components/schemas/ApiClientId"
        organization_id:
          type: string
        roles:
          $ref: "#/components/schemas/ApiClientRoles"
        revoked_at:
          type: ["string", "null"]
          format: date-time
    ApiKey:
      type: object
      additionalProperties: false
      required: [id, api_client_id, expires_at, revoked_at, environment]
      properties:
        id:
          $ref: "#/components/schemas/ApiKeyId"
        api_client_id:
          $ref: "#/components/schemas/ApiClientId"
        expires_at:
          type: ["string", "null"]
          format: date-time
        revoked_at:
          type: ["string", "null"]
          format: date-time
        environment:
          $ref: "#/components/schemas/ApiKeyEnvironment"
    IssuedApiKey:
      type: object
      additionalProperties: false
      required: [api_key, secret]
      properties:
        api_key:
          $ref: "#/components/schemas/ApiKey"
        secret:
          type: string
          pattern: "^ask_(test|live)_[0-9a-f]{64}$"
    CreateApiClientRequest:
      type: object
      additionalProperties: false
      required: [roles]
      properties:
        roles:
          $ref: "#/components/schemas/ApiClientRoles"
    UpdateApiClientRequest:
      type: object
      additionalProperties: false
      required: [roles]
      properties:
        roles:
          $ref: "#/components/schemas/ApiClientRoles"
    CreateApiKeyRequest:
      type: object
      additionalProperties: false
      required: [environment]
      properties:
        environment:
          $ref: "#/components/schemas/ApiKeyEnvironment"
        expires_at:
          type: ["string", "null"]
          format: date-time
    RotateApiKeyRequest:
      type: object
      additionalProperties: false
      properties:
        expires_at:
          type: ["string", "null"]
          format: date-time
    Error:
      type: object
      additionalProperties: false
      required: [code]
      properties:
        code:
          type: string
          enum:
            - NOT_FOUND
            - CONFLICT
            - INVALID_REQUEST
            - EXPIRED
            - REVOKED
            - UNAUTHORIZED
            - FORBIDDEN
  responses:
    InvalidRequest:
      description: Invalid request
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Unauthorized:
      description: Missing or unusable Bearer secret
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Forbidden:
      description: Principal Roles cannot perform this act
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    NotFound:
      description: Unknown id, or path client/key mismatch
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Conflict:
      description: Revoked or expired resource, or conflicting write
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
