Errors
Every error is RFC 9457 problem details with a stable code you can branch on.
On this page
When a request fails, the response has a 4xx or 5xx status and an
application/problem+json body:
{
"type": "https://docs.nowgate.io/errors/amount_too_precise",
"title": "Unprocessable Entity",
"status": 422,
"detail": "USDC has 6 decimal places; amounts are never rounded.",
"instance": "/v1/payments",
"code": "amount_too_precise",
"request_id": "req_01jc3xb1n6f0q2w8e4r7t5y9v3",
"errors": [
{
"pointer": "/amount",
"code": "amount_too_precise",
"detail": "USDC has 6 decimal places; amounts are never rounded."
}
]
}| Field | Meaning |
|---|---|
code | A stable, machine-readable code. Branch on this, never on title or detail. |
type | A link to the code's page in these docs. |
detail | What exactly was wrong, in a sentence for humans. |
errors | Field problems, each with a JSON pointer into your request. |
request_id | Quote it when you contact support. It is also in the X-Request-Id header. |
In the SDKs#
Each SDK turns a problem into an exception of its status's class, carrying the whole problem:
status, code, title, detail, type, instance, request_id, retry_after (from
Retry-After, in seconds) and errors with their pointers. PHP spells them in camelCase, and the
code is errorCode there, since an exception's own code is a number.
try {
await nowgate.payments.create({ network: "tron", asset: "USDT", amount: "25.0000001" });
} catch (err) {
if (!(err instanceof InvalidRequestError)) throw err;
// err.code is "amount_too_precise"; each field's problem has its JSON pointer
pointer = err.errors[0]?.pointer;
assert.equal(err.code, "amount_too_precise");
}try:
client.payments.create(network="tron", asset="USDT", amount="25.0000001")
except InvalidRequestError as err:
# err.code is "amount_too_precise"; each field's problem has its JSON pointer
pointer = err.errors[0]["pointer"]
check(err.code == "amount_too_precise", err.code)try {
$nowgate->payments->create(['network' => 'tron', 'asset' => 'USDT', 'amount' => '25.0000001']);
} catch (InvalidRequestException $e) {
// $e->errorCode is 'amount_too_precise'; each field's problem has its JSON pointer
$pointer = $e->errors[0]['pointer'] ?? null;
check($e->errorCode === 'amount_too_precise', $e->errorCode);
}| When | TypeScript | Python | PHP (Nowgate\Exception\…) |
|---|---|---|---|
| every SDK error | NowgateError | NowgateError | NowgateException |
| any problem the API answers with | ApiError | ApiError | ApiException |
401 | AuthenticationError | AuthenticationError | AuthenticationException |
403 | PermissionError | PermissionDeniedError | PermissionException |
404 | NotFoundError | NotFoundError | NotFoundException |
409 | ConflictError | ConflictError | ConflictException |
400, 405, 413, 414, 415, 422 | InvalidRequestError | InvalidRequestError | InvalidRequestException |
429 | RateLimitError | RateLimitError | RateLimitException |
5xx | ServerError | ServerError | ServerException |
| no answer: refused, reset or timed out | ConnectionError | APIConnectionError | ConnectionException |
| a webhook that doesn't verify | SignatureVerificationError | SignatureVerificationError | SignatureVerificationException |
An answer that isn't problem JSON, such as a proxy's error page, becomes the exception of its
status with the code http_<status> and the first 500 bytes of the body as its detail. The SDKs
already retry what may succeed on a retry: see Idempotency and retries.
Retrying#
Most errors fail the same way if you send the same request again. Retry, with the same
Idempotency-Key, only after 409, 429 and 5xx errors, and honor Retry-After when present.
When 5xx errors go on, status.nowgate.io says whether Nowgate
knows of an incident, and when it's resolved.
Rate limits#
When a limit is reached, the API answers 429 with the rate_limited code, and
Retry-After gives the seconds to wait. The limits:
| Limit | Default |
|---|---|
Reads (GET) per API key | 25 a second, in bursts of up to 50 |
| Writes (every other method) per API key | 10 a second, in bursts of up to 20 |
| Requests per client address | 50 a second, in bursts of up to 100 |
| Requests with a refused API key, per address | 20 a minute |
A burst is how many requests may arrive at once. After it, requests are allowed at the
steady rate. Each key counts on its own, so a busy integration doesn't slow down
another one. An IPv6 address counts together with the rest of its /64.
An address that keeps presenting keys that are refused with 401 must wait before any
of its keys is checked again, even a valid one, so fix the key rather than retrying it.
Spread bulk work over time, and after a 429, wait as long as Retry-After says.
All codes#
| Code | Status | Meaning |
|---|---|---|
address_ | 409 | This refund's address was given already. |
amount_ | 422 | The amount is not a valid decimal string. |
amount_ | 422 | The fiat price is above the largest Nowgate converts. |
amount_ | 422 | The amount has more decimal places than the asset supports. |
amount_ | 422 | The amount is below the asset's minimum payment. |
api_ | 401 | The API key belongs to the other environment. |
asset_ | 422 | This asset is temporarily not accepting new payments. |
asset_ | 422 | Nowgate doesn't support this network and asset pair. |
attempt_ | 429 | Too many payments were started on this link, or from this address, for now. |
authentication_ | 401 | The request has no API key. |
currency_ | 422 | Nowgate doesn't price payments in this currency. |
destination_ | 409 | The payout destination is disabled. |
emails_ | 422 | This deployment sends no email, so it can't email the subscriber. |
external_ | 409 | Another payout of yours already has this external_id. |
fee_ | 503 | The network's fee can't be estimated right now, so the payout can't be quoted. |
idempotency_ | 409 | Another request with the same Idempotency-Key is still being processed. |
idempotency_ | 400 | A POST request has no Idempotency-Key header. |
idempotency_ | 422 | The Idempotency-Key was already used for a different request. |
insufficient_ | 409 | Your available balance doesn't cover the payout and its fees. |
insufficient_ | 403 | The API key isn't allowed to do this. |
internal_ | 500 | Something went wrong on Nowgate's side. |
invalid_ | 401 | The API key is not valid. |
invalid_ | 400 | An ID in the request is not a valid Nowgate ID of the expected kind. |
invalid_ | 422 | Some items of the batch are invalid, so nothing was created. |
invalid_ | 400 | The request is malformed. |
invoice_ | 409 | The invoice can't be paid: it is paid already, or void. |
ip_ | 403 | The request came from an IP address the API key doesn't allow. |
link_ | 409 | The payment link is no longer active. |
merchant_ | 403 | The merchant account is waiting for activation. |
merchant_ | 403 | The merchant account is suspended. |
method_ | 405 | This path doesn't support the HTTP method used. |
not_ | 404 | There is nothing at this address, or nothing your key can see. |
not_ | 409 | Nothing of this payment can be refunded. |
payout_ | 422 | The payout would pass one of your payout limits. |
payout_ | 409 | The payout can't be canceled any more. |
plan_ | 409 | The plan is archived: it takes no new subscriptions. |
pool_ | 503 | No deposit address is ready for this network yet. |
rate_ | 429 | Too many requests in a short time. |
rate_ | 503 | The asset can't be priced in the currency right now. |
refund_ | 422 | The refund is more than the payment has left to refund. |
request_ | 413 | The request body is too large. |
subscription_ | 409 | The subscription is canceled, so it can't change. |
subscription_ | 429 | Your account reached its daily limit of new subscriptions. |
too_ | 409 | You have the most enabled payout destinations allowed. |
unknown_ | 400 | The body contains a field this operation doesn't accept. |
unsupported_ | 415 | The body isn't sent as JSON. |
uri_ | 414 | The URL is too long. |
validation_ | 422 | The request is well-formed JSON, but some values are not acceptable. |