Skip to content

Keys & access

Every 401, 403 and 406 the API returns, and the CORS error that is not one.

Authentication failures are the easiest family to diagnose, because the API answers a different code for each cause. The trap is that the codes are not the ones most people expect: validation is 422, a wrong key class is 406, and there is no 409 at all.

401 Invalid or revoked API credentials

The key was not recognised as a usable credential. Four causes, and the check runs against the database on every request, so a key revoked a second ago stops working now.

CauseHow to confirm
Typo, or a truncated pasteThe value is a prefix, a dot, and 96 characters
The key was deleted or deactivatedIt disappears from Settings, Developer, API Keys
The key is past expires_atThe expiry is shown next to the key
The key belongs to the other environmentpk_test. cannot act live, pk. cannot act in sandbox

Send it in Authorization with no Bearer prefix. The API reads the header value as the key itself, and a Bearer in front of it makes the lookup fail like any other typo.

The header the API expects
curl https://api.wajub.com/payments \
  -H "Authorization: sk_test.kZ3qP8mWvL2xR7tB5nY4hC6dF9jS1aG0eU3i…"

403 Security Alert: You are trying to use a Private Key from a browser

An sk. key arrived with an Origin or a Referer header, which only a browser sends. Wajub refuses the call and emails the key's owner, throttled to one alert per key per hour.

Two things follow. Move the call to your server, and treat the key as leaked: it was in a bundle someone downloaded, so create a replacement, deploy it, then delete the old one. Security has the sequence that avoids an outage.

403 IP address not allowed for this API key

The key has an allow-list and your caller is not on it. It applies to every key type, including public ones, and accepts exact IPv4 or IPv6 addresses as well as CIDR ranges in either family. An empty list means no restriction.

The usual cause is a deploy that changed your egress address, or a job running from a machine nobody added. Check the caller's real IP against the list on the key.

403 This API key does not have permission to read payment

A restricted key without the scope the route needs. Scopes are {resource}.{read|write}, and the verb decides which half applies: GET, HEAD and OPTIONS need read, everything else needs write.

There is a second shape of this error worth knowing.

A restricted key on a route that maps to no resource
{
"code": 403,
"status": "Forbidden",
"message": "This API key cannot access this resource. Restricted keys can only access specific resources."
}

That one is not about a missing scope. A restricted key is an allow-list, not a filter: if a route does not map to one of the scoped resources, no restricted key reaches it, whatever you granted. Four paths are exempt because they carry nothing sensitive: /channels, /countries, /currencies and the API root.

Two resources are read only, so a restricted key can never write them: balance and account. In practice that means a restricted key cannot create a Sync connection. Use a private key on your server for that.

406 Private Key Required

The route needs a private key and you sent a public one. It covers /balance, /transfers, /refunds, /disputes and /beneficiaries.

/payments and /accounts are not in that list, which surprises people in both directions: a public key can create a payment, and a public key can create a Sync connection. That a call is accepted from a public key does not mean it belongs in a browser.

"I get a CORS error"

Almost certainly not. Every response the API returns, errors included, carries Access-Control-Allow-Origin: *, and preflight OPTIONS answers 204 with the allowed methods and headers. Wajub does not produce CORS failures.

What you are seeing is one of these, reported by the browser as a failed request.

What really happenedTell
The 403 browser block aboveA private key in client code
The request never leftA bad URL, an offline tab, a blocked extension
A custom header you addedOnly the listed headers are allowed on preflight

That last row is the one worth checking in the network tab. Preflight allows Content-Type, Authorization, X-Requested-With, Accept, Origin, Idempotency-Key and X-Link-View-Token. A header outside that set fails preflight, and the browser reports a CORS error for a request that was never rejected on its merits.

404 on an id I am certain exists

Sandbox and live are different databases, chosen by your key's prefix before a single query runs. An id from one is genuinely absent from the other, so the answer is 404 rather than a permission error. Sandbox ids carry test_ after the prefix, which tells you at a glance which one you are holding.

The same applies across teams. Ids do not resolve outside the team that owns them, and the response is 404 on purpose: a 403 would confirm the object exists.

422 with errors.idempotency_key

You reused an Idempotency-Key with a different payload. Either you changed an amount without changing the key, or you reused a key you meant to rotate. It is not transient, and retrying produces the same answer.

The key format is also validated before anything else: A-Z, a-z, 0-9 and . _ : -, from 1 to 128 characters. An order number written #4172, or a UUID with braces, is refused on the format alone.

What did you think of this content?