## Overview

Our API design follows a few simple rules that provide a consistent interface across the entire API surface:

* **POST only:** Every endpoint is accessed via a `POST` request.
* **JSON input:** All request bodies must be a JSON object.
* **JSON output:** All responses are returned as JSON values.
* **Predictable status codes:** We use a minimal set of HTTP status codes to indicate the outcome of your request.

To learn more, read the [WebFunction](https://webfunction.org) specification.

## Request structure

To call a Reservepay endpoint, send a `POST` request with the `Accept: application/json`, `Content-Type: application/json`, and `Api-Version: 2025-04-01` headers. The request body should be a JSON object containing the arguments for that endpoint.

### Example

```bash
curl -X POST https://api.reservepay.com/merchants/get-balance \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Api-Version: 2025-04-01" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{}'
```

## Response structure

Responses are always JSON values. Depending on the endpoint, this could be an object, an array, a string, a number, or a boolean.

## Pagination

Some endpoints return a paginated response. These endpoints follow a standardized structure to make it easy to navigate through large sets of results.

When an endpoint is paginated, the response is a JSON object with three keys:

* `page`: A JSON array containing the items for the current page.
* `next`: A JSON object representing the request for the next page, or `null` if there are no more results.
* `previous`: A JSON object representing the request for the previous page, or `null` if you are on the first page.

### Navigating pages

To navigate, you should not attempt to construct or modify the `next` and `previous` objects. Instead, treat them as opaque tokens. To get the next or previous page, simply send a `POST` request to the same endpoint using the exact JSON object provided in the `next` or `previous` field as the request body.

For more details, see the [WebFunction Pagination](https://webfunction.org/pagination) specification.

## Idempotency and timeouts

Reservepay uses `external_id` instead of an idempotency header. Pass your own reference, such as an order or invoice number, when you create a payment or payout:

* **Payments:** `merchants/initiate-payment-flow`. Must be unique across your payments.
* **Payouts:** `merchants/send-payout` or `merchants/send-payout-to-contact`. Must be unique across your payouts.

Payments and payouts are checked separately, so a payment and a payout can share the same reference. `merchants/find-payment` and `merchants/find-payout` also accept your `external_id`, so you can look records up without storing the Reservepay ID.

If a request times out, you can't tell whether Reservepay received it. Resolve it this way:

| Operation | After a timeout |
| :--- | :--- |
| Create a payment or payout | Send the same request again with the same `external_id`. If the first request went through, you get the original record back instead of a duplicate. |
| Capture or reverse | Call `merchants/find-payment` and read the status. Don't rely on a second attempt: `NOT_CAPTURABLE` or `NOT_REVERSIBLE` only means the action isn't available now, which is also true when the authorization has expired. |
| Refund | Read every page of `merchants/list-refunds-by-payment` and compare it with the refunds you meant to create. Refunds take no client reference, so a retry can create a second refund. |

A `NOT_FOUND` right after a timeout doesn't prove the request failed, because it may still be processing. Keep the same `external_id` until you know the outcome. Use a new `external_id` for every new attempt, including when a customer retries after a `FAILED` or `EXPIRED` payment, for example `order_12345_2`.

## Status codes

Almost all business errors return HTTP `400` with an error triple. HTTP `500` is `UNHANDLED_ERROR`. Branch on the triple code, not the HTTP status.

| Status Code | Meaning |
| :--- | :--- |
| `200 OK` | The request was successful. |
| `400 Bad Request` | A handled API error. The body is an error triple; read the code. |
| `500 Internal Server Error` | Unexpected server error. Check the operation's outcome before retrying a write. |

For the triple format, retry rules, and support channels, see [Error handling](/guides/error-handling).

## Namespaces

The API is split into three public namespaces:

* **`merchants/`**: For server-side operations using your secret API key. [See Merchant API reference](/api/merchants).
* **`sdk/`**: For client-side operations (web or mobile apps) using public identifiers. [See SDK API reference](/api/sdk).
* **`terminals/`**: For managing payment terminals. [See Terminal API reference](/api/terminals).

## Packages and client generation

At the root of each namespace, we provide a [WebFunction Package](https://webfunction.org/package) definitions that include all the endpoints available in that namespace. You can use them to:

1. Create type-safe API clients for your language.
2. Set up a client dynamically based on your environment.

This makes integration faster and keeps your code in sync with our API.
