Skip to main content
Every RemitFlex API error returns a structured JSON body alongside an HTTP status code. Understanding the shape of that response — and knowing which errors are safe to retry versus which require human intervention — lets you build payment flows that degrade gracefully and recover automatically when things go wrong.

Error Response Structure

All errors follow a consistent envelope regardless of the HTTP status code. This makes it straightforward to write a single error-handling function in your integration layer.
{
  "error": {
    "code": "INVALID_RECIPIENT",
    "message": "The recipient bank account number is invalid for the specified destination country.",
    "field": "recipient.account_number",
    "docs_url": "https://docs.remitflex.com/configuration/error-codes"
  }
}
FieldTypeDescription
codestringMachine-readable error identifier. Use this in your error-handling logic.
messagestringHuman-readable explanation. Surface this in logs and support tickets, not in end-user UI.
fieldstring | nullThe request field that caused the error, using dot notation. Present only for validation errors.
docs_urlstringDirect link to this page for the relevant error category.
Always branch your error-handling logic on error.code, not on error.message. The message text may change between API versions; the code is a stable, versioned identifier.

HTTP Status Codes

RemitFlex uses a standard subset of HTTP status codes. Each maps to a category of problem with a distinct resolution path.
StatusMeaningTypical cause
400 Bad RequestValidation errorMalformed request body or invalid field value
401 UnauthorizedAuthentication failureMissing, expired, or malformed API key
402 Payment RequiredInsufficient funds or limitAccount balance too low or payment cap exceeded
403 ForbiddenAction not permittedAccount tier, permission scope, or compliance block
404 Not FoundResource missingInvalid payment ID, quote ID, or recipient ID
409 ConflictDuplicate requestIdempotency key already associated with a completed request
422 Unprocessable EntityBusiness logic errorCorridor unavailable, quote expired, or unsupported flow
429 Too Many RequestsRate limit exceededRequest volume exceeded your account’s rate limit
500 Internal Server ErrorRemitFlex server faultTransient infrastructure issue; safe to retry with backoff

Error Code Reference

The sections below group every error code by category. Each entry includes the HTTP status it accompanies, a description of the root cause, and the recommended resolution.
Authentication errors indicate a problem with how your request is identifying itself to the API. These are almost always configuration issues rather than transient faults, so retrying without fixing the root cause will not help.
CodeHTTP StatusDescriptionResolution
UNAUTHORIZED401The Authorization header is missing, malformed, or contains an invalid API key.Verify the key is correctly formatted as Bearer rf_test_... or Bearer rf_live_... and that no characters were truncated.
API_KEY_REVOKED401The API key was explicitly revoked from the Dashboard or via the API Keys management endpoint.Generate a new key under Settings → API Keys and update your environment configuration.
INSUFFICIENT_PERMISSIONS403The API key is valid but does not have the scope required to perform this action (e.g., a read-only key attempting to create a payment).Issue a new key with the appropriate permission scopes, or contact your account administrator.
RemitFlex API keys do not expire on a time basis, but they are invalidated immediately if revoked. If you rotate keys as part of your security policy, ensure your deployment pipeline updates the key atomically to avoid UNAUTHORIZED errors during the rotation window.
Payment errors arise during the creation, processing, or delivery of a payment or conversion. Some are recoverable by correcting input; others indicate a terminal state for that specific payment attempt.
CodeHTTP StatusDescriptionResolution
INVALID_AMOUNT400The requested amount is below the corridor minimum or above the corridor maximum.Query GET /corridors/{id} for the current min/max limits and adjust the amount accordingly.
INVALID_CURRENCY400The currency code is not recognized or is not supported in the requested direction.Check the Supported Currencies page and ensure you are using the correct uppercase symbol or ISO 4217 code.
INVALID_RECIPIENT400One or more recipient fields — such as bank account number, routing number, or mobile wallet ID — are invalid for the destination country.Re-validate recipient details against the field requirements in GET /corridors/{id}/recipient-fields.
CORRIDOR_UNAVAILABLE422The source-to-destination corridor is not currently active. This may be a temporary suspension or a permanently unsupported route.Check the RemitFlex Status Page for corridor outages. If the corridor should be supported, contact support.
QUOTE_EXPIRED422The conversion quote referenced by quote_id has passed its validity window (typically 30 seconds).Fetch a fresh quote via POST /quotes and use the new quote_id in your payment request.
INSUFFICIENT_FUNDS402Your RemitFlex wallet balance for the source currency is too low to cover the payment amount plus fees.Top up your balance via the Dashboard or the POST /funding endpoint, then retry the request.
PAYMENT_CANCELLED409The payment was previously cancelled and cannot be resubmitted with the same ID.Create a new payment with a new Idempotency-Key if you still need to complete the transfer.
RECIPIENT_BANK_REJECTED422The recipient’s bank returned a rejection after RemitFlex submitted the payout. This is terminal for the payment.Contact the recipient to verify their account details, then create a new payment with corrected recipient information.
QUOTE_EXPIRED is one of the most common errors in production integrations. Design your checkout or confirmation flow to re-fetch a quote immediately before the user confirms payment — not at page load — to minimize the window in which expiry can occur.
Compliance errors are raised by RemitFlex’s real-time sanctions screening, KYC/KYB verification checks, and transaction limit enforcement. These errors are not safe to retry without first resolving the underlying compliance issue.
CodeHTTP StatusDescriptionResolution
KYC_REQUIRED403The sending user has not completed identity verification. This is required before any payment can be initiated.Direct the user to your KYC flow. RemitFlex provides a hosted verification link via POST /kyc/sessions.
KYB_REQUIRED403The transaction size or destination requires business verification (KYB) that has not been completed for this account.Complete KYB from Dashboard → Settings → Verification or via the KYB API.
LIMIT_EXCEEDED402The transaction would cause the account’s daily or monthly volume limit to be exceeded.Wait until the limit resets (limits reset at 00:00 UTC), or contact your account manager to request a limit increase.
COMPLIANCE_REVIEW403The transaction has been flagged for manual compliance review and cannot be processed automatically.This review typically completes within one to two business days. You will receive a webhook event (compliance.review.completed) when it resolves.
SANCTIONS_MATCH — HTTP 403This error means the transaction, recipient, or counterparty has been flagged by RemitFlex’s real-time OFAC, EU, or UN sanctions screening engine. The payment is blocked and cannot be retried. Do not attempt to re-submit the transaction under any circumstances. Contact compliance@remitflex.com immediately with the full error response and your payment reference. Attempting to circumvent sanctions screening may violate applicable law.
RemitFlex enforces per-key rate limits to protect platform stability. Rate limit errors are always transient — the correct response is to slow down and retry after the window resets.
CodeHTTP StatusDescriptionResolution
RATE_LIMIT_EXCEEDED429Your API key has exceeded the allowed number of requests within the current time window.Check the Retry-After response header for the number of seconds to wait before sending the next request. Implement exponential backoff for sustained high-volume flows.
The 429 response includes headers to help you manage pacing:
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1714000860
Retry-After: 42
HeaderDescription
X-RateLimit-LimitMaximum requests allowed per minute for this key
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp at which the window resets
Retry-AfterSeconds to wait before retrying
Default rate limits are 300 requests per minute for live keys and 60 requests per minute for sandbox keys. Enterprise accounts have elevated limits configured on their contract. Contact your account manager if your use case requires higher throughput.

Idempotency

RemitFlex supports idempotent requests for all state-mutating endpoints (POST, PATCH, DELETE). Idempotency lets you safely retry a request — for example, after a network timeout — without risking duplicate payments or double charges. To use idempotency, include a unique Idempotency-Key header on every mutating request:
curl https://api.remitflex.com/v1/payments \
  -X POST \
  -H "Authorization: Bearer rf_live_7vBz2wLsYt..." \
  -H "Idempotency-Key: pay_idem_01HX9K3Z4P8QRVT6WNMJB5DFA" \
  -H "Content-Type: application/json" \
  -d '{
    "source_currency": "USDC",
    "destination_currency": "PHP",
    "amount": "200.00",
    "quote_id": "qt_01HX9K3Z4P8QRVT6WNMJB5DFA"
  }'
Key rules for idempotency keys:
  • Generate a new, unique key for each intended payment. A UUID v4 or ULID is a reliable choice.
  • Reuse the same key when retrying a request that received no response or a 5xx error.
  • Idempotency keys are scoped to your API key and expire after 24 hours.
  • If you reuse a key with a different request body, RemitFlex returns 409 Conflict with code PAYMENT_CANCELLED to protect against accidental misuse.
If a request with a given idempotency key is still being processed (e.g., the original request is in flight), subsequent requests with the same key will return 409 Conflict. Poll GET /payments/{id} for the status of the in-flight payment rather than retrying the creation request.

Retry Strategy

Not all errors are worth retrying. Retrying a permanent error wastes resources and delays detecting a configuration problem. Use this table to decide whether to retry, fix and retry, or escalate.
Error typeExamplesRetryable?Recommended action
Transient server fault500 Internal Server Error✅ YesRetry with exponential backoff: 1s → 2s → 4s → 8s (max 3 attempts)
Rate limitRATE_LIMIT_EXCEEDED✅ YesWait for Retry-After seconds, then retry
Quote expiredQUOTE_EXPIRED✅ YesRe-fetch a new quote, then retry the payment
Insufficient fundsINSUFFICIENT_FUNDS✅ After fixTop up balance, then retry
Validation errorINVALID_AMOUNT, INVALID_RECIPIENT✅ After fixCorrect the offending field(s), then retry
AuthenticationUNAUTHORIZED, API_KEY_REVOKED❌ NoFix credentials configuration; retrying will continue to fail
Compliance blockSANCTIONS_MATCH, KYC_REQUIRED❌ NoResolve compliance issue through the appropriate channel
Terminal payment statePAYMENT_CANCELLED, RECIPIENT_BANK_REJECTED❌ NoCreate a new payment; the original cannot be resumed
Corridor unavailableCORRIDOR_UNAVAILABLE⚠️ SometimesCheck status page; retry only after corridor is confirmed restored

Exponential Backoff Example

async function createPaymentWithRetry(payload, idempotencyKey, maxAttempts = 3) {
  const RETRYABLE_STATUSES = [500, 503];
  let delay = 1000; // start at 1 second

  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const response = await fetch("https://api.remitflex.com/v1/payments", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${process.env.REMITFLEX_API_KEY}`,
        "Idempotency-Key": idempotencyKey,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(payload),
    });

    if (response.ok) return response.json();

    const error = await response.json();

    if (!RETRYABLE_STATUSES.includes(response.status)) {
      // Permanent error — surface immediately, do not retry
      throw new Error(`Non-retryable error: ${error.error.code}`);
    }

    if (attempt === maxAttempts) {
      throw new Error(`Max retries exceeded. Last error: ${error.error.code}`);
    }

    console.warn(`Attempt ${attempt} failed (${error.error.code}). Retrying in ${delay}ms...`);
    await new Promise((resolve) => setTimeout(resolve, delay));
    delay *= 2; // exponential backoff
  }
}