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

# Integration Guide

> Best practices for integrating the Afriex Business API

## Integration Guide

This guide covers best practices for integrating the Afriex Business API into your application.

<CardGroup cols={2}>
  <Card title="OpenAPI spec" icon="file-code" href="https://api.afriex.com/api/docs.json">
    Get the full OpenAPI spec at this endpoint for code generation, SDKs, and API tooling.
  </Card>

  <Card title="MCP server" icon="plug" href="/mcp/introduction">
    Add the Afriex Business API to your MCP client and search the docs from Cursor, Claude Code, and other MCP-enabled tools.
  </Card>
</CardGroup>

## Environments

The Afriex API provides two environments:

| Environment | Base URL                         | Purpose                 |
| ----------- | -------------------------------- | ----------------------- |
| Staging     | `https://sandbox.api.afriex.com` | Testing and development |
| Production  | `https://api.afriex.com`         | Live transactions       |

<Warning>
  **Never use production API keys in your testing environment.**
</Warning>

## Authentication

All API requests require authentication using your API key in the `x-api-key` header:

```bash theme={null}
curl -H "x-api-key: YOUR_API_KEY" \
  https://sandbox.api.afriex.com/api/v1/customer
```

## Idempotency

For critical operations like creating transactions, use idempotency keys to prevent duplicate processing:

```json theme={null}
{
  "customerId": "CUSTOMER_ID",
  "type": "WITHDRAW",
  "sourceAmount": "10",
  "destinationAmount": 5000,
  "sourceCurrency": "USD",
  "destinationCurrency": "NGN",
  "destinationId": "PAYMENT_METHOD_ID",
  "meta": {
    "idempotencyKey": "unique-key-123",
    "reference": "YOUR_REFERENCE"
  }
}
```

## Error Handling

The API uses standard HTTP status codes:

| Status | Meaning                                            |
| ------ | -------------------------------------------------- |
| 200    | Success                                            |
| 201    | Created                                            |
| 204    | No Content - Successful deletion, no body returned |
| 400    | Bad Request - Check your request payload           |
| 401    | Unauthorized - Invalid or missing API key          |
| 404    | Not Found - Resource doesn't exist                 |
| 429    | Too Many Requests - Rate limit exceeded            |
| 500    | Server Error - Contact support                     |

### Error response format

All error responses follow a consistent structure:

```json theme={null}
{
  "code": "INVALID_BUSINESS_CUSTOMER_REQUEST",
  "error": "Bad Request",
  "details": {
    "errorMessage": "PHONE_COUNTRY_MISMATCH",
    "friendlyMessage": "The phone number does not match the customer's country."
  }
}
```

| Field                     | Description                                                |
| ------------------------- | ---------------------------------------------------------- |
| `code`                    | Machine-readable error code for programmatic handling      |
| `error`                   | Short human-readable error category                        |
| `details.errorMessage`    | Specific reason for the error                              |
| `details.friendlyMessage` | User-friendly message suitable for displaying to end users |

<Tip>
  Use the `details.friendlyMessage` field to surface errors directly to your users, and use `code` for programmatic error handling in your application logic.
</Tip>

## Pagination

List endpoints support pagination with `page` and `limit` parameters:

```bash theme={null}
curl -H "x-api-key: YOUR_API_KEY" \
  "https://sandbox.api.afriex.com/api/v1/customer?page=0&limit=10"
```

| Parameter | Type    | Default | Description                                  |
| --------- | ------- | ------- | -------------------------------------------- |
| `page`    | integer | `0`     | Page number, zero-indexed (first page = `0`) |
| `limit`   | integer | `10`    | Number of results per page (max `100`)       |

All list endpoints return results in this shape:

```json theme={null}
{
  "page": 0,
  "total": 20,
  "data": []
}
```

Use the `total` field to calculate the number of pages: `Math.ceil(total / limit)`.

## Webhook Integration

Set up webhooks to receive real-time event notifications:

1. Configure your webhook URL in the [Dashboard](https://business.afriex.com/)
2. Verify the `x-webhook-signature` header (`RSA-SHA256`, base64) using the **raw** request body
3. Use the webhook public key from **Dashboard -> Developers -> Webhooks** (staging and production keys are different)
4. Return `200` quickly after successful verification and processing

If signature verification fails, return `400` or `401`. Afriex retries failed deliveries up to 12 times with exponential backoff (starting at 30 seconds).

<Card title="Webhook Documentation" icon="bell" href="/api-reference/endpoint/webhooks/introduction">
  Learn more about webhook setup, security, and event types.
</Card>

## Rate Limiting

Be mindful of rate limits when making API calls:

* Implement exponential backoff for retries
* Cache exchange rates and other static data
* Use webhooks instead of polling for status updates

## Testing Your Integration

<Steps>
  <Step title="Test in Staging">
    Use the staging environment to test all API calls without affecting live data.
  </Step>

  <Step title="Verify Webhooks">
    Test webhook handling with the staging environment events.
  </Step>

  <Step title="Go Live">
    Switch to production base URL and API keys when ready.
  </Step>
</Steps>

### Testing transaction outcomes in sandbox

In the sandbox/staging environment, transactions reach a final status automatically, usually within 1-2 minutes of creation, so you can build and test your integration end to end. The matching `TRANSACTION.UPDATED` webhook is sent when the transaction settles.

You can choose the outcome through the transaction's `meta.reference`:

* If `meta.reference` contains the word `fail` (case-insensitive), the transaction settles as **FAILED**. For withdrawals, the amount is returned to your wallet.
* Otherwise, the transaction settles as **SUCCESS**.

Use this to test both your success and failure handling. This applies to the sandbox/staging environment only.

### Simulating OTP-required deposits

Some deposits (for example, certain mobile-money payments) require the customer to confirm a one-time password (OTP) before they complete. In sandbox you can trigger this flow deterministically through the deposit's `meta.reference`:

* Include `SIMULATE_OTP` in `meta.reference` to make the deposit return the `CUSTOMER_ACTION_REQUIRED` status, meaning an OTP is needed.
* Include `SIMULATE_NO_OTP` to make the deposit complete without an OTP step.

When OTP is required, complete the deposit by calling [Authorize Transaction](/api-reference/endpoint/transactions/authorize) with the sandbox OTP `123456`:

```bash theme={null}
curl -X POST \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type":"OTP","otp":"123456"}' \
  https://sandbox.api.afriex.com/api/v1/transaction/TRANSACTION_ID/authorize
```

Any OTP other than `123456` is rejected, so you can test the wrong-OTP path. Once authorized, the deposit settles automatically like any other sandbox transaction (a `meta.reference` that also contains `fail` settles as **FAILED**, otherwise **SUCCESS**). These values apply to the sandbox/staging environment only; production ignores them.

## Support

Need help with your integration? Contact us at [support@afriex.com](mailto:support@afriex.com).
