Skip to main content
Fingo Pay uses conventional HTTP status codes and returns a consistent JSON error body on every non-2xx response. Each error includes a machine-readable type and code you can use for programmatic handling, a human-readable message for debugging, and a requestId you can reference when contacting support.

Error body

Every error response follows this structure:
{
  "error": {
    "type": "invalid_request_error",
    "code": "missing_parameter",
    "message": "Required parameter 'amount' is missing.",
    "param": "amount",
    "requestId": "req_01j7b6f9p5y9h"
  }
}
type
string
required
The category of error. Use this to determine the general class of the problem. See Error types below.
code
string
required
A specific, machine-readable error code for programmatic handling. See Error codes below.
message
string
required
A human-readable description of the error. Intended for developer logs and debugging — do not display raw error messages to end users.
param
string | null
The request parameter that caused the error, when applicable. Useful for highlighting the specific field in your UI or logs. Returns null when the error is not tied to a single parameter.
requestId
string
A unique identifier for the request. Always include this when contacting Fingo Pay support.

Error types

The error.type field tells you the broad category of the problem. Use it to decide how your integration should respond.
The request was malformed or contained invalid data. This covers missing parameters, bad values, schema validation failures, and missing headers. Check error.code and error.param for specifics.Typical HTTP status: 400, 409
The request could not be authenticated. The API key is missing, invalid, expired, or the authenticated organization does not have permission to perform the action.Typical HTTP status: 401, 403
Your organization has exceeded the allowed request rate. Back off, respect the Retry-After header, and retry. See Rate limits for details.Typical HTTP status: 429
The requested resource does not exist. This applies to transactions, accounts, shortcodes, and other resources looked up by ID or reference.Typical HTTP status: 404
The request conflicts with the current state of a resource. Most commonly returned when an idempotency key is reused with a different request body, path, or method.Typical HTTP status: 409
An unexpected error occurred on the Fingo Pay server. These are rare. If you encounter one, retry with exponential backoff and contact support if it persists.Typical HTTP status: 500

Error codes

The error.code field provides a specific, machine-readable identifier for the error. Use it for programmatic branching in your integration.
error.codeerror.typeHTTPDescription
unauthorizedauthentication_error401Missing Bearer token or invalid/expired API key.
forbiddenauthentication_error403Authenticated but not allowed (for example, organization not verified for production).
too_many_requestsrate_limit_error429Per-organization rate limit exceeded. Respect the Retry-After header.
conflicting_retryconflict_error409Same idempotency key reused with a different request body, path, or method.
validation_errorinvalid_request_error400Request body, query, or header failed schema validation.
missing_headerinvalid_request_error400A required header is missing (usually Idempotency-Key in the live environment).
invalid_parameterinvalid_request_error400A parameter is present but has an invalid value (bad format, out of range, or fails a business rule).
missing_parameterinvalid_request_error400A required query or body field is missing.
duplicateinvalid_request_error409Duplicate resource or reference (commonly a reused merchantTransactionId).
resource_not_foundnot_found_error404The target resource was not found (transaction, account, shortcode, etc.).
internal_errorapi_error500Server-side failure during processing. Retry with backoff; contact support if it persists.

Sample error responses

Realistic response bodies for the most common error scenarios.
401 Unauthorized
{
  "error": {
    "type": "authentication_error",
    "code": "unauthorized",
    "message": "Missing or invalid Authorization header. Provide a valid Bearer token.",
    "requestId": "req_01j7c2a1k9m4x"
  }
}
403 Forbidden
{
  "error": {
    "type": "authentication_error",
    "code": "forbidden",
    "message": "Your organization is not verified for production access. Complete verification in the dashboard.",
    "requestId": "req_01j7c3b4n7p2y"
  }
}
400 Bad Request
{
  "error": {
    "type": "invalid_request_error",
    "code": "missing_parameter",
    "message": "Required parameter 'amount' is missing.",
    "param": "amount",
    "requestId": "req_01j7c4d6q3r8z"
  }
}
400 Bad Request
{
  "error": {
    "type": "invalid_request_error",
    "code": "invalid_parameter",
    "message": "Amount must be between 1000 and 25000000 (KES 10.00 — KES 250,000.00).",
    "param": "amount",
    "requestId": "req_01j7c5e8s2t7a"
  }
}
400 Bad Request
{
  "error": {
    "type": "invalid_request_error",
    "code": "validation_error",
    "message": "phoneNumber must be a valid Kenyan MSISDN in the format +2547XXXXXXXX.",
    "param": "phoneNumber",
    "requestId": "req_01j7c6f9u4v3b"
  }
}
400 Bad Request
{
  "error": {
    "type": "invalid_request_error",
    "code": "missing_header",
    "message": "Idempotency-Key header is required for POST requests in the live environment.",
    "param": "Idempotency-Key",
    "requestId": "req_01j7c7g1w5x2c"
  }
}
409 Conflict
{
  "error": {
    "type": "conflict_error",
    "code": "conflicting_retry",
    "message": "Idempotency-Key '8b47872a-4de9-4c13-b73a-19803f55b1e4' was already used with a different request body.",
    "requestId": "req_01j7c8h3y6z1d"
  }
}
409 Conflict
{
  "error": {
    "type": "invalid_request_error",
    "code": "duplicate",
    "message": "A transaction with merchantTransactionId 'invoice_48291' already exists.",
    "param": "merchantTransactionId",
    "requestId": "req_01j7c9j5a7b0e"
  }
}
404 Not Found
{
  "error": {
    "type": "not_found_error",
    "code": "resource_not_found",
    "message": "No transaction found with transactionId 'txn_nonexistent'.",
    "param": "transactionId",
    "requestId": "req_01j7cak7c8d9f"
  }
}
429 Too Many Requests
{
  "error": {
    "type": "rate_limit_error",
    "code": "too_many_requests",
    "message": "Rate limit exceeded. Retry after 12 seconds.",
    "requestId": "req_01j7cbl9e0f8g"
  }
}
The response includes a Retry-After header with the number of seconds to wait before retrying.
500 Internal Server Error
{
  "error": {
    "type": "api_error",
    "code": "internal_error",
    "message": "An unexpected error occurred while processing your request. Please retry or contact support.",
    "requestId": "req_01j7ccm1g2h7i"
  }
}

Handling errors

Use error.type to categorize the problem, error.code to decide what your application should do, error.param to surface which field failed, and error.message for developer-facing logs.
async function createCharge(body, idempotencyKey) {
  const res = await fetch('https://api.fingopay.io/v1/mpesa/charge', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.FINGO_API_KEY}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': idempotencyKey,
    },
    body: JSON.stringify(body),
  });

  const data = await res.json();

  if (!res.ok) {
    const { type, code, message, param } = data.error;

    switch (type) {
      case 'authentication_error':
        // Check credentials or org verification status
        throw new Error(`Auth failed: ${message}`);

      case 'invalid_request_error':
        if (code === 'missing_parameter' || code === 'invalid_parameter') {
          console.error(`Fix field '${param}': ${message}`);
        }
        if (code === 'duplicate') {
          console.warn(`Duplicate transaction: ${message}`);
        }
        throw new Error(`Bad request [${code}]: ${message}`);

      case 'rate_limit_error':
        const retryAfter = res.headers.get('Retry-After') || '5';
        await new Promise((r) => setTimeout(r, parseInt(retryAfter, 10) * 1000));
        return createCharge(body, idempotencyKey);

      case 'conflict_error':
        // Generate a new idempotency key if the body changed
        throw new Error(`Conflict: ${message}`);

      case 'not_found_error':
        throw new Error(`Not found: ${message}`);

      case 'api_error':
        // Retry with backoff; contact support if persistent
        throw new Error(`Server error: ${message}`);

      default:
        throw new Error(`Unexpected error: ${message}`);
    }
  }

  return data;
}
Always log the requestId from error responses. Fingo Pay support can use it to trace the exact request in our systems.

HTTP status codes

A summary of HTTP status codes returned by the Fingo Pay API and what they mean.
StatusMeaningWhen you see it
200OKRequest succeeded. Response body contains the requested data.
202AcceptedRequest accepted for asynchronous processing. Final result arrives via webhook.
400Bad RequestThe request is invalid — missing fields, bad values, or failed schema validation.
401UnauthorizedNo valid API key provided. Check your Authorization header.
403ForbiddenAuthenticated but not permitted. Your organization may not be verified for this environment.
404Not FoundThe requested resource does not exist. Verify the ID or reference you are looking up.
409ConflictIdempotency conflict or duplicate resource. See error.code for details.
429Too Many RequestsRate limit exceeded. Back off and retry after the Retry-After interval. See Rate limits.
500Internal Server ErrorSomething went wrong on our end. Retry with exponential backoff and contact support if it persists.