openapi: 3.1.0
info:
  title: OrderCore API (Actions)
  version: 0.2.17
  description: >
    Action-ready OpenAPI spec for currently implemented OrderCore endpoints.
    This spec intentionally excludes placeholder /v1 endpoints that return 501.
servers:
  - url: https://api.ordercore.ai
    description: Production
  - url: https://demo-api.ordercore.ai
    description: Demo
  - url: http://localhost:8080
    description: Local development
tags:
  - name: System
  - name: Onboarding
  - name: Account
  - name: Catalog
  - name: Inventory
  - name: Pricing
  - name: Orders
  - name: Webhooks
  - name: UCP
paths:
  /:
    get:
      tags: [System]
      operationId: getApiRoot
      summary: Get API metadata
      responses:
        "200":
          description: API root metadata
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
  /health:
    get:
      tags: [System]
      operationId: getHealth
      summary: Health check
      responses:
        "200":
          description: Service health
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
  /bootstrap/sandbox-key:
    post:
      tags: [Onboarding]
      operationId: issueSandboxKey
      summary: Issue a read-only sandbox API key (no signup)
      description: >
        Issues a fresh read-only API key on the shared demo catalog — no signup and
        no credentials. Send the returned key in the X-API-Key header to read the
        catalog (GET /v1/products, /v1/prices, /v1/inventory). Write operations are
        blocked (403); the key is strictly rate limited and short-lived. For a full
        read/write key, request one at https://ordercore.ai/bootstrap.
      security: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
      responses:
        "201":
          description: A read-only sandbox key was issued.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SandboxKeyResponse"
        "429":
          description: Sandbox key issuance is rate limited; retry shortly.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/products:
    get:
      tags: [Catalog]
      operationId: listProducts
      summary: List products
      security:
        - ApiKeyAuth: []
      parameters:
        - in: query
          name: status
          schema:
            type: string
            enum: [draft, active, archived]
        - in: query
          name: page
          schema:
            type: integer
            minimum: 1
        - in: query
          name: per_page
          schema:
            type: integer
            minimum: 1
            maximum: 100
      responses:
        "200":
          description: Product list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProductListResponse"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    post:
      tags: [Catalog]
      operationId: createProduct
      summary: Create product
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProductCreateRequest"
      responses:
        "201":
          description: Product created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProductDetail"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: External ID conflict
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/products/{productID}:
    get:
      tags: [Catalog]
      operationId: getProduct
      summary: Get product detail
      security:
        - ApiKeyAuth: []
      parameters:
        - in: path
          name: productID
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Product detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProductDetail"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    patch:
      tags: [Catalog]
      operationId: updateProduct
      summary: Update product
      security:
        - ApiKeyAuth: []
      parameters:
        - in: path
          name: productID
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProductUpdateRequest"
      responses:
        "200":
          description: Updated product
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProductDetail"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: External ID conflict
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      tags: [Catalog]
      operationId: deleteProduct
      summary: Delete product
      security:
        - ApiKeyAuth: []
      parameters:
        - in: path
          name: productID
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Deleted
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/skus/{skuCode}:
    get:
      tags: [Catalog]
      operationId: getSkuByCode
      summary: Get SKU detail by code
      security:
        - ApiKeyAuth: []
      parameters:
        - in: path
          name: skuCode
          required: true
          schema:
            type: string
      responses:
        "200":
          description: SKU detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SkuDetail"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/inventory:
    get:
      tags: [Inventory]
      operationId: listInventory
      summary: List inventory balances
      security:
        - ApiKeyAuth: []
      parameters:
        - in: query
          name: sku_id
          schema:
            type: string
        - in: query
          name: sku_code
          schema:
            type: string
        - in: query
          name: location_id
          schema:
            type: string
        - in: query
          name: page
          schema:
            type: integer
            minimum: 1
        - in: query
          name: per_page
          schema:
            type: integer
            minimum: 1
            maximum: 100
      responses:
        "200":
          description: Inventory list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InventoryListResponse"
  /v1/inventory/adjust:
    post:
      tags: [Inventory]
      operationId: adjustInventory
      summary: Adjust stock levels
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InventoryAdjustRequest"
      responses:
        "200":
          description: Updated inventory balance
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InventoryItem"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Conflict
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/inventory/reserve:
    post:
      tags: [Inventory]
      operationId: reserveInventory
      summary: Reserve stock
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InventoryReserveRequest"
      responses:
        "200":
          description: Updated inventory balance
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InventoryItem"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Conflict
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/inventory/release:
    post:
      tags: [Inventory]
      operationId: releaseInventory
      summary: Release reserved stock
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InventoryReleaseRequest"
      responses:
        "200":
          description: Updated inventory balance
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InventoryItem"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Conflict
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/prices:
    get:
      tags: [Pricing]
      operationId: listPrices
      summary: List prices
      security:
        - ApiKeyAuth: []
      parameters:
        - in: query
          name: sku_id
          schema:
            type: string
        - in: query
          name: sku_code
          schema:
            type: string
        - in: query
          name: price_list_code
          schema:
            type: string
        - in: query
          name: currency
          schema:
            type: string
        - in: query
          name: customer_id
          schema:
            type: string
        - in: query
          name: quantity
          schema:
            type: integer
            minimum: 1
        - in: query
          name: valid_now
          schema:
            type: boolean
        - in: query
          name: active_only
          schema:
            type: boolean
        - in: query
          name: page
          schema:
            type: integer
            minimum: 1
        - in: query
          name: per_page
          schema:
            type: integer
            minimum: 1
            maximum: 100
      responses:
        "200":
          description: Price list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PriceListResponse"
  /v1/orders:
    get:
      tags: [Orders]
      operationId: listOrders
      summary: List orders
      security:
        - ApiKeyAuth: []
      parameters:
        - in: query
          name: status
          schema:
            type: string
            enum: [pending, confirmed, processing, shipped, delivered, cancelled, refunded]
        - in: query
          name: customer_id
          schema:
            type: string
        - in: query
          name: page
          schema:
            type: integer
            minimum: 1
        - in: query
          name: per_page
          schema:
            type: integer
            minimum: 1
            maximum: 100
      responses:
        "200":
          description: Order list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrderListResponse"
    post:
      tags: [Orders]
      operationId: createOrder
      summary: Create order
      description: >
        Creates a tenant-scoped order. Requires API key authentication.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateOrderRequest"
            examples:
              basic:
                value:
                  idempotency_key: order-123-abc
                  customer_id: cust_123
                  items:
                    - sku_id: sku_456
                      quantity: 2
      responses:
        "201":
          description: Order created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrderResponse"
        "200":
          description: Idempotent replay
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrderResponse"
        "400":
          description: Invalid request payload
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Unauthorized or missing API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Conflict (inventory, status, or idempotency key reused with a different payload)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Internal error while creating order
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/account/status:
    get:
      tags: [Account]
      operationId: getAccountStatus
      summary: Get tenant account status
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Account status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccountStatusResponse"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/account/auth:
    get:
      tags: [Account]
      operationId: getAccountAuth
      summary: Verify authenticated tenant and API key context
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Authenticated context
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccountAuthResponse"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/account/usage:
    get:
      tags: [Account]
      operationId: getAccountUsage
      summary: Get tenant usage and quota status
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Usage summary
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccountUsageResponse"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/account/api-keys:
    get:
      tags: [Account]
      operationId: listApiKeys
      summary: List API keys for current tenant
      security:
        - ApiKeyAuth: []
      parameters:
        - in: query
          name: active_only
          schema:
            type: boolean
      responses:
        "200":
          description: API key list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiKeyListResponse"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    post:
      tags: [Account]
      operationId: createApiKey
      summary: Create API key for current tenant
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateApiKeyRequest"
      responses:
        "201":
          description: API key created (plain key is returned only once)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateApiKeyResponse"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/account/api-keys/{apiKeyID}/revoke:
    post:
      tags: [Account]
      operationId: revokeApiKey
      summary: Revoke API key
      security:
        - ApiKeyAuth: []
      parameters:
        - in: path
          name: apiKeyID
          required: true
          schema:
            type: string
      responses:
        "200":
          description: API key revoked
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiKeyRevokeResponse"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Conflict
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/account/api-keys/{apiKeyID}/rotate:
    post:
      tags: [Account]
      operationId: rotateApiKey
      summary: Rotate API key (revoke old and issue new)
      security:
        - ApiKeyAuth: []
      parameters:
        - in: path
          name: apiKeyID
          required: true
          schema:
            type: string
      responses:
        "200":
          description: API key rotated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RotateApiKeyResponse"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Conflict
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/onboarding/demo-data:
    post:
      tags: [Onboarding]
      operationId: seedDemoData
      summary: Seed demo catalog data for current tenant
      description: >
        Creates demo products, SKUs, inventory, prices and a sample customer in an idempotent way.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DemoSeedRequest"
      responses:
        "201":
          description: Demo data created or updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DemoSeedResponse"
        "400":
          description: Invalid request payload
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Internal error during demo seed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/orders/{orderID}:
    get:
      tags: [Orders]
      operationId: getOrder
      summary: Get order detail
      security:
        - ApiKeyAuth: []
      parameters:
        - in: path
          name: orderID
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Order detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrderDetail"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/orders/{orderID}/confirm:
    post:
      tags: [Orders]
      operationId: confirmOrder
      summary: Confirm order
      security:
        - ApiKeyAuth: []
      parameters:
        - in: path
          name: orderID
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OrderActionRequest"
      responses:
        "200":
          description: Updated order
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrderDetail"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Conflict
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/orders/{orderID}/cancel:
    post:
      tags: [Orders]
      operationId: cancelOrder
      summary: Cancel order
      security:
        - ApiKeyAuth: []
      parameters:
        - in: path
          name: orderID
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OrderActionRequest"
      responses:
        "200":
          description: Updated order
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrderDetail"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Conflict
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/webhooks/endpoints:
    get:
      tags: [Webhooks]
      operationId: listWebhookEndpoints
      summary: List webhook endpoints
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Webhook endpoints
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookEndpointListResponse"
    post:
      tags: [Webhooks]
      operationId: createWebhookEndpoint
      summary: Create webhook endpoint
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateWebhookEndpointRequest"
      responses:
        "201":
          description: Webhook endpoint created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateWebhookEndpointResponse"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/webhooks/endpoints/{endpointID}:
    delete:
      tags: [Webhooks]
      operationId: deleteWebhookEndpoint
      summary: Delete webhook endpoint
      security:
        - ApiKeyAuth: []
      parameters:
        - in: path
          name: endpointID
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Deleted
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /ucp/.well-known/ucp:
    get:
      tags: [UCP]
      operationId: getUcpProfile
      summary: UCP profile discovery
      responses:
        "200":
          description: UCP profile
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UcpProfile"
  /ucp/checkout/sessions:
    post:
      tags: [UCP]
      operationId: createUcpCheckoutSession
      summary: Create UCP checkout session
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UcpCreateSessionRequest"
      responses:
        "201":
          description: Checkout session created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UcpCheckoutSession"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UcpErrorResponse"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UcpErrorResponse"
        "409":
          description: Conflict (inventory, pricing, currency)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UcpErrorResponse"
  /ucp/checkout/sessions/{sessionID}:
    put:
      tags: [UCP]
      operationId: updateUcpCheckoutSession
      summary: Update UCP checkout session
      security:
        - ApiKeyAuth: []
      parameters:
        - name: sessionID
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UcpUpdateSessionRequest"
      responses:
        "200":
          description: Checkout session updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UcpCheckoutSession"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UcpErrorResponse"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UcpErrorResponse"
        "404":
          description: Session not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UcpErrorResponse"
        "409":
          description: Conflict (inventory, pricing, currency)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UcpErrorResponse"
  /ucp/checkout/sessions/{sessionID}/complete:
    post:
      tags: [UCP]
      operationId: completeUcpCheckoutSession
      summary: Complete checkout session and create order
      security:
        - ApiKeyAuth: []
      parameters:
        - name: sessionID
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UcpCompleteRequest"
      responses:
        "201":
          description: Checkout completed (new order created)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UcpOrderResponse"
        "200":
          description: Checkout replay (idempotent; existing order returned)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UcpOrderResponse"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UcpErrorResponse"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UcpErrorResponse"
        "404":
          description: Session not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UcpErrorResponse"
        "409":
          description: Conflict during order creation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UcpErrorResponse"
        "500":
          description: Internal error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UcpErrorResponse"
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
  schemas:
    SandboxKeyResponse:
      type: object
      required: [api_key, scopes, read_only, expires_at]
      properties:
        api_key:
          type: string
          description: The read-only sandbox API key. Send it in the X-API-Key header.
          example: oc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
        key_prefix:
          type: string
          example: oc_live_
        tenant_slug:
          type: string
          example: demo
        scopes:
          type: array
          items:
            type: string
          example: ["read"]
        read_only:
          type: boolean
          example: true
        expires_at:
          type: string
          format: date-time
        note:
          type: string
        try_next:
          type: array
          items:
            type: string
    HealthResponse:
      type: object
      required: [status, version, environment, timestamp]
      properties:
        status:
          type: string
          example: healthy
        version:
          type: string
          example: "v0.2.17"
        environment:
          type: string
          example: development
        timestamp:
          type: string
          format: date-time
    ErrorResponse:
      type: object
      properties:
        error:
          type: string
        message:
          type: string
        auth_reason:
          type: string
      additionalProperties: true
    AccountUsageSummary:
      type: object
      properties:
        month_start:
          type: string
          format: date-time
        month_end:
          type: string
          format: date-time
        monthly_limit:
          type: integer
          format: int64
        monthly_used:
          type: integer
          format: int64
        monthly_remaining:
          type: integer
          format: int64
        usage_percent:
          type: number
          format: double
        warning_level:
          type: string
          enum: [none, warning, critical, limit_reached, over_limit]
        projected_monthly:
          type: integer
          format: int64
        projected_overage:
          type: integer
          format: int64
        recommended_action:
          type: string
          nullable: true
        upgrade_url:
          type: string
          nullable: true
    ApiKeyListItem:
      type: object
      properties:
        id:
          type: string
        key_prefix:
          type: string
        name:
          type: string
        scopes:
          type: array
          items:
            type: string
        rate_limit_per_minute:
          type: integer
        is_active:
          type: boolean
        expires_at:
          type: string
          format: date-time
          nullable: true
        last_used_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        revoked_at:
          type: string
          format: date-time
          nullable: true
        current:
          type: boolean
    ApiKeyListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/ApiKeyListItem"
    AccountStatusResponse:
      type: object
      properties:
        tenant_id:
          type: string
        tenant_slug:
          type: string
        plan:
          type: string
        tenant_active:
          type: boolean
        current_api_key:
          $ref: "#/components/schemas/ApiKeyListItem"
        usage:
          $ref: "#/components/schemas/AccountUsageSummary"
    AccountAuthResponse:
      type: object
      properties:
        tenant_id:
          type: string
        tenant_slug:
          type: string
        plan:
          type: string
        api_key_id:
          type: string
        auth_mode:
          type: string
          enum: [x-api-key, bearer]
        authenticated_at:
          type: string
          format: date-time
    AccountUsageResponse:
      type: object
      properties:
        tenant_id:
          type: string
        plan:
          type: string
        usage:
          $ref: "#/components/schemas/AccountUsageSummary"
        current_hour_used:
          type: integer
          format: int64
        hourly_limit:
          type: integer
    CreateApiKeyRequest:
      type: object
      required: [name]
      properties:
        name:
          type: string
        scopes:
          type: array
          items:
            type: string
        rate_limit_per_minute:
          type: integer
          minimum: 1
        expires_at:
          type: string
          format: date-time
          description: RFC3339 timestamp in UTC
    CreateApiKeyResponse:
      type: object
      properties:
        api_key:
          type: string
          description: Plain key is shown only once
        key:
          $ref: "#/components/schemas/ApiKeyListItem"
    RotateApiKeyResponse:
      type: object
      properties:
        api_key:
          type: string
          description: New plain key shown only once
        rotated_from:
          type: string
        key:
          $ref: "#/components/schemas/ApiKeyListItem"
    ApiKeyRevokeResponse:
      type: object
      properties:
        status:
          type: string
          example: revoked
        key:
          $ref: "#/components/schemas/ApiKeyListItem"
    ProductListItem:
      type: object
      required: [id, name, status, created_at, updated_at]
      properties:
        id:
          type: string
        external_id:
          type: string
          nullable: true
        name:
          type: string
        description:
          type: string
          nullable: true
        status:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    ProductDetail:
      type: object
      required: [id, name, status, created_at, updated_at]
      properties:
        id:
          type: string
        external_id:
          type: string
          nullable: true
        name:
          type: string
        description:
          type: string
          nullable: true
        status:
          type: string
        attributes:
          type: object
          nullable: true
          additionalProperties: true
        metadata:
          type: object
          nullable: true
          additionalProperties: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    ProductListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/ProductListItem"
        meta:
          type: object
          properties:
            total:
              type: integer
            page:
              type: integer
            per_page:
              type: integer
    ProductCreateRequest:
      type: object
      required: [name]
      properties:
        external_id:
          type: string
        name:
          type: string
        description:
          type: string
        status:
          type: string
          enum: [draft, active, archived]
        attributes:
          type: object
          additionalProperties: true
        metadata:
          type: object
          additionalProperties: true
    ProductUpdateRequest:
      type: object
      properties:
        external_id:
          type: string
          nullable: true
        name:
          type: string
        description:
          type: string
          nullable: true
        status:
          type: string
          enum: [draft, active, archived]
        attributes:
          type: object
          nullable: true
          additionalProperties: true
        metadata:
          type: object
          nullable: true
          additionalProperties: true
    SkuDetail:
      type: object
      required: [id, product_id, product_name, code, is_active, created_at, updated_at]
      properties:
        id:
          type: string
        product_id:
          type: string
        product_name:
          type: string
        code:
          type: string
        name:
          type: string
          nullable: true
        barcode:
          type: string
          nullable: true
        attributes:
          type: object
          nullable: true
          additionalProperties: true
        weight_grams:
          type: integer
          nullable: true
        dimensions:
          type: object
          nullable: true
          additionalProperties: true
        is_active:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    InventoryLocation:
      type: object
      required: [id, code, name]
      properties:
        id:
          type: string
        code:
          type: string
        name:
          type: string
    InventoryItem:
      type: object
      required: [sku_id, sku_code, location, quantity_on_hand, quantity_reserved, quantity_available, updated_at]
      properties:
        sku_id:
          type: string
        sku_code:
          type: string
        location:
          $ref: "#/components/schemas/InventoryLocation"
        quantity_on_hand:
          type: integer
        quantity_reserved:
          type: integer
        quantity_available:
          type: integer
        updated_at:
          type: string
          format: date-time
    InventoryListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/InventoryItem"
        meta:
          type: object
          properties:
            total:
              type: integer
            page:
              type: integer
            per_page:
              type: integer
    InventoryAdjustRequest:
      type: object
      required: [quantity_delta]
      properties:
        sku_id:
          type: string
        sku_code:
          type: string
        location_id:
          type: string
        location_code:
          type: string
        quantity_delta:
          type: integer
        reason:
          type: string
        reference_type:
          type: string
        reference_id:
          type: string
        metadata:
          type: object
          additionalProperties: true
        created_by:
          type: string
    InventoryReserveRequest:
      type: object
      required: [quantity]
      properties:
        sku_id:
          type: string
        sku_code:
          type: string
        location_id:
          type: string
        location_code:
          type: string
        quantity:
          type: integer
          minimum: 1
        reason:
          type: string
        reference_type:
          type: string
        reference_id:
          type: string
        metadata:
          type: object
          additionalProperties: true
        created_by:
          type: string
    InventoryReleaseRequest:
      type: object
      required: [quantity]
      properties:
        sku_id:
          type: string
        sku_code:
          type: string
        location_id:
          type: string
        location_code:
          type: string
        quantity:
          type: integer
          minimum: 1
        reason:
          type: string
        reference_type:
          type: string
        reference_id:
          type: string
        metadata:
          type: object
          additionalProperties: true
        created_by:
          type: string
    PriceListItem:
      type: object
      required: [id, sku_id, sku_code, price_list_id, price_list_code, currency, unit_price, min_quantity, updated_at]
      properties:
        id:
          type: string
        sku_id:
          type: string
        sku_code:
          type: string
        price_list_id:
          type: string
        price_list_code:
          type: string
        currency:
          type: string
        unit_price:
          type: number
          format: double
        compare_at_price:
          type: number
          format: double
          nullable: true
        min_quantity:
          type: integer
        max_quantity:
          type: integer
          nullable: true
        valid_from:
          type: string
          format: date-time
          nullable: true
        valid_to:
          type: string
          format: date-time
          nullable: true
        updated_at:
          type: string
          format: date-time
    PriceListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/PriceListItem"
        meta:
          type: object
          properties:
            total:
              type: integer
            page:
              type: integer
            per_page:
              type: integer
    OrderListItem:
      type: object
      required: [id, order_number, status, currency, grand_total, created_at, updated_at]
      properties:
        id:
          type: string
        order_number:
          type: string
        customer_id:
          type: string
          nullable: true
        status:
          type: string
        currency:
          type: string
        grand_total:
          type: number
          format: double
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    OrderListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/OrderListItem"
        meta:
          type: object
          properties:
            total:
              type: integer
            page:
              type: integer
            per_page:
              type: integer
    OrderItemDetail:
      type: object
      required: [id, sku_id, sku_code, product_name, quantity, unit_price, line_total]
      properties:
        id:
          type: string
        sku_id:
          type: string
        sku_code:
          type: string
        product_name:
          type: string
        quantity:
          type: integer
        unit_price:
          type: number
          format: double
        tax_rate:
          type: number
          format: double
        tax_amount:
          type: number
          format: double
        discount_amount:
          type: number
          format: double
        line_total:
          type: number
          format: double
        metadata:
          type: object
          nullable: true
          additionalProperties: true
    OrderDetail:
      type: object
      required: [id, order_number, status, currency, subtotal, tax_total, shipping_total, discount_total, grand_total, created_at, updated_at, items]
      properties:
        id:
          type: string
        external_id:
          type: string
          nullable: true
        order_number:
          type: string
        customer_id:
          type: string
          nullable: true
        status:
          type: string
        currency:
          type: string
        subtotal:
          type: number
          format: double
        tax_total:
          type: number
          format: double
        shipping_total:
          type: number
          format: double
        discount_total:
          type: number
          format: double
        grand_total:
          type: number
          format: double
        shipping_address:
          type: object
          nullable: true
          additionalProperties: true
        billing_address:
          type: object
          nullable: true
          additionalProperties: true
        notes:
          type: string
          nullable: true
        metadata:
          type: object
          nullable: true
          additionalProperties: true
        placed_at:
          type: string
          format: date-time
          nullable: true
        confirmed_at:
          type: string
          format: date-time
          nullable: true
        shipped_at:
          type: string
          format: date-time
          nullable: true
        delivered_at:
          type: string
          format: date-time
          nullable: true
        cancelled_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        items:
          type: array
          items:
            $ref: "#/components/schemas/OrderItemDetail"
    OrderActionRequest:
      type: object
      properties:
        reason:
          type: string
        metadata:
          type: object
          additionalProperties: true
        created_by:
          type: string
    CreateOrderRequest:
      type: object
      required: [customer_id, items]
      properties:
        idempotency_key:
          type: string
          description: Optional idempotency key persisted by backend.
          maxLength: 255
          example: order-123-abc
        external_id:
          type: string
          description: Optional external order id from upstream system (e.g. Woo/Shopify).
          example: woo_myshop_12345
        customer_id:
          type: string
          description: Customer UUID or external ID. Unknown external IDs are auto-created for the tenant.
          example: cust_123
        metadata:
          type: object
          additionalProperties: true
          description: Optional metadata stored with the order.
        items:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/OrderItemRequest"
    OrderItemRequest:
      type: object
      required: [sku_id, quantity]
      properties:
        sku_id:
          type: string
          example: sku_456
        quantity:
          type: integer
          minimum: 1
          example: 2
    OrderResponse:
      type: object
      required: [id, status, created_at, order_number]
      properties:
        id:
          type: string
        status:
          type: string
          example: pending
        created_at:
          type: string
          format: date-time
        order_number:
          type: string
          example: OC-2026-00077
    DemoSeedRequest:
      type: object
      properties:
        currency:
          type: string
          minLength: 3
          maxLength: 3
          example: USD
        price_list_code:
          type: string
          example: DEFAULT
        location_code:
          type: string
          example: MAIN-WH
    DemoSeedCounts:
      type: object
      required: [products, skus, prices, inventory_rows, customers, price_lists, locations]
      properties:
        products:
          type: integer
        skus:
          type: integer
        prices:
          type: integer
        inventory_rows:
          type: integer
        customers:
          type: integer
        price_lists:
          type: integer
        locations:
          type: integer
    DemoOrderItem:
      type: object
      required: [sku_id, quantity]
      properties:
        sku_id:
          type: string
        quantity:
          type: integer
          minimum: 1
    DemoSeedResponse:
      type: object
      required: [status, price_list_code, location_code, currency, sample_customer_id, sample_order_item, created_or_updated]
      properties:
        status:
          type: string
          example: ok
        price_list_code:
          type: string
        location_code:
          type: string
        currency:
          type: string
        sample_customer_id:
          type: string
          example: demo-buyer
        sample_order_item:
          $ref: "#/components/schemas/DemoOrderItem"
        created_or_updated:
          $ref: "#/components/schemas/DemoSeedCounts"
    WebhookEndpoint:
      type: object
      required: [id, url, events, is_active]
      properties:
        id:
          type: string
        url:
          type: string
        events:
          type: array
          items:
            type: string
        is_active:
          type: boolean
        created_at:
          type: string
          format: date-time
          nullable: true
        updated_at:
          type: string
          format: date-time
          nullable: true
    WebhookEndpointListResponse:
      type: object
      required: [endpoints]
      properties:
        endpoints:
          type: array
          items:
            $ref: "#/components/schemas/WebhookEndpoint"
    CreateWebhookEndpointRequest:
      type: object
      required: [url, events]
      properties:
        url:
          type: string
        events:
          type: array
          items:
            type: string
    CreateWebhookEndpointResponse:
      type: object
      required: [endpoint, secret]
      properties:
        endpoint:
          $ref: "#/components/schemas/WebhookEndpoint"
        secret:
          type: string
    UcpErrorResponse:
      type: object
      properties:
        error:
          type: string
          example: invalid_request
    UcpCapability:
      type: object
      required: [name, version]
      properties:
        name:
          type: string
        version:
          type: string
    UcpBlock:
      type: object
      required: [version, capabilities]
      properties:
        version:
          type: string
          example: "2026-01-11"
        capabilities:
          type: array
          items:
            $ref: "#/components/schemas/UcpCapability"
    UcpProfile:
      type: object
      required: [ucp, business, services]
      properties:
        ucp:
          $ref: "#/components/schemas/UcpBlock"
        business:
          type: object
          additionalProperties: true
        services:
          type: object
          additionalProperties: true
    UcpItem:
      type: object
      required: [id, title]
      properties:
        id:
          type: string
        title:
          type: string
        price:
          type: integer
          nullable: true
    UcpLineItem:
      type: object
      required: [id, item, quantity]
      properties:
        id:
          type: string
        item:
          $ref: "#/components/schemas/UcpItem"
        quantity:
          type: integer
          minimum: 1
    UcpTotal:
      type: object
      required: [type, amount]
      properties:
        type:
          type: string
        amount:
          type: integer
        display_text:
          type: string
    UcpAddress:
      type: object
      properties:
        full_name:
          type: string
        address_line_1:
          type: string
        address_line_2:
          type: string
        city:
          type: string
        state:
          type: string
        postal_code:
          type: string
        country:
          type: string
    UcpFulfillmentExpectation:
      type: object
      properties:
        id:
          type: string
        method_type:
          type: string
    UcpShippingOption:
      type: object
      required: [id, title, amount]
      properties:
        id:
          type: string
        title:
          type: string
        description:
          type: string
        amount:
          type: integer
        min_delivery_days:
          type: integer
        max_delivery_days:
          type: integer
    UcpFulfillment:
      type: object
      properties:
        expectations:
          type: array
          items:
            $ref: "#/components/schemas/UcpFulfillmentExpectation"
        address:
          $ref: "#/components/schemas/UcpAddress"
        options:
          type: array
          items:
            $ref: "#/components/schemas/UcpShippingOption"
        selected:
          type: string
    UcpPaymentHandler:
      type: object
      required: [id, type]
      properties:
        id:
          type: string
        type:
          type: string
        supported_networks:
          type: array
          items:
            type: string
    UcpPaymentCredential:
      type: object
      properties:
        type:
          type: string
        token:
          type: string
        last_digits:
          type: string
        brand:
          type: string
    UcpPayment:
      type: object
      properties:
        handlers:
          type: array
          items:
            $ref: "#/components/schemas/UcpPaymentHandler"
        credential:
          $ref: "#/components/schemas/UcpPaymentCredential"
    UcpLink:
      type: object
      required: [rel, href]
      properties:
        rel:
          type: string
        href:
          type: string
    UcpCheckoutSession:
      type: object
      required: [ucp, id, status, currency, line_items, totals, links, created_at, updated_at]
      properties:
        ucp:
          $ref: "#/components/schemas/UcpBlock"
        id:
          type: string
        status:
          type: string
          enum: [incomplete, ready_for_complete, complete]
        currency:
          type: string
        line_items:
          type: array
          items:
            $ref: "#/components/schemas/UcpLineItem"
        totals:
          type: array
          items:
            $ref: "#/components/schemas/UcpTotal"
        buyer:
          $ref: "#/components/schemas/UcpBuyer"
        fulfillment:
          $ref: "#/components/schemas/UcpFulfillment"
        payment:
          $ref: "#/components/schemas/UcpPayment"
        links:
          type: array
          items:
            $ref: "#/components/schemas/UcpLink"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    UcpCreateSessionRequest:
      type: object
      required: [line_items]
      properties:
        line_items:
          type: array
          items:
            type: object
            required: [item, quantity]
            properties:
              item:
                type: object
                required: [id]
                properties:
                  id:
                    type: string
                  title:
                    type: string
              quantity:
                type: integer
                minimum: 1
        currency:
          type: string
          default: USD
    UcpBuyer:
      type: object
      properties:
        email:
          type: string
        first_name:
          type: string
        last_name:
          type: string
    UcpUpdateSessionRequest:
      type: object
      properties:
        line_items:
          type: array
          items:
            type: object
            required: [item, quantity]
            properties:
              item:
                type: object
                required: [id]
                properties:
                  id:
                    type: string
                  title:
                    type: string
              quantity:
                type: integer
                minimum: 1
        currency:
          type: string
        buyer:
          $ref: "#/components/schemas/UcpBuyer"
        fulfillment:
          $ref: "#/components/schemas/UcpFulfillment"
        payment:
          $ref: "#/components/schemas/UcpPayment"
    UcpCompleteRequest:
      type: object
      properties:
        payment_data:
          type: object
          properties:
            id:
              type: string
            handler_id:
              type: string
            type:
              type: string
            brand:
              type: string
            last_digits:
              type: string
            billing_address:
              $ref: "#/components/schemas/UcpAddress"
            credential:
              type: object
              properties:
                type:
                  type: string
                token:
                  type: string
    UcpOrderResponse:
      type: object
      required: [ucp, id, checkout_id, status, permalink_url, line_items, totals, created_at]
      properties:
        ucp:
          $ref: "#/components/schemas/UcpBlock"
        id:
          type: string
        checkout_id:
          type: string
        status:
          type: string
          example: pending
        permalink_url:
          type: string
        line_items:
          type: array
          items:
            $ref: "#/components/schemas/UcpLineItem"
        totals:
          type: array
          items:
            $ref: "#/components/schemas/UcpTotal"
        fulfillment:
          $ref: "#/components/schemas/UcpFulfillment"
        created_at:
          type: string
          format: date-time
