openapi: 3.1.0
info:
  title: PairRail Atlas Agent API
  version: 0.3.0
  description: |
    Governed, non-binding access to published seller commercial truth.

    Public (no auth):
      GET  /v1/public/catalog?sellerId={slug}
      POST /v1/public/discover?sellerId={slug}
      POST /v1/access-requests
      MCP tools: catalog_browse, catalog_discover, request_catalog_access

    Seller-approved (Bearer agent key after inbox approval):
      POST /v1/pricing/calculate, /v1/quote-requests, MCP pricing_calculate / quote_request
      Seller console: GET/POST /api/admin/access-requests/{id}/approve|deny
servers:
  - url: https://www.pairrail.com
    description: Production
  - url: https://pairrail.com
    description: Production (apex)
  - url: http://127.0.0.1:3000
    description: Local development
security:
  - agentBearer: []
paths:
  /v1/catalog:
    get:
      summary: Retrieve the current published catalog
      operationId: getCatalog
      responses:
        '200':
          description: Agent-safe catalog projection
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Envelope'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/catalog/offers:
    get:
      summary: List offers visible to this agent
      operationId: listOffers
      responses:
        '200':
          description: Published offers allowed by disclosure policy
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Envelope'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/discover:
    post:
      summary: Discover offers matching buyer context
      operationId: discoverOffers
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CommercialContext'
      responses:
        '200':
          description: Eligible, visible offers and policy decisions
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Envelope'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/pricing/calculate:
    post:
      summary: Calculate a deterministic non-binding price
      operationId: calculatePrice
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/CommercialContext'
                - type: object
                  required: [offerId]
                  properties:
                    offerId:
                      type: string
                    discountBps:
                      type: integer
                      minimum: 0
                    usage:
                      type: object
                      additionalProperties:
                        type: number
      responses:
        '200':
          description: Line-item calculation and policy decision
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Envelope'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/ValidationError'
  /v1/quote-requests:
    post:
      summary: Submit an idempotent, non-binding quote request
      operationId: createQuoteRequest
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          schema:
            type: string
            minLength: 1
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/CommercialContext'
                - type: object
                  required: [offerId]
                  properties:
                    offerId:
                      type: string
                    discountBps:
                      type: integer
                      minimum: 0
                    usage:
                      type: object
                      additionalProperties:
                        type: number
      responses:
        '201':
          description: Quote request created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Envelope'
        '200':
          description: Existing idempotent quote request returned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Envelope'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/ValidationError'
  /v1/quote-requests/{quoteRequestId}:
    get:
      summary: Retrieve quote and approval status
      operationId: getQuoteRequest
      parameters:
        - name: quoteRequestId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Quote request status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Envelope'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/readiness:
    get:
      summary: Retrieve current seller readiness
      operationId: getReadiness
      responses:
        '200':
          description: Readiness score, dimensions, blockers, and actions
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Envelope'
        '401':
          $ref: '#/components/responses/Unauthorized'
components:
  securitySchemes:
    agentBearer:
      type: http
      scheme: bearer
      bearerFormat: opaque
  schemas:
    CommercialContext:
      type: object
      properties:
        region:
          type: string
          example: US
        seats:
          type: integer
          minimum: 1
          example: 50
        commitmentMonths:
          type: integer
          minimum: 1
          example: 12
    Envelope:
      type: object
      required: [meta, data]
      properties:
        meta:
          type: object
          required: [requestId, traceId, binding]
          properties:
            requestId:
              type: string
            traceId:
              type: string
            catalogVersion:
              type: string
            policyVersion:
              type: string
            answerType:
              type: string
              enum: [deterministic, indicative]
            binding:
              type: boolean
              const: false
            validUntil:
              type: string
              format: date-time
        data:
          description: Operation-specific response data.
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
            message:
              type: string
            details:
              type: object
  responses:
    Unauthorized:
      description: Missing, invalid, revoked, or insufficiently scoped credential
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    ValidationError:
      description: Request or commercial validation failed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    NotFound:
      description: Resource not found or not visible to this agent
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
