openapi: 3.1.0
info:
  title: Kora Remittance API
  description: |
    Embed cross-border money transfer into any application — quoting, corridor
    routing, KYC-gated transfer creation, off-ramp payouts, and real-time
    lifecycle webhooks. Fully hosted and multi-tenant.

    ## Authentication

    - **Authorization**: Bearer token — a tenant API key (`kora_…`) for
      server-to-server calls, or an end-user **session token** for user-scoped
      actions (create transfer, list own history).
    - **X-Tenant-ID**: UUID identifying the tenant account.
    - **X-Partner-API-Key**: used only to mint a handoff token.

    See [Authentication](/remittance/authentication) for the full model.
  version: 1.0.0
  contact:
    name: Korastratum Engineering
    email: engineering@korastratum.com
  license:
    name: Proprietary
    url: https://korastratum.com/terms

servers:
  - url: https://api.korastratum.com/api/v1/remittance
    description: Production
  - url: https://sandbox.korastratum.com/api/v1/remittance
    description: Sandbox

tags:
  - name: Quotes
    description: Lock an FX rate and fees for a corridor.
  - name: Transfers
    description: Create transfers and retrieve transfer history.
  - name: Corridors
    description: Supported corridors, countries, and payout providers.
  - name: Handoff
    description: Partner-initiated SSO — mint and exchange handoff tokens.

paths:
  /quotes:
    post:
      tags: [Quotes]
      summary: Create a quote
      description: Locks the exchange rate and fees for a corridor for a short window.
      security:
        - BearerAuth: []
          TenantHeader: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateQuoteRequest"
      responses:
        "201":
          description: Quote created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Quote"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /transfers:
    post:
      tags: [Transfers]
      summary: Create a transfer
      description: |
        Creates a transfer against a quote. The sender is taken from the session
        token. Gated by KYC and screening — an unverified sender is rejected.
      security:
        - BearerAuth: []
          TenantHeader: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateTransferRequest"
      responses:
        "201":
          description: Transfer created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Transfer"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "422":
          description: Compliance/KYC not satisfied
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
    get:
      tags: [Transfers]
      summary: List transfers (history)
      description: |
        Returns the authenticated user's transfers, most recent first. The user
        id is taken from the session token — a caller only ever sees their own
        transfers. Paginated.
      security:
        - BearerAuth: []
          TenantHeader: []
      parameters:
        - name: page
          in: query
          description: 1-indexed page number.
          schema: { type: integer, default: 1, minimum: 1 }
        - name: pageSize
          in: query
          description: Items per page (max 100).
          schema: { type: integer, default: 20, minimum: 1, maximum: 100 }
      responses:
        "200":
          description: A page of transfers
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TransferListResponse"
        "401": { $ref: "#/components/responses/Unauthorized" }

  /transfers/{id}:
    get:
      tags: [Transfers]
      summary: Get a transfer
      description: Retrieve a single transfer by id, including its status-history events.
      security:
        - BearerAuth: []
          TenantHeader: []
      parameters:
        - name: id
          in: path
          required: true
          description: Transfer UUID.
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: The transfer
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Transfer"
        "404": { $ref: "#/components/responses/NotFound" }

  /corridors:
    get:
      tags: [Corridors]
      summary: List supported corridors
      description: Returns the corridors available to the tenant, with supported currencies and destination countries.
      security:
        - BearerAuth: []
          TenantHeader: []
      responses:
        "200":
          description: Supported corridors
          content:
            application/json:
              schema:
                type: object
                properties:
                  corridors:
                    type: array
                    items: { $ref: "#/components/schemas/Corridor" }

  /auth/partner-token:
    post:
      tags: [Handoff]
      summary: Mint a handoff token
      description: |
        Called from a partner backend with `X-Partner-API-Key`. Returns a
        one-time, short-lived token and a redirect URL. Redirect the user's
        browser to `redirectUrl` to land them in the flow already signed in.
      security:
        - PartnerKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MintRequest"
      responses:
        "200":
          description: Handoff token minted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MintResponse"
        "401": { $ref: "#/components/responses/Unauthorized" }

  /auth/exchange-token:
    post:
      tags: [Handoff]
      summary: Exchange a handoff token
      description: |
        Exchanges a one-time handoff token for a normal end-user session. Called
        by the hosted handoff page; no other credential is required.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ExchangeRequest"
      responses:
        "200":
          description: Session issued
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Session"
        "401": { $ref: "#/components/responses/Unauthorized" }

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: |
        Tenant API key (`kora_…`) for server-to-server calls, or an end-user
        session token for user-scoped actions.
    TenantHeader:
      type: apiKey
      in: header
      name: X-Tenant-ID
      description: UUID identifying the tenant account.
    PartnerKey:
      type: apiKey
      in: header
      name: X-Partner-API-Key
      description: Server-side partner key, used only to mint a handoff token.

  responses:
    BadRequest:
      description: Invalid request
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    Unauthorized:
      description: Missing or invalid credentials
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }

  schemas:
    Money:
      type: string
      description: Decimal amount as a string, to avoid float rounding.
      example: "100.00"

    CreateQuoteRequest:
      type: object
      required: [sendAmount, sendCurrency, receiveCurrency, destinationCountry]
      properties:
        sendAmount: { $ref: "#/components/schemas/Money" }
        sendCurrency: { type: string, example: "USD" }
        receiveCurrency: { type: string, example: "PHP" }
        destinationCountry: { type: string, description: ISO 3166-1 alpha-2, example: "PH" }

    Quote:
      type: object
      properties:
        quoteId: { type: string, format: uuid }
        exchangeRate: { type: string, example: "56.0000" }
        sendAmount: { $ref: "#/components/schemas/Money" }
        sendCurrency: { type: string, example: "USD" }
        receiveAmount: { $ref: "#/components/schemas/Money" }
        receiveCurrency: { type: string, example: "PHP" }
        feeTotal: { $ref: "#/components/schemas/Money" }
        expiresAt: { type: string, format: date-time }

    CreateTransferRequest:
      type: object
      required: [quoteId, recipientId, transferReason, sourceOfFunds, relationshipToRecipient]
      properties:
        quoteId: { type: string, format: uuid }
        recipientId: { type: string, format: uuid }
        walletId: { type: string, format: uuid, description: Optional source wallet. }
        channel: { type: string, enum: [mobile, web, kora, api], description: Optional origination channel. }
        transferReason: { type: string, example: "family_support" }
        sourceOfFunds: { type: string, example: "salary" }
        relationshipToRecipient: { type: string, example: "family" }
        signedAgreementId: { type: string, format: uuid, description: Optional. }

    Transfer:
      type: object
      properties:
        id: { type: string, format: uuid }
        reference: { type: string, example: "TXN-8ZK3QP" }
        status:
          type: string
          enum: [created, funded, processing, completed, failed, refunded]
        sendAmount: { $ref: "#/components/schemas/Money" }
        sendCurrency: { type: string, example: "USD" }
        receiveAmount: { $ref: "#/components/schemas/Money" }
        receiveCurrency: { type: string, example: "PHP" }
        exchangeRate: { type: string, example: "56.0000" }
        feeTotal: { $ref: "#/components/schemas/Money" }
        recipientId: { type: string, format: uuid }
        recipientName: { type: string, example: "Maria S." }
        destinationCountry: { type: string, example: "PH" }
        createdAt: { type: string, format: date-time }
        events:
          type: array
          description: Status-history timeline (present on single-transfer reads).
          items:
            type: object
            properties:
              status: { type: string }
              at: { type: string, format: date-time }

    TransferListResponse:
      type: object
      properties:
        transfers:
          type: array
          items: { $ref: "#/components/schemas/Transfer" }
        totalCount: { type: integer, example: 42 }
        page: { type: integer, example: 1 }
        pageSize: { type: integer, example: 20 }

    Corridor:
      type: object
      properties:
        sendCurrency: { type: string, example: "USD" }
        receiveCurrency: { type: string, example: "PHP" }
        destinationCountry: { type: string, example: "PH" }
        enabled: { type: boolean, example: true }

    MintRequest:
      type: object
      required: [partnerUserId]
      properties:
        partnerUserId: { type: string, description: Your stable id for the user., example: "usr_abc123" }
        phone: { type: string, example: "+15551234567" }
        email: { type: string, format: email, example: "user@example.com" }
        firstName: { type: string, example: "Alex" }
        lastName: { type: string, example: "Rivera" }
        dateOfBirth: { type: string, description: RFC3339 or YYYY-MM-DD., example: "1990-04-01" }
        countryCode: { type: string, example: "US" }
        returnUrl: { type: string, description: Where "back to your app" links go; validated against your allowlist., example: "https://app.yourbrand.com/wallet" }
        context:
          type: object
          additionalProperties: true
          description: Arbitrary values passed through to the flow.

    MintResponse:
      type: object
      properties:
        token: { type: string, description: One-time handoff token. }
        redirectUrl: { type: string, example: "https://app.yourbrand.com/handoff?t=..." }
        expiresIn: { type: integer, description: Seconds until the token expires., example: 120 }

    ExchangeRequest:
      type: object
      required: [token]
      properties:
        token: { type: string, description: The one-time handoff token. }

    Session:
      type: object
      properties:
        token: { type: string, description: End-user session (access) token. }
        refreshToken: { type: string }
        expiresIn: { type: integer, description: Access-token lifetime in seconds., example: 3600 }

    Error:
      type: object
      properties:
        error: { type: string, example: "invalid request" }
        code: { type: string, example: "VALIDATION_ERROR" }

security:
  - BearerAuth: []
    TenantHeader: []
