> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kirafin.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Go from credentials to your first payout against the Kira sandbox.

This walkthrough takes you end to end in the sandbox: authenticate, pin your version, create a user and a virtual account, and make a payout. Every example uses the sandbox base URL `https://api.balampay.com/sandbox`.

<Warning>
  **Sandbox seed IDs are tenant-scoped placeholders.** The sandbox does not ship working shared seed IDs — values like `verified_user_id`, `virtual_account_id`, and `recipient_id` are placeholders, not live resources. Sandbox tenancy isolates these resources per client, so any baked-in UUID would return `404` for an integrator using their own `api_key`.

  You can create and verify a **user** (the sandbox triggers verification automatically for a complete user, but approval is a **manual** step — per Kira it can take **up to 24 hours**, not seconds), create recipients, and create a **virtual account** — all self-serve. A new sandbox VA activates automatically within about a minute (see [Known limitations](/guides/known-limitations)). Cross-tenant shared seeds are coming, but are not available yet.
</Warning>

<Steps>
  <Step title="Get your credentials">
    Kira provides three credentials for your tenant:

    * `api_key` — sent as the `x-api-key` header on **every** request, including `/auth`.
    * `client_id` and `password` — exchanged at `POST /auth` for a short-lived access token.

    Keep them out of source control. The examples below read them from environment variables:

    ```bash theme={null}
    export KIRA_API_KEY="<your api_key>"
    export KIRA_CLIENT_ID="<your client_id>"
    export KIRA_PASSWORD="<your password>"
    ```
  </Step>

  <Step title="Authenticate">
    Call `POST /auth` with your `client_id` and `password` in the body and your `x-api-key` header. The `/auth` endpoint itself needs only `x-api-key` — no bearer token yet.

    ```bash theme={null}
    curl https://api.balampay.com/sandbox/auth \
      -H "x-api-key: $KIRA_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "client_id": "'"$KIRA_CLIENT_ID"'", "password": "'"$KIRA_PASSWORD"'" }'
    ```

    The response wraps the token in the success envelope:

    ```json theme={null}
    {
      "message": "...",
      "data": {
        "access_token": "...",
        "expires_in": 3600,
        "token_type": "Bearer"
      }
    }
    ```

    Use the returned `access_token` as your bearer token on every other request. It is valid for `3600` seconds, so refresh it before it expires.

    ```bash theme={null}
    export ACCESS_TOKEN="<data.access_token from the response>"
    ```

    See [Authentication](/guides/authentication) for the full token lifecycle.
  </Step>

  <Step title="Pin your version">
    Send the `X-Api-Version: 2026-04-14` header on **every** request. The header always selects the documented schema, always wins over your account's stored default, and works no matter which version your account is pinned to — every example below includes it.

    Optionally, if your account's current default is `2026-04-14` or older, you can also pin your account once with `POST /v1/versioning/upgrade`, after which the header becomes optional. Re-pinning to the **same** version is a safe no-op. See [Versioning](/guides/versioning) for details.

    ```bash theme={null}
    curl https://api.balampay.com/sandbox/v1/versioning/upgrade \
      -H "Authorization: Bearer $ACCESS_TOKEN" \
      -H "x-api-key: $KIRA_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "target_version": "2026-04-14" }'
    ```

    <Warning>
      **Pinning is forward-only.** `POST /v1/versioning/upgrade` only moves your account pin to the **same or a newer** version. If your account is already pinned to a version newer than `2026-04-14`, this call returns `400 "Cannot downgrade API version from '<current>' to '2026-04-14'. Upgrades are forward-only. To use an older documented version per-request, send the X-Api-Version header — it is exempt from this rule."` — there is no self-serve downgrade. To integrate against `2026-04-14` regardless of your account pin, keep sending the per-request `X-Api-Version: 2026-04-14` header: it always wins over the account default and is **not** subject to the forward-only rule.
    </Warning>
  </Step>

  <Step title="Create a user">
    Create a user with `POST /v1/users`. A user is either an **individual** or a **business**. `source_of_funds` is **optional** at create — the call returns `201` without it — but it is a KYC questionnaire field that business (KYB) products require, so omitting it doesn't reject the user outright; it leaves verification unable to proceed until you supply it. Send it on create to keep the flow moving.

    ```bash theme={null}
    curl https://api.balampay.com/sandbox/v1/users \
      -H "Authorization: Bearer $ACCESS_TOKEN" \
      -H "x-api-key: $KIRA_API_KEY" \
      -H "X-Api-Version: 2026-04-14" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: $(uuidgen)" \
      -d '{ "type": "individual", "first_name": "Ana", "last_name": "García", "email": "ana+sandbox@example.com", "source_of_funds": "salary" }'
    ```

    This minimal body returns `201` as-is. To take the user all the way to `VERIFIED`, send the complete field set for its category (documents, address, questionnaire) — see [Creating users](/guides/creating-users) for full example payloads.

    <Warning>
      **The sandbox triggers verification automatically — but approval is manual, not instant.** Verification triggers on its own once the payload is complete for a product (all required fields and documents — the `missing_fields` map on the response lists what is still outstanding). Per Kira, final approval can take **up to 24 hours**; a user can land in **manual review** (`status: REVIEW`) at any point in that window — more likely for businesses (KYB) and for low-quality or placeholder document images — which a Kira operator resolves. (Don't build against a "verifies in seconds" assumption.) An **incomplete** user is not rejected — it stays `CREATED` with `verification_triggered: false` until you supply the missing fields with `PUT /v1/users/{id}`. A terminal `REJECTED` means the provider actually declined the submitted data; the reasons arrive only in the `user.verification.failed` webhook. Gate your state machine on `status`, not `verification_status`.
    </Warning>
  </Step>

  <Step title="Create a virtual account">
    Create a virtual account for the user with `POST /v1/virtual-accounts`.

    ```bash theme={null}
    curl https://api.balampay.com/sandbox/v1/virtual-accounts \
      -H "Authorization: Bearer $ACCESS_TOKEN" \
      -H "x-api-key: $KIRA_API_KEY" \
      -H "X-Api-Version: 2026-04-14" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: $(uuidgen)" \
      -d '{ "user_id": "<verified user id>", "type": "US_BANK", "mode": "fiat", "bank": "slovak_savings_bank" }'
    ```

    `bank: "slovak_savings_bank"` selects the recommended default rail. The `bank` value is **environment-scoped**: in **production** the same rail is `bank: "portage"` (sending `portage` in sandbox returns `400 "Invalid bank"`).

    <Note>
      A VA being `approved` does not mean funds are ready: confirm readiness via `account_number` being a real value (it stays `null` while provisioning; on the ACT rail — `bank: "austin_capital_trust"` — it shows the `"PENDING-ACT-ACCOUNT"` placeholder instead, and ACT allows **one VA per user**: re-creating returns `409 Conflict`). A new sandbox VA activates automatically — the sandbox polls provider status every 15 seconds, so expect activation (and the `virtual_account.activated` webhook) within about a minute (see [Known limitations](/guides/known-limitations)).
    </Note>
  </Step>

  <Step title="Make a payout">
    In the sandbox there's no deposit to run first — an active VA reports a fixed provider `available_balance` (check it with `GET /v1/virtual-accounts/{id}/balance`).

    Payouts are a two-step flow: **price it**, then **create it**.

    Price with `POST /v1/quotations`. Pin `X-Api-Version: 2026-06-01` on this call to get the itemized breakdown — one object per fee, plus the conversion rate and tier context. The quote is redeemable for **15 minutes**.

    ```bash theme={null}
    # 1. Quote — returns quote_id + an itemized fee breakdown
    curl https://api.balampay.com/sandbox/v1/quotations \
      -H "Authorization: Bearer $ACCESS_TOKEN" \
      -H "x-api-key: $KIRA_API_KEY" \
      -H "X-Api-Version: 2026-06-01" \
      -H "Content-Type: application/json" \
      -d '{
            "virtual_account_id": "'"$VA_ID"'",
            "amount": "50.00",
            "rail": "WIRE_DOMESTIC",
            "target": { "currency": "USD" }
          }'

    # 2. Create the payout, redeeming the quote
    curl https://api.balampay.com/sandbox/v1/virtual-accounts/$VA_ID/payout \
      -H "Authorization: Bearer $ACCESS_TOKEN" \
      -H "x-api-key: $KIRA_API_KEY" \
      -H "X-Api-Version: 2026-04-14" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: $(uuidgen)" \
      -d '{ "recipient_id": "<recipient id>", "amount": "50.00", "quote_id": "<quote_id>" }'
    ```

    `rail` is the bank rail you intend to pay out on (`ACH_STANDARD`, `ACH_SAME_DAY`, `WIRE_DOMESTIC`) — declare it so the quote prices that rail's fixed fee. Paying from the VA's on-book balance instead of a fresh deposit? Add `"from_held_balance": true`. Funding the payout with stablecoin? Send `payment_instructions` instead. Both, and the full response shape, are in [Itemized quotations](/guides/itemized-quotations).

    <Note>
      **`payout/preview` is deprecated as of July 16, 2026** and is not shown here. It still works for existing integrations — see [Payouts, quotations, and recipients](/guides/payouts-quotations-recipients) — but do not build new integrations on it.
    </Note>

    <Note>
      There is no fee schedule endpoint and no `dry_run` mode. For amounts below the minimum, `POST /v1/quotations` returns `400` with `code: "fees_exceed_amount"` and the message `"The fees exceed the provided amount. Please increase the amount."`, plus a `details` object (`amount`, `total_fees`) you can use to size the next attempt. The `201` create response returns lowercase `status: "created"`, while `GET /v1/payouts/{id}` returns `CREATED` for the same payout — compare statuses case-insensitively.
    </Note>
  </Step>
</Steps>

## Next steps

<CardGroup cols={2}>
  <Card title="Authentication" icon="key" href="/guides/authentication">
    The full token lifecycle, required headers, and credential handling.
  </Card>

  <Card title="Webhooks" icon="webhook" href="/guides/webhooks">
    Register an endpoint and consume deposit, user, and payout events.
  </Card>

  <Card title="API reference" icon="code" href="/api-reference">
    Browse every endpoint, request shape, and response in the interactive reference.
  </Card>
</CardGroup>
