Skip to content

Error Codes

All Sangho API errors return a flat structured JSON object (no nested keys) with a business error code, a readable message, and a direct link to the section of this page that explains it. Clicking a doc_url link from your console or logs brings you here and automatically highlights the relevant row.

Error Structure

ChampTypeRequisDescription
typestringRequisError category — one of the 7 values listed below. Useful for generic handling (displaying a message per category).
codestringRequisPrecise Sangho business code — e.g. AMOUNT_TOO_SMALL. This is the source of truth for fine-grained error handling, to be preferred over type and the HTTP status code.
messagestringRequisHuman-readable error description
statusintegerRequisAssociated HTTP code — 400, 401, 403, 404, 409, 422, 429, 500
doc_urlstringOptionnelDirect link to the row of this page matching the code — e.g. https://docs.sangho.ga/errors#AMOUNT_TOO_SMALL
paramstringOptionnelField at fault for validation errors (422)
errorsobjectOptionnelPer-field detail for multi-field validation errors — {`{ "amount": ["..."] }`}
request_idstringOptionnelRequest identifier, to provide to support if needed

The 7 categories (type)

Every error belongs to one of these 7 categories. The JavaScript SDK exposes the same taxonomy on err.type — handy for generic branch handling (retry, login redirect, etc.) before drilling down to the precise code if needed.

typeTypical HTTP statusMeaning
AUTHENTICATION_ERROR401API key missing, invalid, or expired
PERMISSION_ERROR403Identity recognized, but the action is not authorized (public key, restricted account, insufficient plan…)
NOT_FOUND_ERROR404Resource not found
CONFLICT_ERROR409Conflict — reused idempotency key, duplicate
VALIDATION_ERROR422Syntactically correct request, but invalid at the business level
RATE_LIMIT_ERROR429Call quota exceeded
API_ERROR500Error on Sangho's side

Standard HTTP Codes

The HTTP code indicates the error category. Use it to guide your retry and display logic.

CodeDescription
200Success — request processed
201Resource created successfully
400Invalid request — malformed JSON or incorrect headers
401API key missing, expired, or invalid
403Insufficient permission — the App does not have access to this resource or action
404Resource not found — incorrect ID or deleted resource
409Conflict — idempotency key reused with a different request body
422Business validation failed — business rule not respected
429Too many requests — rate limit reached, apply backoff
500Internal Sangho error — retry with exponential backoff

Business Error Codes

All codes follow the SCREAMING_SNAKE_CASE standard, with no prefix. Hook your error handlers on code rather than the HTTP status — the code is the source of truth.

Authentication — 401

CodeDescription
MISSING_API_KEYAPI key missing — provide it via Authorization: Bearer <key>
INVALID_API_KEYAPI key invalid, revoked, or malformed
EXPIRED_API_KEYAPI key expired — generate a new one from your dashboard
INVALID_TOKENInvalid or expired JWT token
AUTHENTICATION_FAILEDGeneric authentication failure — check the Authorization header

Authorization — 403

These errors are returned when the identity is recognized but the operation is not authorized for this key, account, or plan.

CodeDescription
PERMISSION_DENIEDPermission denied — your key is not authorized to perform this action on this resource
PUBLIC_KEY_NOT_ALLOWEDThis route requires a secret key (sk_prod_ or sk_test_). Never expose your secret key client-side
APP_DISABLEDThe application is disabled — re-enable it from your dashboard
APP_NOT_VERIFIEDApplication not verified — KYC pending approval
INSUFFICIENT_PERMISSIONSInsufficient permissions for this resource or action
SANDBOX_ONLYThis route is restricted to the sandbox environment — use a sk_test_ key
CURRENCY_NOT_IN_PLANThe requested currency is active on Sangho but not included in your current plan — upgrade to enable it
COMPANY_BLACKLISTEDThis account has been blacklisted and no longer has access to the platform — contact support
COMPANY_PAYMENTS_DISABLEDIncoming payments are currently disabled for this account
COMPANY_PAYOUTS_DISABLEDOutgoing payouts are currently disabled for this account
COMPANY_SANDBOX_ONLYThis account is restricted to sandbox (test) mode for now
COMPANY_KYC_REQUIREDKYC verification is required before payments can be processed
Attention

The PERMISSION_DENIED code is notably returned by POST /v1/reset when called with a production key (sk_prod_). This route wipes all of the application’s data and is only accessible in sandbox. Use a sk_test_ key to access it.

Resource Not Found — 404

CodeDescription
NOT_FOUNDResource not found — incorrect ID, deleted resource, or access denied
CUSTOMER_NOT_FOUNDNo Customer with this ID for this application
PRODUCT_NOT_FOUNDNo Product with this ID
PAYMENT_INTENT_NOT_FOUNDNo PaymentIntent with this ID
TRANSACTION_NOT_FOUNDNo Transaction with this ID
REFUND_NOT_FOUNDNo Refund with this ID
INVOICE_NOT_FOUNDNo Invoice with this ID
SUBSCRIPTION_NOT_FOUNDNo Subscription with this ID
CHECKOUT_SESSION_NOT_FOUNDNo CheckoutSession with this ID
PAYMENT_METHOD_NOT_FOUNDNo PaymentMethod with this ID
RECEIPT_NOT_FOUNDNo Receipt with this ID
WEBHOOK_NOT_FOUNDNo Webhook with this ID
SEARCH_NO_RESULTSNo results for the search — check the ?search= parameter

Conflict — 409

CodeDescription
IDEMPOTENCY_CONFLICTA request with the same Idempotency-Key was already processed with a different request body
DUPLICATE_EMAILA Customer with this email already exists for this application
DUPLICATE_SKUA Product with this SKU already exists

Business Validation — 422

CodeDescription
VALIDATION_ERRORThe provided data is invalid — check the errors field for per-field details
MISSING_FIELDA required field is missing from the request
INVALID_FIELDA field value is incorrect or outside accepted values
INVALID_FILTER_PARAMA filter parameter (?param=) does not match any model field
INVALID_CURRENCYThe provided currency is not a recognized ISO 4217 code, or is not enabled on Sangho — see supported currencies
AMOUNT_TOO_SMALLAmount below the minimum allowed (100 XAF by default)
AMOUNT_TOO_LARGEAmount above the maximum allowed
INVALID_STATUS_TRANSITIONThe requested status transition is not allowed (e.g. reopening a canceled Invoice)
PAYMENT_INTENT_UNCANCELABLECannot cancel this PaymentIntent in its current status
PAYMENT_INTENT_NOT_CONFIRMABLECannot confirm this PaymentIntent in its current status
PAYMENT_INTENT_NOT_CAPTURABLECannot capture — status must be requires_capture
CAPTURE_AMOUNT_EXCEEDS_ORIGINALCapture amount exceeds the original PaymentIntent amount
CHECKOUT_SESSION_EXPIREDThe checkout session has expired — ask the customer to start a new one
SUBSCRIPTION_ALREADY_CANCELEDThis Subscription is already canceled and cannot be canceled again
INVOICE_NOT_DRAFTABLEThis Invoice is not in a state that allows reverting to draft

Rate Limiting — 429

CodeDescription
RATE_LIMIT_EXCEEDEDAPI call quota exceeded — check the Retry-After header for wait time
TOO_MANY_REQUESTSToo many concurrent requests from this API key

Payments & Mobile Money

CodeDescription
INSUFFICIENT_FUNDSInsufficient funds on the buyer side (Mobile Money or card)
PAYMENT_METHOD_NOT_SUPPORTEDPayment method not enabled for this merchant account
MOBILE_MONEY_TIMEOUTThe buyer did not confirm the payment within the allotted time (usually 3 min)
MOBILE_MONEY_REJECTEDThe buyer rejected the payment request on their phone
MOBILE_MONEY_NUMBER_INVALIDIncorrect Mobile Money number or not registered with the operator
MOBILE_MONEY_LIMIT_REACHEDDaily or monthly operator limit reached for this number
CUSTOMER_BLACKLISTEDThe Customer is blacklisted — no payments allowed

Webhooks & Integrations

CodeDescription
WEBHOOK_SIGNATURE_INVALIDInvalid HMAC-SHA256 signature — verify your webhook secret
WEBHOOK_ENDPOINT_UNREACHABLEThe webhook URL is not responding — Sangho will retry according to the retry policy
TERMINAL_OFFLINEThe Sangho terminal is unreachable — check the network connection
PDF_NOT_AVAILABLEThe PDF for this document has not been generated yet — try again in a moment
METHOD_NOT_ALLOWEDHTTP method not allowed on this endpoint

Server Error — 500

CodeDescription
INTERNAL_ERRORInternal Sangho error — our team has been notified. Retry with exponential backoff
DATABASE_ERRORDatabase error — temporary, retry in a moment
UPSTREAM_ERRORThird-party service error (operator, gateway) — retry or contact support

Recommended Retry Strategy

Not all errors are equal. The JavaScript SDK already applies this logic automatically (see Node.js SDK) — useful to know if you call the API directly.

HTTP CodeRecommended action
400 / 422Do not retry — fix the request parameters
401 / 403Do not retry — verify the API key, permissions, or plan
404Do not retry — verify the resource ID
409Retrieve the already-produced idempotent response — do not create a duplicate
429Wait the duration indicated in the Retry-After header, then retry
500Retry with exponential backoff (1s → 2s → 4s → 8s, max 3 attempts)
Note

The Retry-After header is included in all 429 responses. Its value is expressed in seconds.

Error Structure

ResponseResponse — AMOUNT_TOO_SMALL
json
{
  "status": 422,
  "type": "VALIDATION_ERROR",
  "code": "AMOUNT_TOO_SMALL",
  "message": "The amount is too small. The minimum is 100 XAF.",
  "param": "amount",
  "doc_url": "https://docs.sangho.ga/errors#AMOUNT_TOO_SMALL",
  "request_id": "req_5d0cc04ea0664a50a14a247f"
}

Currency outside plan (403)

ResponseResponse — CURRENCY_NOT_IN_PLAN
json
{
  "status": 403,
  "type": "PERMISSION_ERROR",
  "code": "CURRENCY_NOT_IN_PLAN",
  "message": "The currency 'USD' is not included in your current plan.",
  "doc_url": "https://docs.sangho.ga/errors#CURRENCY_NOT_IN_PLAN",
  "request_id": "req_5d0cc04ea0664a50a14a247f"
}

Access denied — sandbox only (403)

ResponseResponse — PERMISSION_DENIED
json
{
  "status": 403,
  "type": "PERMISSION_ERROR",
  "code": "PERMISSION_DENIED",
  "message": "Reset is only available in the sandbox environment. Use a sandbox key (sk_test_*) to access this route.",
  "doc_url": "https://docs.sangho.ga/errors#PERMISSION_DENIED",
  "request_id": "req_5d0cc04ea0664a50a14a247f"
}

Reused Idempotency-Key (409)

ResponseResponse — IDEMPOTENCY_CONFLICT
json
{
  "status": 409,
  "type": "CONFLICT_ERROR",
  "code": "IDEMPOTENCY_CONFLICT",
  "message": "A request with the same idempotency key is already in progress or already produced a different result.",
  "doc_url": "https://docs.sangho.ga/errors#IDEMPOTENCY_CONFLICT",
  "request_id": "req_9a1bc23de4567f89b01c234d"
}

Validation error (422)

ResponseResponse — VALIDATION_ERROR
json
{
  "status": 422,
  "type": "VALIDATION_ERROR",
  "code": "VALIDATION_ERROR",
  "message": "Email is required.",
  "errors": {
    "email": ["This field is required."],
    "amount": ["Value must be a positive integer."]
  },
  "doc_url": "https://docs.sangho.ga/errors#VALIDATION_ERROR",
  "request_id": "req_9a1bc23de4567f89b01c234d"
}

Rate limit reached (429)

ResponseResponse — RATE_LIMIT_EXCEEDED
json
{
  "status": 429,
  "type": "RATE_LIMIT_ERROR",
  "code": "RATE_LIMIT_EXCEEDED",
  "message": "Request limit reached. Retry in 60 seconds.",
  "retry_after": 60,
  "doc_url": "https://docs.sangho.ga/errors#RATE_LIMIT_EXCEEDED",
  "request_id": "req_2f4a8b1c9e3d5f70a0b2c4d6"
}

Resource not found (404)

ResponseResponse — CUSTOMER_NOT_FOUND
json
{
  "status": 404,
  "type": "NOT_FOUND_ERROR",
  "code": "CUSTOMER_NOT_FOUND",
  "message": "No Customer with the identifier 'cust_xyz123'.",
  "doc_url": "https://docs.sangho.ga/errors#CUSTOMER_NOT_FOUND",
  "request_id": "req_7c3e5a2d1b8f4096e5d7c9b1"
}

Error Handling — SDKs

bash
# HTTP errors always return a flat JSON body (no nested keys)
# Capture the HTTP status and read the "code" field directly
HTTP_STATUS=$(curl -s -o response.json -w "%{http_code}" \
  -X POST https://api.sangho.ga/v1/payment-intents/ \
  -H "Authorization: Bearer sk_test_xxxx" \
  -H "Content-Type: application/json" \
  -d '{"amount": -1, "currency": "XAF"}')


if [ "$HTTP_STATUS" != "200" ] && [ "$HTTP_STATUS" != "201" ]; then
  ERROR_CODE=$(cat response.json | python3 -c "import sys,json; print(json.load(sys.stdin)['code'])")
  echo "Error: $ERROR_CODE (HTTP $HTTP_STATUS)"
fi