Getting started

Authentication

Every call to the NATIO API is authenticated with a secret API key sent as a bearer token. The key alone determines the merchant, the project and whether you are in test or live mode.

Bearer API keys

Send the key in the Authorization header. There is no other authentication scheme, no signing of API requests, and no merchant or project parameter — those are resolved from the key.

Authenticated request
curl https://api.natio.me/v1/me \
  -H "Authorization: Bearer natio_sk_test_..."

GET /v1/me is the cheapest way to confirm that a key works and to see exactly which merchant, project and mode it resolves to.

200 OK
{
  "merchant": { "id": "mer_...", "name": "Demo Merchant Ltd" },
  "project": { "id": "prj_...", "name": "Default" },
  "mode": "test",
  "api_key": { "id": "key_...", "prefix": "natio_sk_test_AP8j", "name": "Sandbox key" }
}

Test keys and live keys

A key is bound to one mode for its whole life. There is no header or parameter that switches mode; you change mode by using a different key. Test and live data are fully separated — a test key can never read a live payment and the reverse.

natio_sk_test_…natio_sk_live_…
Providers reachedNATIO demo providers onlyThe licensed providers connected to your account
Money movementSimulated end to end, no funds moveReal: funds move between the merchant and licensed providers
test_scenarioAccepted on payments and refundsRejected with 400 test_scenario_not_allowed
AvailabilityImmediately after registrationIssued only once merchant KYB review is approved
Hosted pagesSandbox simulation pagesProvider-hosted pages
Live keys require KYB approval
Creating a live key before the merchant record is approved is refused by the API. Build and certify the whole integration on test keys first — the request and response contracts are identical.

Storage, rotation and revocation

The secret is generated once and shown once. NATIO stores only a SHA-256 hash of it plus a short display prefix (for example natio_sk_test_AP8j), so a key can be identified in the dashboard and in audit logs without ever being readable again.

Where it belongs
A secrets manager or an environment variable on the server. Never in a browser, a mobile app, a repository or a log line.
Creating a key
Dashboard → API keys. Each key has a name and a mode; keys are scoped to one project.
Rotation
Create the new key, deploy it, confirm traffic has moved by watching the last-used timestamp on the old key, then revoke the old key. Both work at once, so there is no downtime window.
Revocation
Revoking is immediate and irreversible. The next request with that key returns 401 invalid_api_key.
Compromise
Revoke first, investigate second. Every key creation and revocation is written to the audit trail with the acting user.
Least privilege
Use one key per deployed service rather than one shared key, so a rotation or a revocation never takes the whole estate down.

IP allow-list per project

Each project can carry an allow-list of source addresses. When the list is non-empty, any API request from an address outside it is refused before the route runs, regardless of whether the key is valid. An empty list means no IP restriction.

Entries are exact IPv4 or IPv6 addresses, or CIDR ranges such as 203.0.113.0/24 and 2001:db8::/32. Configure the list under project settings in the dashboard.

Check your egress addresses first
The address NATIO sees is the public address your servers egress from, which behind a NAT gateway or a proxy is not the address of the machine making the call. Add the range before you enable the list, or you will lock out your own integration.

Rate limits

API traffic is rate limited per key over a rolling one-minute window. Responses carry the state of that window, so a client should read the headers rather than hardcode a number.

Response headers
HTTP/1.1 201 Created
content-type: application/json; charset=utf-8
x-request-id: req_sT5KhaixXe6tNK0q
x-ratelimit-limit: 300
x-ratelimit-remaining: 293
x-ratelimit-reset: 36
HeaderMeaning
x-ratelimit-limitRequests allowed in the current window.
x-ratelimit-remainingRequests still available in the current window.
x-ratelimit-resetSeconds until the window resets.

Exceeding the window returns 429 with the standard error envelope. Back off for at least x-ratelimit-reset seconds, then retry. Because mutating requests should carry an Idempotency-Key, retrying after a 429 is safe and cannot duplicate a payment.

429 Too Many Requests
{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limited",
    "message": "Too many requests"
  },
  "request_id": "req_Yp3BZPrpF6EwG8WS"
}
Backing off on 429
async function callNatio(path, init, attempt = 0) {
  const res = await fetch(`https://api.natio.me${path}`, init);
  if (res.status === 429 && attempt < 3) {
    const reset = Number(res.headers.get("x-ratelimit-reset") ?? "1");
    await new Promise((r) => setTimeout(r, Math.max(reset, 1) * 1000));
    return callNatio(path, init, attempt + 1);
  }
  return res;
}

Request ids

Every response carries an x-request-id header, and every error body repeats it as a top-level request_id field. The same id appears in the NATIO server logs and in the audit trail for the action, which makes it the fastest way to have a specific call investigated.

  • Log x-request-id alongside your own correlation id for every NATIO call, successful or not.
  • When you open a support request, quote the request_id, the UTC timestamp and the object id (pay_…, rf_…, po_…). That triple identifies the call exactly; a description of the symptom does not.
  • Never quote the API key itself, or any part of it beyond the visible prefix.

401 and 403 responses

Authentication and permission failures use the same envelope as every other error: an error object with type, code and message, plus the request_id. Branch on error.code, never on the message text.

401 — authentication_error

No key was presented, or the header was not a bearer token:

401 Unauthorized
{
  "error": {
    "type": "authentication_error",
    "code": "unauthorized",
    "message": "Provide your API key as `Authorization: Bearer natio_sk_test_...`"
  },
  "request_id": "req_pqumnNFCN09cAqT2"
}

The key was presented but is unknown or has been revoked:

401 Unauthorized
{
  "error": {
    "type": "authentication_error",
    "code": "invalid_api_key",
    "message": "Invalid or revoked API key"
  },
  "request_id": "req_0uwP4ioIZ8q8dgBW"
}

403 — permission_error

The key is valid, but the call is not allowed from this source address:

403 Forbidden
{
  "error": {
    "type": "permission_error",
    "code": "ip_not_allowed",
    "message": "Request IP is not in the project allow-list"
  },
  "request_id": "req_M2XNd2ZI3qslCEU3v"
}

The other 403 code is forbidden, returned when a valid key is used for something it is not entitled to — for example a test-only endpoint such as GET /v1/test/scenarios called with a live key.

StatusCodeWhat to do
401unauthorizedFix the header. It must be exactly Authorization: Bearer <key>.
401invalid_api_keyThe key is wrong, revoked, or from the other environment. Do not retry; it will not recover.
403ip_not_allowedAdd the calling egress range to the project allow-list.
403forbiddenThe endpoint is not available for this key — usually a test-only endpoint called with a live key.

The full error catalogue, including the failure codes carried by failed payments, is in Errors and failure codes.