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

# API Conventions

> Conventions used across the entire Cartevo API — base URL, authentication, request/response shape, money, dates, pagination, errors, idempotency, and rate limits.

This page describes conventions that apply to **every** endpoint. Endpoint-specific behavior is documented on each endpoint's reference page.

***

## Base URL

```text theme={null}
https://api.cartevo.co/api/v1
```

All paths shown in the reference (e.g. `POST /cards`, `GET /wallets`) are relative to this base URL. The full URL for `POST /cards` is therefore `https://api.cartevo.co/api/v1/cards`.

The same base URL serves both **preproduction** and **production**. Which environment you reach is determined by the credentials (`client_id` / `client_key`) you use to obtain your access token. See [Environments](/getting-started/environment).

***

## Authentication

Every endpoint except `POST /auth/token` requires a Bearer access token in the `Authorization` header:

```http theme={null}
Authorization: Bearer <access_token>
Content-Type: application/json
```

Tokens are obtained via [`POST /auth/token`](/api-reference/endpoint/post-access-token) and **expire after 1 hour**. There is no refresh token endpoint — when a token expires, call `POST /auth/token` again.

Tokens are scoped to one company in one mode (preproduction or production). Treat them as secrets: never expose them to browsers, mobile apps, or anything client-side.

***

## Request encoding

| Endpoint type                           | Encoding                   |
| --------------------------------------- | -------------------------- |
| All endpoints by default                | `application/json` (UTF-8) |
| `POST /customers` (file upload variant) | `multipart/form-data`      |

When sending JSON, send a `Content-Type: application/json` header.

***

## Response envelope

Successful responses are wrapped in a standard envelope:

```json theme={null}
{
  "success": true,
  "statusCode": 200,
  "message": "Request successful",
  "data": { /* endpoint-specific payload */ }
}
```

Paginated responses extend this envelope with a `meta` object:

```json theme={null}
{
  "success": true,
  "statusCode": 200,
  "message": "10 items retrieved successfully",
  "data": [ /* array of items */ ],
  "meta": {
    "page": 1,
    "limit": 10,
    "total": 137,
    "totalPages": 14
  }
}
```

**Field reference:**

| Field        | Type    | Description                                                         |
| ------------ | ------- | ------------------------------------------------------------------- |
| `success`    | boolean | `true` for any 2xx response                                         |
| `statusCode` | integer | The HTTP status code, repeated in the body for client convenience   |
| `message`    | string  | Human-readable status message. May be localized (English or French) |
| `data`       | any     | Endpoint-specific payload. `null` for endpoints that return no body |
| `meta`       | object  | Present only on paginated responses                                 |

A small number of endpoints — notably the auth flows (`/auth/login`, `/auth/logout`, `/auth/verify-otp`, `/auth/forgot-password`, `/auth/reset-password`) and `POST /company/register/*` — return raw responses **without** this envelope. Each affected endpoint flags this on its own reference page.

***

## Field naming convention

All request bodies, query parameters, and response payloads use **`snake_case`** (e.g. `customer_id`, `phone_number`, `created_at`).

A small number of legacy webhook event payloads still emit a few `camelCase` keys (e.g. `cardId`, `transactionId`); each affected webhook event flags this in its field table. New endpoints and webhook events will always use `snake_case`.

***

## Money

* Amounts are sent and returned as **JSON numbers in the major currency unit** (e.g. `100.50` USD = one hundred dollars and fifty cents). Cartevo does not use minor units (cents).
* Currencies are identified by ISO 4217 codes: `USD`, `XAF`, `XOF`, `CDF`, `GNF`.
* Wherever an `amount` field appears, an accompanying `currency` field describes the unit.
* For collections and payouts, the `currency` you provide must match the currency of the wallet associated with that country.

***

## Dates and times

All timestamps are returned as **ISO 8601 strings in UTC**, including milliseconds:

```
2025-01-08T12:00:00.000Z
```

Date-only fields (such as `date_of_birth`) use `YYYY-MM-DD`.

Date-range query parameters (`from_date`, `to_date`) accept either format; if you omit the time, midnight UTC is assumed.

***

## Phone numbers

Phone numbers are sent and returned in **E.164 format with a leading `+`**:

```
+237600000000
```

For payment collection and payout, you supply the country code separately as `country_phone_code` (digits only, e.g. `237`) and the local number as `phone_number`. The reference pages for those endpoints show worked examples per country.

***

## Pagination

List endpoints accept two query parameters:

| Parameter | Type    | Default | Max | Description              |
| --------- | ------- | ------- | --- | ------------------------ |
| `page`    | integer | `1`     | —   | 1-indexed page number    |
| `limit`   | integer | `10`    | 100 | Number of items per page |

The response always includes a `meta` block with `page`, `limit`, `total`, and `totalPages`.

> **Inconsistency to be aware of:** `GET /wallet/transactions` (the unfiltered listing) currently uses `limit` / `offset` instead of `page` / `limit`. This is being aligned with the rest of the API.

***

## Idempotency

Mutating endpoints accept an idempotency key on a per-endpoint basis:

* **Payments** (`POST /payment/collect`, `POST /payment/payout`): supply a unique `external_id` in the request body. Replays with the same `external_id` are rejected with `400` (after the first call succeeds, subsequent calls return the original transaction in the response).
* **Card actions** (`POST /cards`, `POST /cards/{id}/fund`, `POST /cards/{id}/withdraw`): no idempotency key is supported today. Implement your own deduplication keyed on the response `id`.

Always store the `external_id` (or the resulting `transaction_id`) in your own database before making the call, so that if the network drops you can safely retry with the same key.

***

## Webhooks vs. polling

Most asynchronous outcomes (card funding settlement, payment collection result, cross-border charges, fee debits) are delivered to your server as **webhooks**. See the [Webhooks reference](/api-reference/endpoint/webhook) for the full event catalogue, payload shapes, retry policy, and security guidance.

If you cannot receive webhooks, you can poll status endpoints (e.g. `GET /payment/transactions/{transactionId}/status`) instead. Recommended cadence: poll at most once every 5 seconds, with exponential backoff after the first 30 seconds.

***

## Errors

Error responses use a flat shape (they are **not** wrapped in the success envelope):

```json theme={null}
{
  "message": "Insufficient wallet balance",
  "error": "Bad Request",
  "statusCode": 400
}
```

Some endpoints return additional fields (`error_code`, `details`) — see the per-endpoint reference for specifics.

### HTTP status codes

| Code  | Meaning                                                                                          |
| ----- | ------------------------------------------------------------------------------------------------ |
| `200` | Request succeeded                                                                                |
| `201` | Resource created                                                                                 |
| `400` | Validation error or business-rule violation (e.g. insufficient balance, duplicate `external_id`) |
| `401` | Missing, invalid, or expired Bearer token; or invalid `client_id`/`client_key`                   |
| `403` | Authenticated but not authorized for this resource (e.g. wrong company)                          |
| `404` | Resource not found                                                                               |
| `409` | Conflict (e.g. customer already exists with that email)                                          |
| `422` | Upstream provider rejected the request (KYC failure, operator unavailable)                       |
| `429` | Rate limit exceeded — see [Rate limits](#rate-limits)                                            |
| `500` | Server error — safe to retry with exponential backoff                                            |

### Recommended client behavior

| Status | Retry? | Action                                                               |
| ------ | ------ | -------------------------------------------------------------------- |
| `2xx`  | n/a    | Continue                                                             |
| `400`  | No     | Fix the request — do not retry as-is                                 |
| `401`  | Once   | Refresh the access token, then retry                                 |
| `403`  | No     | Verify the resource belongs to your company                          |
| `404`  | No     | Verify the ID                                                        |
| `409`  | No     | The resource already exists — fetch it instead                       |
| `422`  | Maybe  | Inspect `message`; some upstream errors are transient                |
| `429`  | Yes    | Wait, then retry with exponential backoff (start at 1s)              |
| `5xx`  | Yes    | Retry with exponential backoff (start at 1s, max 60s, cap 5 retries) |

***

## Rate limits

By default, Cartevo applies a per-IP rate limit of **10 requests per 60 seconds** across the whole API surface. Custom limits can be agreed on a per-account basis — contact support if your integration needs a higher allowance.

When you exceed the limit, the API returns `429 Too Many Requests`. Back off and retry; the limit window is sliding, so waiting \~60 seconds restores full quota.

***

## IDs

| ID type                                                    | Format                 | Where it comes from                      |
| ---------------------------------------------------------- | ---------------------- | ---------------------------------------- |
| Cartevo internal IDs (customer, card, wallet, transaction) | UUID v4                | Returned by Cartevo on resource creation |
| `external_id` (payment idempotency key)                    | Any string ≤ 255 chars | Supplied by you                          |
| Provider reference (upstream operator)                     | Provider-defined       | Returned in webhook payloads             |

Always use the Cartevo internal ID when calling Cartevo APIs. Provider references are informational only.

***

## Modes (preproduction vs. production)

Each set of API credentials is bound to one mode:

* **Preproduction** — limited wallet balances (max 50,000 XAF / \~80 USD), no real money movement, suitable for end-to-end testing.
* **Production** — real money, real KYC, no balance ceiling beyond your account configuration.

You cannot mix modes within a single token. To call production, obtain a token with production credentials.

***

## See also

* [Environments](/getting-started/environment) — preproduction vs. production limits
* [Authentication](/getting-started/authentication) — how to obtain and use a Bearer token
* [Webhooks](/api-reference/endpoint/webhook) — outbound event catalogue and signature verification
* [Glossary](/getting-started/glossary) — definitions of wallet, customer, transaction, etc.
