Skip to content

Errors

Every failed /v1 request returns the same JSON shape, whether the failure came from validation, authentication, a provider, or Nuansis itself.

{
"error": {
"code": "invalid_request",
"message": "Exceeds balance: 1200 NGN"
},
"request_id": "5f2d8a41-3c6b-4c19-9f0e-7a8b9c0d1e2f"
}
Field Type Notes
error.code string Machine-readable, stable. Branch on this, not on message.
error.message string Human-readable explanation. May change.
error.details object Present only when the error carries structured extras (scopes, rate-limit hints).
request_id string Same value as the X-Request-Id response header. Include it in support requests.

Successful responses use the sibling envelope { data, meta: { request_id } }; they never include an error key.

Code HTTP status Raised when
invalid_request 400, 405, 422 Validation failed, a field is unsupported, the balance is insufficient, or the route/method does not accept the request
unauthorized 401 No key presented (missing_api_key) or the key is malformed, unknown, or revoked (invalid_api_key)
forbidden 403 The caller is authenticated but not allowed to perform the action
insufficient_scope 403 The key lacks one or more scopes the endpoint requires
not_found 404 The resource does not exist in this workspace
conflict 409 Generic state conflict
idempotency_conflict 409 An Idempotency-Key was reused with a different request body, path, or method
rate_limited 429 The per-key request budget, or a verification-specific limit, was exceeded
provider_error 502, 503, 504 The upstream bank, collection, or messaging provider failed or timed out
coming_soon 501 The endpoint exists but the capability is not available yet
internal_error 500 An unexpected server failure

The code is taken from an explicit code in the exception when it is part of this vocabulary; otherwise it is derived from the HTTP status. That is why 405 Method Not Allowed and 422 Unprocessable Entity both surface as invalid_request, and why 502/503/504 all surface as provider_error.

Some errors add details:

Scope failure
{
"error": {
"code": "insufficient_scope",
"message": "This API key is missing the required scope(s)",
"details": {
"required": ["collections:write"],
"granted": ["read"],
"missing": ["collections:write"]
}
},
"request_id": "5f2d8a41-3c6b-4c19-9f0e-7a8b9c0d1e2f"
}
Rate limit
{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded",
"details": { "limit": 120, "retry_after_seconds": 17 }
},
"request_id": "5f2d8a41-3c6b-4c19-9f0e-7a8b9c0d1e2f"
}

The API validates request bodies with a global pipe. When several fields fail, the individual messages are joined into one message string with , :

{
"error": {
"code": "invalid_request",
"message": "amount must be a number conforming to the specified constraints, currency must be one of the following values: NGN"
},
"request_id": "5f2d8a41-3c6b-4c19-9f0e-7a8b9c0d1e2f"
}

Field names in messages are the JSON field names you sent, so they map directly to the request body.

Status Retry? How
400, 401, 403, 404, 405, 422 No Fix the request or the key
409 idempotency_conflict No Use a new key or send the original body
429 Yes Wait details.retry_after_seconds, then retry
500, 502, 503, 504 Yes Retry with backoff, and reuse the same Idempotency-Key

Any POST that creates money movement should be retried with the same Idempotency-Key so a successful-but-unseen first attempt cannot be applied twice. See Idempotency.

Status Meaning on /v1
200 Success (reads, bank resolve, verification check)
201 Created (collections, reserved accounts, transfers, withdrawals, beneficiaries, deposits, verifications)
400 Invalid request or business rule failure
401 Missing or invalid API key
403 Forbidden or insufficient scope
404 Resource not found
409 Conflict or idempotency conflict
429 Rate limited
500 Internal error
501 Capability not available yet
502 / 503 / 504 Upstream provider failure