Get started
API conventions
Responses are wrapped
A successful call returns its result in data, next to three fields that describe the call:
{
"success": true,
"data": { "id": "1c9f4a2e-…", "status": "OPEN" },
"timestamp": "2026-07-16T14:02:10.000Z",
"path": "/v1/payment-links"
}Read data, and ignore fields you don't recognise: we add fields to /v1 without notice.
Errors are not wrapped; they have their own shape.
Lists and pagination
List endpoints take page (from 1) and limit (default 25, at most 100), and return the page in
data.data with the totals beside it:
curl "https://sandbox-api.baxpay.com/v1/payment-links?status=OPEN&page=1&limit=25" \
-H "Authorization: Bearer $BAXPAY_API_KEY"// Walk every page of open payment links.
let page = 1;
const all = [];
for (;;) {
const res = await fetch(`https://sandbox-api.baxpay.com/v1/payment-links?status=OPEN&page=${page}&limit=100`, {
headers: { Authorization: `Bearer ${process.env.BAXPAY_API_KEY}` },
});
const { data } = await res.json();
all.push(...data.data);
if (page * data.limit >= data.total) break;
page += 1;
}
console.log(JSON.stringify({ count: all.length }));{
"success": true,
"data": {
"data": [{ "id": "1c9f4a2e-…", "status": "OPEN" }],
"total": 1,
"page": 1,
"limit": 25
}
}A limit above 100 is treated as 100.
Idempotency keys
Every call that creates something or moves money requires an Idempotency-Key header: any
unique string of 8 to 255 characters (a UUID is ideal). Without it the call is refused with 400.
If a request times out, you don't know whether it went through. Send it again with the same key: if the first attempt succeeded, you get its original response back and nothing is created twice.
KEY=$(uuidgen)
BODY='{ "description": "Order #1042", "amount": 25, "businessWide": true }'
first=$(curl -s -X POST https://sandbox-api.baxpay.com/v1/payment-links \
-H "Authorization: Bearer $BAXPAY_API_KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: $KEY" -d "$BODY" | jq -r .data.id)
again=$(curl -s -X POST https://sandbox-api.baxpay.com/v1/payment-links \
-H "Authorization: Bearer $BAXPAY_API_KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: $KEY" -d "$BODY" | jq -r .data.id)
echo "first=$first again=$again" # the same link: the retry created nothing
[ "$first" = "$again" ]The rules:
| Situation | Result |
|---|---|
| Same key, same body, first call finished | The original response, replayed |
| Same key, same body, first call still running | 409: wait a moment and retry with the same key |
| Same key, different body | 400: a bug in your code, never retried for you |
| No key on a call that requires one | 400 |
Keys are remembered for 24 hours, per merchant and per endpoint.
KEY=$(uuidgen)
curl -s -X POST https://sandbox-api.baxpay.com/v1/payment-links \
-H "Authorization: Bearer $BAXPAY_API_KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: $KEY" -d '{ "description": "Order #1043", "amount": 25, "businessWide": true }' > /dev/null
curl -X POST https://sandbox-api.baxpay.com/v1/payment-links \
-H "Authorization: Bearer $BAXPAY_API_KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: $KEY" -d '{ "description": "Order #1043", "amount": 30, "businessWide": true }'{
"statusCode": 400,
"message": "This Idempotency-Key was already used with a different request body.",
"error": "Bad Request"
}Rate limits
Limits are counted per IP address over a rolling minute:
| Scope | Limit |
|---|---|
| Any endpoint (default) | 200 requests / minute |
POST /payment-links, POST /einvoices, POST /clients, POST /products | 60 / minute |
POST /einvoices/{id}/send | 30 / minute |
Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset.
Over the limit you get 429: wait for the reset, then retry with the same idempotency key.
Amounts
- Payment links, e-invoices, clients and products use decimal euros:
47.30. - In-store sessions (
/integration/…) use integer minor units:4730, in fields ending inMinor.
Currencies are ISO 4217 codes in capitals (EUR).
Ids, times and request ids
- Ids are UUIDs (
1c9f4a2e-6b1d-4c1e-9a52-0d9f6f2c1b7a). Payment-link tokens (plk_…) are the only prefixed ids, and they belong in URLs you give to payers, never in API paths that need an id. - Times are ISO 8601 in UTC (
2026-07-16T14:03:21.512Z). - Every call made with an API key gets a request id: the
X-Request-Idresponse header, andrequestIdin an error. Send your ownX-Request-ID(8 to 128 letters, digits,.,_,:or-) and it is used instead. Quote it when you contact support.
Request log
Every call made with an API key is logged for 30 days, in its mode: method, path (without the query string), status, duration, request id, key and IP. Never a body, a header or a key. The dashboard shows the same log under Developers → Requests.
/requeststhis mode's requests, newest first; filter by status, method, path, apiKeyId/requests/{id}one requeststatus takes a class (2xx, 4xx, 5xx) and path any part of the path:
curl "https://sandbox-api.baxpay.com/v1/requests?status=2xx&path=/payment-links&limit=5" \
-H "Authorization: Bearer $BAXPAY_API_KEY"{
"success": true,
"data": {
"data": [
{
"id": "6e1d0f3a-…",
"method": "POST",
"path": "/v1/payment-links",
"status": 201,
"durationMs": 38,
"requestId": "req_9b1c…",
"ip": "203.0.113.9",
"livemode": false,
"apiKey": { "id": "0c5d…", "label": "Backend server", "prefix": "bx_test_", "last4": "a9f2" },
"createdAt": "2026-07-16T14:03:21.512Z"
}
],
"total": 1,
"page": 1,
"limit": 5
}
}curl https://sandbox-api.baxpay.com/v1/requests/6e1d0f3a-… \
-H "Authorization: Bearer $BAXPAY_API_KEY"{ "success": true, "data": { "id": "6e1d0f3a-…", "method": "POST", "status": 201, "requestId": "req_9b1c…" } }