Skip to content

Errors

Speedmap uses conventional HTTP status codes and one JSON error shape with a stable, machine-readable code.

View as Markdown

Successful requests return 2xx. 4xx means the request cannot succeed as sent; 5xx means a temporary problem on our side or upstream, and the request is safe to retry.

Every error has the same body. Branch on error.code, never on message, which is written for people and can change.

  • error.codestringrequired

    Stable, machine-readable code from the table below.

  • error.messagestringrequired

    Human-readable explanation.

  • error.reference_idstring

    Present when a verification was created before the failure. Use it with support.

  • error.detailsarray of objects

    For validation errors: { path, message } per field. path is dotted, e.g. match.date_of_birth.

400 Bad Request
{
  "error": {
    "code": "invalid_request",
    "message": "Request validation failed",
    "details": [
      {
        "path": "nin",
        "message": "NIN must be 11 digits"
      },
      {
        "path": "match.date_of_birth",
        "message": "Use YYYY-MM-DD"
      }
    ]
  }
}

Error codes

StatusCodeMeaningWhat to do
400
invalid_request
The body or query failed validation. details has one entry per field.
Fix the fields listed in details. Do not retry unchanged.
400
invalid_idempotency_key
Idempotency-Key is shorter than 8 or longer than 128 characters.
Use a UUID.
400
invalid_categories
Consent categories is not a 4-letter Y/N mask, or is NNNN.
Send a mask with at least one Y, e.g. YYNN.
400
email_required
Consent channel email was chosen without an email.
Add email, or use another channel.
400
too_many_items
A bulk job has more than 10,000 items.
Split the job.
400
invalid_amount
A wallet top-up is below ₦1,000 or above ₦10,000,000.
Choose an amount in range.
400
phone_required
A top-up was started for a workspace with no contact phone number.
Add a phone number in the top-up dialog (saved to the workspace profile).
400
invalid_token
An email verification, password reset or invitation link is malformed or was already used.
Request a new link from the dashboard or sign-in page.
400
token_expired
The link has expired (verification 24 h, reset 30 min, invitation 7 days).
Request a new link.
401
unauthorized
The API key is missing, malformed or revoked.
Send Authorization: Bearer sk_… with an active key.
401
key_expired
The API key passed its expires_at.
Create or rotate a key in the dashboard and deploy it.
401
invalid_code
The two-factor or emailed sign-in code is wrong. details.attempts_left counts down; five failures end the attempt.
Enter the current code from your authenticator, or a backup code.
401
invalid_otp
The one-time code on the hosted consent page is wrong.
Re-enter it. Five failures lock the request.
402
insufficient_balance
Live only: the wallet balance is below the price of the check.
Top up the wallet in the dashboard, then retry.
403
account_suspended
The workspace is suspended.
Contact support.
403
forbidden
Your role is not allowed to perform this action.
Ask a workspace owner or admin.
403
email_unverified
A live key or go-live application needs a verified email address.
Open the verification link we emailed, or resend it from the dashboard banner.
403
mfa_required
Two-factor authentication is on for this account and this sign-in has not completed it.
Complete the second step with POST /auth/mfa/verify.
403
mfa_required_by_org
The workspace requires two-factor authentication and this member has not enabled it. Dashboard writes are blocked; reads still work.
Enable two-factor authentication under Settings → Security.
403
key_scope
The API key is not scoped to this product or is read-only.
Use a key with the product in its scopes, or create one in the dashboard.
403
ip_not_allowed
The request came from an IP address outside the key’s allowlist.
Call from an allowed address, or edit the key’s allowlist.
403
live_access_required
A live action before go-live was approved.
Complete go-live in the dashboard, or use a sandbox key.
403
not_entitled
The workspace is not enabled for the product or method (e.g. raw NIN lookup).
Use a permitted method, or request access in the dashboard.
403
product_not_live
The product is in preview and answers in sandbox only.
Use a sandbox key until the product is live.
403
sandbox_only
A sandbox endpoint (such as consent approve/deny) was called with a live key.
Use a sandbox key.
404
not_found
No object with that id in this mode.
Check the id and that the key mode (sandbox or live) matches.
404
consent_not_found
The consent_id on a check does not exist in this mode.
Create a consent request first.
404
unknown_product
The product does not exist.
Check the endpoint path.
409
idempotency_key_reused
The Idempotency-Key was already used with a different body.
Use a new key for a new request.
409
consent_not_granted
Revoke was called on a consent that is not granted.
Only granted consents can be revoked.
409
consent_not_pending
A decision was made on a consent that is no longer pending.
Read the consent to see its status.
409
payment_not_pending
A top-up was checked or simulated after it was already paid, expired or failed.
Read the payment; start a new top-up if needed.
412
consent_not_granted
A check used a consent that is pending, denied, expired or revoked.
Wait for consent.updated, or create a new request.
412
consent_expired
The consent grant is older than 24 hours.
Create a new consent request.
412
consent_subject_mismatch
The consent was granted for a different identifier.
Use the consent for this identifier.
422
provider_rejected_request
The upstream registry rejected the request as invalid.
Check the identifier and fields. reference_id identifies the attempt.
429
rate_limited
The API key exceeded its per-minute budget.
Wait for Retry-After seconds, then retry with back-off.
429
too_many_attempts
Too many attempts on a sign-in or consent flow.
Wait a few minutes.
502
consent_hub_error
NIBSS Consent Hub rejected or failed the consent request.
Retry later; check the identifier.
503
provider_unavailable
The upstream for this product is temporarily switched off.
Retry later. Safe with the same Idempotency-Key.
503
verification_unavailable
The check could not be completed (provider outage or a Speedily-side issue).
Retry later with the same or a new Idempotency-Key. You are not charged.
503
payments_unavailable
The payment provider (FintavaPay) could not open a one-time account or confirm a transfer.
Retry in a few minutes. Nothing was charged.

Retrying safely

  • Retry 429, 502 and 503 with exponential back-off. On 429, honour Retry-After.
  • Always send an Idempotency-Key on POST requests and reuse it on retry: a retried check returns the original result and is never billed twice. A key whose first attempt failed with 503 is released, so the retry runs again.
  • Do not retry 4xx responses unchanged (except 429); they will fail the same way.

Statuses that are not errors

  • 202 Accepted with status: "pending": the provider has not answered yet. See pending verifications.
  • 200 with status: "not_found": the check ran and no record exists.

Questions? Email developers@speedmap.ng. This page is also available as Markdown.