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

# Development Guide

> Environments, authentication, request structure, and error handling for the Kotani Pay API v3

## Environments

Always develop against sandbox first. Production and sandbox share the same API structure — only the base URL and credentials differ.

| Environment    | Base URL                           |
| -------------- | ---------------------------------- |
| **Sandbox**    | `https://sandbox-api.kotanipay.io` |
| **Production** | `https://api.kotanipay.io`         |

Sandbox API keys do not work in production and vice versa. Generate environment-specific credentials for each.

***

## Authentication

Every request requires an `Authorization: Bearer` header. Both API keys and JWT tokens are accepted in the same header format.

**API keys** are the right choice for server-to-server integrations — they do not expire unless revoked. **JWT tokens** are short-lived (1 hour) and mainly used during the API key generation flow.

See [API Keys](/v3/getting-started/api-keys) for how to generate a key and the difference between integrator-level and user-bound keys.

***

## Request & Response Format

All endpoints accept and return JSON. Set `Content-Type: application/json` on requests with a body.

All responses follow a consistent envelope:

**Success:**

```json theme={null}
{
  "success": true,
  "message": "...",
  "data": {}
}
```

**Error:**

```json theme={null}
{
  "success": false,
  "message": "Descriptive error message",
  "error_code": 400,
  "data": {}
}
```

See [Error Handling](/v3/essentials/error-handling) for the full status code reference and error payload details.

***

## Error Codes

| Status | Meaning                                                        |
| ------ | -------------------------------------------------------------- |
| `200`  | Success                                                        |
| `400`  | Bad request — check your request body and parameters           |
| `401`  | Unauthorized — API key or JWT is missing or invalid            |
| `403`  | Forbidden — key exists but lacks permission for this operation |
| `404`  | Resource not found                                             |
| `429`  | Rate limit exceeded                                            |
| `500`  | Internal server error — contact support if persistent          |

***

## Rate Limits

The API applies per-endpoint, per-key rate limits with three tiers:

| Tier          | Applied to                                     |
| ------------- | ---------------------------------------------- |
| **General**   | Standard create/update operations              |
| **Sensitive** | Authentication and security-critical endpoints |
| **Read-only** | GET/list endpoints                             |

When rate limited, the response includes a `retryAfter` value in seconds. Specific limits are configurable per integrator — contact your account manager if you need a higher limit for a high-volume integration.

***

## Idempotency

For deposits and withdrawals, always send a reference ID. The field name differs by flow:

* Deposits use `reference_id` (snake\_case)
* Withdrawals use `referenceId` (camelCase)

Reference IDs must be unique per integrator. If a request times out or fails at the network level, retrying with the same reference ID will not create a duplicate transaction.

***

## Webhooks

Use webhooks instead of polling. Configure your endpoint in the dashboard under **Settings**.

There are two delivery modes — which one you receive depends on whether a webhook secret is configured on your account. When a secret is configured, each request includes an `X-Kotani-Signature` header you must verify before processing. Without a secret, Kotani Pay posts directly to the `callbackUrl` on each transaction with no signature headers.

See [Webhooks](/v3/essentials/webhooks) for both delivery modes, all event types, payload schemas, and signature verification.

***

## Going to Production

1. Switch your base URL to `https://api.kotanipay.io`
2. Run the magic link login flow against production to get a production JWT
3. Generate a production API key using that JWT
4. Update your webhook endpoint to point to your production server

<Warning>
  Sandbox credentials do not work against the production URL. Always generate environment-specific keys.
</Warning>

***

## Transaction Flows

Each flow walks through the full lifecycle — what to call, what the customer experiences, how the callback arrives, and how to poll for status.

<CardGroup cols={2}>
  <Card title="Deposit Flow" icon="arrow-down" href="/v3/flows/deposit-flow">
    Mobile money, bank checkout, and card collections
  </Card>

  <Card title="Withdrawal Flow" icon="arrow-up" href="/v3/flows/withdrawal-flow">
    Mobile money and bank disbursements
  </Card>

  <Card title="Onramp Flow" icon="coins" href="/v3/flows/onramp-flow">
    Fiat → crypto via mobile money or bank
  </Card>

  <Card title="Offramp Flow" icon="arrow-right-arrow-left" href="/v3/flows/offramp-flow">
    Crypto → fiat disbursement and refund handling
  </Card>

  <Card title="Payment Links" icon="link" href="/v3/flows/payment-links-flow">
    Hosted checkout pages with multiple payment methods
  </Card>

  <Card title="Bulk Payments" icon="list" href="/v3/flows/bulk-payments-flow">
    CSV-based mass disbursements with batch tracking
  </Card>
</CardGroup>
