Skip to main content
When a Payment API request fails, Quidkey returns a consistent JSON envelope and a meaningful HTTP status code. Use the status code and code for programmatic handling, and message for logging.

Error Envelope

Every error response has success: false and an error object:
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Human-readable error message",
    "metadata": {
      "errors": [
        {
          "field": "amount",
          "message": "Amount must be greater than 0"
        }
      ]
    }
  }
}
FieldDescription
error.codeStable, machine-readable error code. Branch on this in your code.
error.messageHuman-readable description. Use for logs and debugging, not for control flow.
error.metadata.errorsPresent on validation failures (400). An array of per-field problems.
Always branch on error.code, never on error.message. Messages may be reworded over time; codes are stable.

HTTP Status Codes

StatusMeaningNotes
200 / 201SuccessRequest processed; resource created on 201.
400VALIDATION_ERRORMalformed or invalid input. Field-level details in error.metadata.errors.
401NO_TOKEN / INVALID_TOKENMissing, malformed, or expired access token. Branch on the status, not the code.
403ForbiddenAuthenticated, but insufficient permission for this action.
404Not foundResource does not exist or belongs to another tenant.
409IDEMPOTENT_REQUEST_IN_PROGRESSA request with the same idempotency key is still in flight.
410GoneResource expired or revoked (e.g. PAYMENT_LINK_EXPIRED).
422UnprocessableRequest is well-formed but cannot be fulfilled.
500Internal errorServer-side issue (rare). Safe to retry idempotent requests.
503IDEMPOTENCY_STORE_UNAVAILABLEIdempotency store temporarily down. Retryable.

Validation Errors (400)

A 400 indicates the request body or parameters failed validation. The error.metadata.errors array pinpoints each offending field, so you can surface precise feedback.
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "metadata": {
      "errors": [
        {
          "field": "amount",
          "message": "Amount must be a positive integer in minor units"
        },
        {
          "field": "currency",
          "message": "currency must be a valid ISO 4217 code"
        }
      ]
    }
  }
}
Amounts are integer minor units: 1999 means £19.99, not £1,999. Sending a decimal or a major-unit value is a common source of 400 validation errors. See Amounts & Currencies.

Cross-Tenant Access Returns 404

If you request a resource that exists but belongs to another merchant, Quidkey returns 404, not 403.
This is deliberate. A 403 would confirm the resource exists, leaking information across tenants. Quidkey returns 404 so a resource you cannot access is indistinguishable from one that does not exist. Do not treat a 404 as proof a payment was never created.

Authentication & Permission Errors

Code / StatusMeaningResolution
401 (NO_TOKEN / INVALID_TOKEN)Missing or expired tokenRefresh your access token and retry. See Authentication.
403 ForbiddenToken lacks the required permissionVerify the credentials have access to this operation.
404 Not foundResource missing or cross-tenantCheck the ID; confirm it belongs to your merchant.

Error Codes

Branch on these stable error.code values:
Statuserror.codeMeaning
400VALIDATION_ERRORRequest body or parameters failed validation. Field-level details in error.metadata.errors.
409IDEMPOTENT_REQUEST_IN_PROGRESSA request with the same idempotency key is still in flight. Retry with the same key.
410PAYMENT_LINK_EXPIREDThe payment link has passed its expiry.
410PAYMENT_LINK_NOT_ACTIVEThe payment link is not in an active state.
422MISSING_CONVERTED_AMOUNTA required converted amount was not supplied.
503IDEMPOTENCY_STORE_UNAVAILABLEIdempotency store temporarily down. Retryable.

Handling Errors

const response = await fetch(url, options);
const body = await response.json();

if (!body.success) {
  const { code, message, metadata } = body.error;

  // A 401 (code NO_TOKEN or INVALID_TOKEN) means the token is missing or expired.
  if (response.status === 401) {
    // Refresh the access token and retry once
  } else switch (code) {
    case 'IDEMPOTENT_REQUEST_IN_PROGRESS':
      // Wait briefly, then retry with the SAME idempotency key
      break;
    case 'IDEMPOTENCY_STORE_UNAVAILABLE':
      // Retry with backoff; the request was not processed
      break;
    case 'VALIDATION_ERROR':
      // Surface field errors to the caller
      for (const e of metadata?.errors ?? []) console.error(e.field, e.message);
      break;
    default:
      console.error(code, message);
  }
}
Body abbreviated. See the Redirect guide for the full required payload.
Retry guidance: retry 409, 503, and 500 with exponential backoff. Do not blindly retry 400, 401, 403, 404, or 410; fix the request or credentials first.

Next Steps

Idempotency

Safe retries for create requests

Authentication

Resolve 401 errors with token refresh

Amounts & Currencies

Avoid the most common validation error

API Reference

Response format and conventions