Core concepts

Errors

Every v1 error has the same body. Branch your code on the code, and show the message to an operator if you like.

The error body#

400 Bad Request
{
  "code": "invalid_request",
  "message": "Add at least one invoice item",
  "details": null
}
  • code is stable and machine-readable. Branch on it, never on message.
  • message is plain English and safe to show to an operator. Its wording can change.
  • details carries extra data for some codes, else null.

Error codes#

HTTPcodeMeaningRetry?
400invalid_requestValidation failed. The message says what, such as Add at least one invoice item.No. Fix the request.
400invalid_idempotency_keyIdempotency-Key is longer than 255 characters.No. Use a shorter key.
401unauthorizedNo API key, an unknown key or a revoked key.No. Fix or replace the key.
403insufficient_scopeThe key lacks the scope. details names it: { "required": "invoices:write" }.No. Ask the admin for a key with the scope.
404not_foundUnknown id, or an id that belongs to another organization.No.
404not_paidYou asked for the receipt of an unpaid invoice.Later, after invoice.paid.
409conflictA uniqueness rule failed, such as another customer having the same externalRef.No. Look up the existing record.
409idempotency_in_progressA request with the same Idempotency-Key is still running.Yes, after a short wait, with the same key.
409invoice_voidedCheckout: the invoice is voided.No.
409already_paidCheckout: the invoice is already paid.No.
409not_onlineCheckout: the invoice wasn't approved for online payment.No. The organization decides how the invoice is paid.
409payment_not_availableCheckout: online payment can't start, for example because the organization's payment settings aren't complete. The message explains.Not until the organization fixes its payment settings.
422idempotency_key_reusedThe same Idempotency-Key was sent with a different request.No. Use a new key for a different request.
429rate_limitedToo many requests for this key. Wait for the Retry-After seconds.Yes, after Retry-After.
502gateway_errorPaystack didn't start the payment. details holds the gateway's response.Yes, with backoff.
503links_not_configuredThe server has no link-signing key configured.No. Contact Flux.

Validation messages#

400 invalid_request covers every validation rule. The message says which one failed. Some you may see:

  • Add at least one invoice item
  • currency must be a valid ISO code, e.g. NGN
  • One or more invoice items reference an unknown service
  • Discount amount cannot be higher than invoice amount
  • Enter both the billing period start and end
  • Invalid customer
  • Invalid email address
  • First name and last name are required for an individual customer
  • Company name is required for a company customer
  • A customer can have at most 20 billing contacts
  • Only one billing contact can be primary
  • Webhook URLs must use https
  • Unknown event type 'invoice.sent'

Retrying#

  • Retry 429 after the Retry-After seconds, and 5xx responses, timeouts and network errors with exponential backoff and jitter.
  • Don't retry other 4xx responses unchanged. They fail the same way every time.
  • Retry creates with the same Idempotency-Key. Flux then either runs the request once or replays the first response. See Idempotency.
// Retries 429 (after Retry-After) and 5xx/network errors. Keep the same Idempotency-Key on retries.
async function fluxFetch(url, options = {}, attempts = 5) {
  for (let attempt = 1; ; attempt++) {
    let response;
    try {
      response = await fetch(url, options);
    } catch (networkError) {
      if (attempt >= attempts) throw networkError;
    }
    if (response && response.status < 500 && response.status !== 429) return response;
    if (attempt >= attempts) return response;
    const retryAfter = Number(response?.headers.get("Retry-After"));
    const delay = retryAfter > 0 ? retryAfter * 1000 : Math.min(30000, 500 * 2 ** attempt) * (0.5 + Math.random());
    await new Promise((resolve) => setTimeout(resolve, delay));
  }
}