openapi: 3.1.0
info:
  title: Saint Aura Agency API
  version: "1.0.0"
  description: >
    White-label agency REST API (docs/whitelabel-agency-plan.md §9). Authenticate
    with a scoped bearer key created in the panel (API & webhooks). All money is
    in integer minor units of the agency currency. Writes accept an
    `Idempotency-Key` header (≤128 chars, 24 h).
servers:
  - url: https://www.saintaura.co/api/agency/v1
  - url: https://whitelabel.saintaura.co/api/agency/v1
security:
  - bearerAuth: []
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: "Authorization: Bearer sa_live_<32 chars>"
  schemas:
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code: { type: string }
            message: { type: string }
            details: { type: object }
    Client:
      type: object
      required: [id, name, slug, client_ref, category, status, tier, cycle, review_url, created_at]
      properties:
        id: { type: string }
        name: { type: string }
        slug: { type: string }
        client_ref: { type: [string, "null"] }
        category: { type: string }
        status: { type: string, enum: [none, trial, active, halted, cancelled] }
        tier: { type: [string, "null"] }
        cycle: { type: [string, "null"] }
        review_url: { type: string }
        created_at: { type: string }
    Subscription:
      type: object
      required: [id, business_id, status, tier, cycle, cancel_at_period_end]
      properties:
        id: { type: string }
        business_id: { type: string }
        status: { type: string }
        tier: { type: string }
        cycle: { type: string }
        period_end_at: { type: [string, "null"] }
        trial_ends_at: { type: [string, "null"] }
        cancel_at_period_end: { type: boolean }
        wholesale_price: { type: [integer, "null"] }
        retail_price: { type: [integer, "null"] }
        retail_currency: { type: [string, "null"] }
    ClientList:
      type: object
      required: [data, next_cursor]
      properties:
        data: { type: array, items: { $ref: "#/components/schemas/Client" } }
        next_cursor: { type: [string, "null"] }
    ClientCreated:
      type: object
      required: [client, subscription, activated]
      properties:
        client: { $ref: "#/components/schemas/Client" }
        subscription: { oneOf: [{ $ref: "#/components/schemas/Subscription" }, { type: "null" }] }
        activated: { type: boolean }
    ClientDetail:
      type: object
      required: [client, subscription, stats_30d]
      properties:
        client: { $ref: "#/components/schemas/Client" }
        subscription: { oneOf: [{ $ref: "#/components/schemas/Subscription" }, { type: "null" }] }
        stats_30d:
          type: object
          required: [scans, reviews, feedback]
          properties:
            scans: { type: integer }
            reviews: { type: integer }
            feedback: { type: integer }
    SubscriptionEnvelope:
      type: object
      required: [subscription]
      properties:
        subscription: { $ref: "#/components/schemas/Subscription" }
    Wallet:
      type: object
      required: [balance, currency, runway_days, lots]
      properties:
        balance: { type: integer }
        currency: { type: string }
        runway_days: { type: [integer, "null"] }
        lots: { type: array, items: { type: object } }
    LedgerEntry:
      type: object
      required: [id, amount, type, created_at]
      properties:
        id: { type: string }
        amount: { type: [integer, "null"] }
        type: { type: [string, "null"] }
        description: { type: [string, "null"] }
        balance_after: { type: [integer, "null"] }
        created_at: { type: string }
    LedgerList:
      type: object
      required: [data, next_cursor]
      properties:
        data: { type: array, items: { $ref: "#/components/schemas/LedgerEntry" } }
        next_cursor: { type: [string, "null"] }
    Prices:
      type: object
      required: [currency, offered_tiers, wholesale_prices]
      properties:
        currency: { type: string }
        default_tier: { type: [string, "null"] }
        offered_tiers: { type: array, items: { type: string } }
        wholesale_prices: { type: object }
        extra_location_price: { type: [object, "null"] }
    Event:
      type: object
      required: [id, type, api_version, agency_id, data, created_at]
      properties:
        id: { type: string }
        type: { type: string }
        api_version: { type: string }
        agency_id: { type: string }
        data: { type: object }
        created_at: { type: string }
    EventList:
      type: object
      required: [data, next_cursor]
      properties:
        data: { type: array, items: { $ref: "#/components/schemas/Event" } }
        next_cursor: { type: [string, "null"] }
  responses:
    Unauthorized:
      description: Missing/invalid key
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    Forbidden:
      description: Missing scope or suspended agency
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    NotFound:
      description: Resource not found (including cross-agency ids)
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
paths:
  /clients:
    get:
      summary: List clients
      parameters:
        - { in: query, name: status, schema: { type: string } }
        - { in: query, name: tier, schema: { type: string } }
        - { in: query, name: ref, schema: { type: string } }
        - { in: query, name: q, schema: { type: string } }
        - { in: query, name: cursor, schema: { type: string } }
        - { in: query, name: limit, schema: { type: integer, maximum: 100 } }
      responses:
        "200": { description: OK, content: { application/json: { schema: { $ref: "#/components/schemas/ClientList" } } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
    post:
      summary: Create and activate a client (wholesale wallet debit required)
      responses:
        "201": { description: Created, content: { application/json: { schema: { $ref: "#/components/schemas/ClientCreated" } } } }
        "402": { description: Wallet insufficient, content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
        "409": { description: Slug taken / limit reached, content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
        "422": { description: Validation, content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
  /clients/{id}:
    get:
      summary: Get a client
      parameters: [{ in: path, name: id, required: true, schema: { type: string } }]
      responses:
        "200": { description: OK, content: { application/json: { schema: { $ref: "#/components/schemas/ClientDetail" } } } }
        "404": { $ref: "#/components/responses/NotFound" }
  /clients/{id}/subscription/change-plan:
    post:
      summary: Change plan
      parameters: [{ in: path, name: id, required: true, schema: { type: string } }]
      responses:
        "200": { description: OK, content: { application/json: { schema: { $ref: "#/components/schemas/SubscriptionEnvelope" } } } }
        "404": { $ref: "#/components/responses/NotFound" }
  /clients/{id}/subscription/cancel:
    post:
      summary: Cancel at period end
      parameters: [{ in: path, name: id, required: true, schema: { type: string } }]
      responses:
        "200": { description: OK, content: { application/json: { schema: { $ref: "#/components/schemas/SubscriptionEnvelope" } } } }
        "404": { $ref: "#/components/responses/NotFound" }
  /clients/{id}/subscription/resume:
    post:
      summary: Resume a halted subscription or activate a legacy trial with a wallet debit
      parameters: [{ in: path, name: id, required: true, schema: { type: string } }]
      responses:
        "200": { description: OK, content: { application/json: { schema: { $ref: "#/components/schemas/SubscriptionEnvelope" } } } }
        "404": { $ref: "#/components/responses/NotFound" }
  /wallet:
    get:
      summary: Wallet balance + runway
      responses:
        "200": { description: OK, content: { application/json: { schema: { $ref: "#/components/schemas/Wallet" } } } }
  /wallet/ledger:
    get:
      summary: Paginated ledger
      responses:
        "200": { description: OK, content: { application/json: { schema: { $ref: "#/components/schemas/LedgerList" } } } }
  /prices:
    get:
      summary: Wholesale price table
      responses:
        "200": { description: OK, content: { application/json: { schema: { $ref: "#/components/schemas/Prices" } } } }
  /events:
    get:
      summary: Paginated event log (webhook payloads)
      responses:
        "200": { description: OK, content: { application/json: { schema: { $ref: "#/components/schemas/EventList" } } } }
