Skip to main content
All errors from the UBN BaaS API follow the RFC 7807 Problem Details standard — a consistent format used across every endpoint. Every error includes a human-readable explanation you can read while debugging, and a machine-readable code field your application can branch on. For the full shape of an error response, see Error Response Format in the API overview.
Always log the correlationId from every error response. When you contact support, including the correlationId is the single fastest way to find your request in our systems — it can cut investigation time from hours to minutes.

HTTP Status Codes

The HTTP status code gives you the first, broad signal about what went wrong. Read the code field in the body for the specific cause.
StatusMeaning
200 OKThe request succeeded and a response body is returned. Used for GET requests and most updates.
201 CreatedA new resource was created. For example: a new virtual account, a new API key. The created resource is returned in the data field.
202 AcceptedThe request was accepted but processing happens asynchronously. The operation is in progress — subscribe to the relevant webhook event to know when it completes. Used for payments and KYB review.
204 No ContentThe request succeeded but there is nothing to return. Used for DELETE operations such as removing a webhook endpoint.

Error Codes

Use the code field in the error response body to handle specific errors programmatically. The table below covers every code the API can return, what it means, and the exact fix.
Error codes are stable — we will not rename or remove a code without a deprecation period. You can safely use them in switch statements and conditional logic.

Onboarding and Identity

CodeHTTP StatusWhat happenedHow to fix
NDPR_CONSENT_REQUIRED422The ndprConsent field in the registration request is false or missing. The Nigeria Data Protection Regulation requires explicit consent before we can process personal data.Set ndprConsent: true in the registration request body.
FREE_EMAIL_REJECTED422You used a free consumer email address (such as @gmail.com, @yahoo.com, @outlook.com). Only corporate domain email addresses are accepted.Register with your company domain email address (for example yourname@yourcompany.com).
CODE_EXPIRED400The OTP (one-time password) you submitted has expired. OTPs are valid for 10 minutes.Re-register or use the resend option to receive a fresh code, then submit the new code.
CODE_ALREADY_USED409This OTP has already been submitted successfully.Check whether your email address is already verified by attempting to log in. If it is, proceed to the next onboarding step.
MISSING_DOCS422Your KYB (Know Your Business) document submission is incomplete. Required documents have not been uploaded.Check the KYB Documents guide for the full list of required documents, then upload all missing files.
NAME_MISMATCH422The name in your registration form does not match the name on record for the BVN or NIN you provided.Compare the name on your form exactly against the name registered with NIMC (for NIN) or the CBN (for BVN). Correct any spelling differences, name order differences, or missing middle names.

Authentication and Authorisation

CodeHTTP StatusWhat happenedHow to fix
ENVIRONMENT_MISMATCH403You used a sandbox key (ubn_sb_) against the production URL, or a production key (ubn_pk_) against the sandbox URL.Check your base URL and key prefix. Sandbox keys must only be used with https://sandbox.api.unionbank.ng. Production keys must only be used with https://api.unionbank.ng.
INVALID_SIGNATURE401The HMAC-SHA256 signature in the X-Signature header does not match what we computed from your request body.Re-read the Signing Requests guide. Most common causes: using the API key instead of the signing secret, or your HTTP client re-serialising the JSON after you signed it.

Payments and Accounts

CodeHTTP StatusWhat happenedHow to fix
INSUFFICIENT_FUNDS422The source account does not have enough balance to cover the transfer amount plus any applicable fees.Top up the source account, or reduce the transfer amount. The detail field in the error includes the current balance and the minimum required amount.
ACCOUNT_NOT_FOUND404The account number you specified does not exist in the system.Double-check the account number — a transposed digit is a common cause. If you are referencing a recently created account, wait a few seconds and retry (account creation is near-instant but not instantaneous).

Quota and Rate Limiting

CodeHTTP StatusWhat happenedHow to fix
QUOTA_EXHAUSTED402Your plan’s API call quota for this billing period has been fully used.Check the X-Quota-Reset header to find the Unix timestamp when your quota resets. If you need more calls sooner, contact your UBN account manager to upgrade your plan.
RATE_LIMITED429You are sending too many requests too quickly.Wait for the number of seconds specified in the Retry-After response header before sending another request. Implement exponential backoff for automated retries — see Retry Strategy below.

Infrastructure and Idempotency

CodeHTTP StatusWhat happenedHow to fix
CIRCUIT_BREAKER_OPEN503A downstream service (such as the NIP payment network or an identity bureau) is temporarily unavailable. The circuit breaker has opened to prevent cascading failures.This is temporary. Wait 30 seconds, then retry. The X-Circuit-Breaker-Open: true response header will be absent once the circuit closes.
IDEMPOTENCY_REPLAY409You sent a request with an X-Idempotency-Key that was already used for a previous successful request.This is not always an error — it means your original request succeeded. The response body contains the original result. Check the X-Idempotency-Replayed: true response header to confirm it is a replay. See Idempotency Keys.

Retry Strategy

For 429 Rate Limited and 503 Circuit Breaker Open errors, use exponential backoff with jitter: wait progressively longer between each retry attempt, and add a small random delay to avoid multiple clients hammering the API at the same time. The recommended sequence: 1 second, then 2 seconds, then 4 seconds, then 8 seconds, then give up and surface the error to the caller.
async function fetchWithRetry(url, options, maxRetries = 4) {
  const retryableStatuses = new Set([429, 503]);

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const response = await fetch(url, options);

    if (!retryableStatuses.has(response.status)) {
      // Success, or a non-retryable error — return immediately
      return response;
    }

    if (attempt === maxRetries) {
      // We have exhausted our retries
      return response;
    }

    // Exponential backoff: 1s, 2s, 4s, 8s
    const backoffMs = Math.pow(2, attempt) * 1000;
    // Add up to 500ms of random jitter to avoid thundering herd
    const jitterMs = Math.random() * 500;
    const waitMs = backoffMs + jitterMs;

    console.warn(
      `Request failed with ${response.status}. Retrying in ${Math.round(waitMs)}ms ` +
      `(attempt ${attempt + 1} of ${maxRetries})...`
    );

    await new Promise(resolve => setTimeout(resolve, waitMs));
  }
}

// Usage
const response = await fetchWithRetry(
  'https://sandbox.api.unionbank.ng/api/v1/accounts',
  {
    headers: {
      'Authorization': `ApiKey ${process.env.UBN_API_KEY}`,
      'Content-Type': 'application/json',
    },
  }
);
For 429 errors, check the Retry-After response header first — it tells you the exact number of seconds to wait. Use that value instead of the exponential backoff if it is present, as it reflects the actual rate limit window rather than an estimate.

Circuit Breaker

The circuit breaker protects the platform when a downstream service (the NIP payment network, NIBSS, or an identity verification bureau) experiences degraded performance. When too many requests to a downstream service fail within a short window, the circuit breaker “opens” and all requests that would reach that service are rejected immediately — rather than queuing and timing out. This is a safety mechanism, not a sign that your integration is broken. When the circuit is open:
  1. The response status is 503 with code CIRCUIT_BREAKER_OPEN.
  2. The X-Circuit-Breaker-Open: true header is present in the response.
  3. Wait 30 seconds before retrying.
  4. The circuit closes automatically once the downstream service recovers.
You can check real-time service status at sandbox.api.unionbank.ng/status.

Getting Help

If you cannot resolve an error using this reference, contact support at baas-support@unionbank.ng. Include the following in your message to get the fastest response:
  • The correlationId from the error response body
  • The endpoint you were calling
  • The error code and HTTP status
  • A sanitised version of your request (remove API keys, account numbers, and personal data before sharing)
The correlationId is the single most important piece of information you can provide. It points directly to your request in our logs, which allows us to see exactly what happened — including what we received, what was checked, and what failed — without needing to reproduce the issue.