Errors
Error shape
Section titled “Error shape”Every error response has the same envelope:
{ "error": { "code": "VALIDATION_ERROR", "message": "amount must be at least 10000 for virtual_account" }}code is stable and machine-readable — write your error handling against
it, not against message or the HTTP status alone. message is meant for
you as a developer (safe to log), not for display to your customer.
Status and code reference
Section titled “Status and code reference”| Status | Code | Meaning |
|---|---|---|
400 |
VALIDATION_ERROR |
The request body is well-formed but fails a business rule (e.g. amount below the method’s minimum) |
400 |
INVALID_REQUEST |
The request body isn’t valid JSON, or doesn’t match the expected shape |
400 |
IDEMPOTENCY_KEY_REQUIRED |
This endpoint requires an Idempotency-Key header |
401 |
UNAUTHORIZED |
The Authorization header is missing, or the API key is invalid or revoked |
403 |
FORBIDDEN |
The key is valid but isn’t allowed to perform this operation (e.g. some dashboard-only reads aren’t available to API keys) |
403 |
PROJECT_UNAVAILABLE |
The project exists but isn’t usable right now (inactive, or in production and not yet approved) |
403 |
CORS_ORIGIN_FORBIDDEN |
The request’s browser Origin isn’t on the project’s allowed list |
404 |
PROJECT_NOT_FOUND |
The project doesn’t exist, or belongs to a different API key — see Authentication |
404 |
RESOURCE_NOT_FOUND |
The requested payment link, payment, or other resource doesn’t exist |
409 |
CONFLICT |
A resource with the same unique value already exists |
409 |
IDEMPOTENCY_CONFLICT |
The Idempotency-Key was already used with a different request body |
409 |
PAYMENT_LINK_NOT_ACTIVE |
The payment link is no longer accepting payments |
409 |
PAYMENT_NOT_EDITABLE |
This transaction can no longer accept a new payment attempt |
429 |
RATE_LIMITED |
Too many requests — back off and retry |
500 |
INTERNAL_ERROR |
Unexpected server error. Safe to retry |
503 |
SERVICE_UNAVAILABLE |
A dependency (e.g. the database) is temporarily unavailable |
503 |
AUTH_UNAVAILABLE |
Authentication is temporarily unavailable |
503 |
PAYMENT_PROVIDER_UNAVAILABLE |
The upstream bank/QRIS provider is temporarily unavailable |
Idempotency
Section titled “Idempotency”POST /v1/payments requires an Idempotency-Key header. Retrying the same
key with an identical body returns the original resource (not a duplicate);
retrying with a different body returns 409 IDEMPOTENCY_CONFLICT. This
makes retries on a network timeout safe — see the
Direct Payments guide.
Retrying
Section titled “Retrying”500, 503, and 429 are the only codes worth retrying automatically —
back off between attempts. Every other code means the request itself needs
to change before retrying will help.