openapi: 3.1.0
info:
  title: Gift Card Layer for AI Agents
  version: 1.0.0
  description: >
    REST contract mirroring the 5 MCP tools. Amounts are integer minor units.
    Errors use the closed taxonomy; every error carries an agent-actionable hint.
servers:
  - url: https://api.example.com
security:
  - bearerAuth: []
paths:
  /v1/brands:
    get:
      operationId: list_brands
      summary: Search purchasable gift card brands
      parameters:
        - {name: query, in: query, schema: {type: string}}
        - {name: country, in: query, schema: {type: string, default: US}}
        - {name: category, in: query, schema: {type: string}}
        - {name: limit, in: query, schema: {type: integer, default: 20, maximum: 50}}
      responses:
        "200":
          description: Brand list
          content:
            application/json:
              schema:
                type: object
                properties:
                  brands:
                    type: array
                    items: {$ref: "#/components/schemas/Brand"}
        default: {$ref: "#/components/responses/Error"}
  /v1/purchases:
    post:
      operationId: buy_giftcard
      summary: Start a gift card purchase (async)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [brand_id, amount_minor, currency]
              properties:
                brand_id: {type: string}
                amount_minor: {type: integer, minimum: 1}
                currency: {type: string, default: USD}
                country: {type: string}
                client_ref:
                  type: string
                  description: Agent idempotency key. Re-use returns the existing task.
                metadata: {type: object, additionalProperties: true}
      responses:
        "202":
          description: Accepted; complete via webhook or polling
          content:
            application/json:
              schema:
                type: object
                required: [task_id, status]
                properties:
                  task_id: {type: string, format: uuid}
                  status: {type: string, enum: [accepted]}
                  estimated_seconds: {type: integer}
        default: {$ref: "#/components/responses/Error"}
  /v1/purchases/{task_id}:
    get:
      operationId: get_purchase
      summary: Poll purchase status
      parameters:
        - {name: task_id, in: path, required: true, schema: {type: string, format: uuid}}
      responses:
        "200":
          description: Purchase state
          content:
            application/json:
              schema: {$ref: "#/components/schemas/Purchase"}
        default: {$ref: "#/components/responses/Error"}
  /v1/purchases/{task_id}/code:
    post:
      operationId: get_code
      summary: Release the redemption code (single release, logged)
      parameters:
        - {name: task_id, in: path, required: true, schema: {type: string, format: uuid}}
      responses:
        "200":
          description: Code released to the purchasing agent exactly once
          content:
            application/json:
              schema: {$ref: "#/components/schemas/CodeRelease"}
        default: {$ref: "#/components/responses/Error"}
  /v1/balance:
    get:
      operationId: check_balance
      summary: Calling agent's budget and platform status
      responses:
        "200":
          description: Balance snapshot
          content:
            application/json:
              schema:
                type: object
                properties:
                  budget_remaining_minor: {type: integer}
                  daily_limit_minor: {type: integer}
                  monthly_limit_minor: {type: integer}
                  float_days_cover: {type: number}
                  kill_switch: {type: string, enum: [active, paused]}
        default: {$ref: "#/components/responses/Error"}
  /v1/approvals:
    post:
      operationId: request_approval
      summary: Re-ping the human approver for a parked task
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [task_id, reason]
              properties:
                task_id: {type: string, format: uuid}
                reason: {type: string, maxLength: 500}
      responses:
        "200":
          description: Approval state
          content:
            application/json:
              schema:
                type: object
                properties:
                  approval_id: {type: string, format: uuid}
                  status: {type: string, enum: [pending, approved, rejected, expired]}
        default: {$ref: "#/components/responses/Error"}
  /v1/webhooks/bitrefill:
    post:
      operationId: bitrefill_webhook
      summary: Inbound provider webhook (HMAC-verified, event-id deduplicated)
      security: []
      parameters:
        - name: X-Bitrefill-Signature
          in: header
          required: true
          description: HMAC-SHA256 of the raw body keyed by BITREFILL_WEBHOOK_SECRET; verified before processing.
          schema: {type: string}
      responses:
        "204": {description: Accepted}
        "401": {description: Signature verification failed}
components:
  securitySchemes:
    bearerAuth: {type: http, scheme: bearer}
  responses:
    Error:
      description: Taxonomy error
      content:
        application/json:
          schema: {$ref: "#/components/schemas/Error"}
  schemas:
    Brand:
      type: object
      properties:
        brand_id: {type: string}
        name: {type: string}
        country: {type: string}
        currency: {type: string}
        denominations_minor: {type: array, items: {type: integer}}
        min_minor: {type: integer}
        max_minor: {type: integer}
        in_stock: {type: boolean}
    Purchase:
      type: object
      properties:
        task_id: {type: string, format: uuid}
        state:
          type: string
          enum: [created, awaiting_approval, invoice_pending, invoiced, paid, completed, abandoned, dlq, policy_denied]
        brand_id: {type: string}
        amount_minor: {type: integer}
        currency: {type: string}
        invoice_id: {type: string}
        code_available: {type: boolean}
        created_at: {type: string, format: date-time}
    CodeRelease:
      type: object
      properties:
        code: {type: string}
        link: {type: string}
        pin: {type: string}
        instructions: {type: string}
        barcode_value: {type: string}
        face_minor: {type: integer}
        remaining_minor: {type: integer}
    Error:
      type: object
      required: [code, message]
      properties:
        code:
          type: string
          enum: [POLICY_DENIED, BUDGET_EXCEEDED, APPROVAL_REQUIRED, APPROVAL_REJECTED,
                 OUT_OF_STOCK, PAYMENT_FAILED, INVOICE_EXPIRED, PROVIDER_HOLD,
                 ALREADY_RELEASED, NOT_FOUND, UNAUTHORIZED, RETRYABLE]
        message: {type: string}
        hint: {type: string, description: Agent-actionable next step}
        task_id: {type: string, format: uuid}
        retry_after: {type: integer, description: Seconds; present when code=RETRYABLE}
webhooks:
  purchaseCompleted:
    post:
      summary: Outbound event to the agent's registered webhook (agents.webhook_url)
      description: >
        Signed with the agent's webhook secret. Verify X-Giftcard-Signature
        (HMAC-SHA256 of the raw body) before trusting; event_id is stable for
        at-least-once delivery, so dedupe on it.
      parameters:
        - name: X-Giftcard-Signature
          in: header
          required: true
          schema: {type: string}
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [event_id, event_type, task_id]
              properties:
                event_id: {type: string}
                event_type: {type: string, enum: [purchase.completed, purchase.failed]}
                task_id: {type: string, format: uuid}
                state: {type: string}
                created: {type: string, format: date-time}
      responses:
        "200": {description: Acknowledged}
