Customer export
List every buyer page by page — the cabinet's customer list with account references and totals — with a cursor, up to 100 per page, and updatedSince for catch-up sync.
GET /api/v1/customers lists your buyers page by page: the rows of the cabinet's customer list (its Clients section) — the same buyers in the same order (newest first purchase on top) and the same totals — plus the account references found on each buyer's orders and the moment the buyer last changed. Use it to fill your own database once and to keep it in sync. The partner's internal figures (commission, payout share, hold) are not in it, as in the webhooks.
The server API is for a partner whose application knows its own user: a payment is created from your server, with no button or panel on a page, and the user's state (subscriptions and purchases) is read by your own account reference. The key is the same as for the access check (API key tab in the cabinet), sent as Authorization: Bearer; the partner is identified by the key, there is no partner parameter. Rate limits are the same as for the access check: per your server's address and per key. Responses are JSON only and never cached. You can ask only about your own products and buyers: a foreign product or buyer and a non-existent one answer alike.
Request
GET https://app.norelio.eu/api/v1/customers?limit={1..100}&cursor={nextCursor}&updatedSince={date-time}
Authorization: Bearer {api key}Every parameter is optional: limit — rows per page, 1 to 100 (default 100); cursor — the nextCursor of the previous page; updatedSince — an ISO 8601 date-time with a time zone (2026-03-14T00:00:00Z): only buyers whose own record or any of whose orders changed at or after that moment. A malformed value is 400 invalid_request.
curl -s "https://app.norelio.eu/api/v1/customers?limit=2" \
-H "Authorization: Bearer $NORELIO_API_KEY"
curl -s "https://app.norelio.eu/api/v1/customers?limit=2&cursor=eyJ0IjoiMjAyNi0wMi0wMVQxODowMzoxMS4wMDBaIiwiayI6ImNtN2s5In0" \
-H "Authorization: Bearer $NORELIO_API_KEY"Response
200 OK. Each row of customers:
| Field | Description |
|---|---|
email | The buyer's email, lower-cased as in the cabinet. |
clientReferences | Your account references found on the buyer's orders, newest first, up to 50; empty when none was passed. |
firstPurchaseAt | The buyer's first purchase (ISO 8601) — the order of the list. |
lastPaymentAt | The last confirmed payment (ISO 8601); null when there is none. |
ordersCount | The number of the buyer's orders on which money moved — paid, refunded and disputed — the same count as in the cabinet's customer list. |
paidAmount | The sum of those orders, in the smallest unit of currency, without VAT — the purchases total of the cabinet's customer list. |
refundedAmount | How much of it went back to the buyer, in the same units. |
currency | The currency of paidAmount and refundedAmount, three-letter code. |
updatedAt | When the buyer's record or any of their orders last changed (ISO 8601) — what updatedSince compares with. |
{
"customers": [
{
"email": "buyer@example.com",
"clientReferences": [
"acc_10294"
],
"firstPurchaseAt": "2026-03-14T09:26:53.000Z",
"lastPaymentAt": "2026-03-14T09:26:53.000Z",
"ordersCount": 1,
"paidAmount": 1990,
"refundedAmount": 0,
"currency": "EUR",
"updatedAt": "2026-03-14T09:26:54.000Z"
},
{
"email": "second@example.com",
"clientReferences": [],
"firstPurchaseAt": "2026-02-01T18:03:11.000Z",
"lastPaymentAt": "2026-03-01T18:03:12.000Z",
"ordersCount": 2,
"paidAmount": 1980,
"refundedAmount": 990,
"currency": "EUR",
"updatedAt": "2026-03-02T10:15:00.000Z"
}
],
"nextCursor": "eyJ0IjoiMjAyNi0wMi0wMVQxODowMzoxMS4wMDBaIiwiayI6ImNtN2s5In0"
}Paging
The response is an object with customers (the rows) and nextCursor: an opaque string to pass back as cursor for the next page, null on the last one. The order is stable — by first purchase, then by an internal id — so no buyer is repeated or skipped between pages, even when several bought at the same moment.
Keeping a copy in sync
To keep a copy in sync, remember the moment you START each run and pass it as updatedSince to the next one: a buyer who changes while you are reading the pages is then picked up by the next run. Between runs, the customer.state_changed webhook tells you about each change as it happens.
curl -s "https://app.norelio.eu/api/v1/customers?updatedSince=2026-03-14T00:00:00Z" \
-H "Authorization: Bearer $NORELIO_API_KEY"One buyer's subscriptions and purchases are in Customer state; changes as they happen, in Customer state notification.
Test mode
With the test key the export lists the buyers of your test orders, by the email of each paid, refunded or disputed test order, in the same shape and with the same paging; with the live key, real buyers only. Test buyers never appear in the cabinet's customer list. See Test mode.
Errors
An error from any API endpoint is JSON of one shape: an error object with code (one of the codes below) and message (a short English sentence). A 429 or 503 response carries a Retry-After header. The access check keeps its own, older error shape.
| Status | Code | When |
|---|---|---|
429 | rate_limited | too many requests in a row, from your address or with your key |
503 | rate_limiter_busy | the request could not be counted against the rate limits, so nothing was done; send the same request again after the number of seconds in the Retry-After header |
401 | invalid_api_key | the key is missing or does not match |
400 | invalid_request | the body or the query is malformed; message says what exactly |
403 | direct_checkout_not_enabled | creating payments from your server is not yet enabled for your account: the platform first reviews your purchase terms (the same review as for direct checkout) |
404 | product_not_found | no product with this id belongs to your account, or it cannot be bought right now |
502 | checkout_failed | the payment provider could not start the payment; retry later |
404 | not_found | no such endpoint |
405 | method_not_allowed | the endpoint does not accept that method |
403 | test_key_required | the endpoint works only with the test API key (it starts with nrl_test_), and the request came with the live key |
404 | test_order_not_found | no test order with this id belongs to your account, or it has already been deleted: test orders live for 30 days |
409 | test_action_not_allowed | the action is not possible in the test order's current state, such as a refund of an order not yet paid or a renewal of a canceled subscription; message names the reason |
409 | insufficient_balance | the balance is lower than the amount to debit; nothing was debited, and the response carries the current balance in the balance field next to error |
422 | idempotency_conflict | a request with this idempotency key was already processed with a different body; use a new key for a new operation. A balance debit or grant answers 422 (idempotencyKey in the body), a payment created with the Idempotency-Key header answers 409 |
409 | idempotency_in_progress | a payment request with this Idempotency-Key is still being processed; send the same request again after the number of seconds in Retry-After and you get its response |
404 | subscription_not_found | no subscription with this id belongs to your account in this mode: a live key sees only live subscriptions, a test key only test ones |
{
"error": {
"code": "direct_checkout_not_enabled",
"message": "Creating payments from your server is enabled after the platform has reviewed your purchase terms. Ask support from the cabinet's Messages section."
}
}