Ledgerline uses conventional HTTP status codes to say whether a request worked, and a consistent JSON body to say why it did not. Branch your code on the type and code, never on the message, which can change.
The error object
Every error response has the same shape:
{
"error": {
"type": "invalid_request_error",
"code": "entries_unbalanced",
"message": "Debits (480000) and credits (450000) in USD must be equal.",
"param": "entries",
"request_id": "req_01JA9G3RWB",
"doc_url": "https://docs.ledgerline.dev/errors/entries_unbalanced"
}
}
| Field |
Description |
type |
The broad category. Stable and safe to switch on. |
code |
A specific, stable machine-readable reason. |
message |
A human-readable explanation. For logs and developers, not end users. |
param |
The request field at fault, when one applies. |
request_id |
The id of the request, also in the Ledgerline-Request-Id header. Include it in any report. |
doc_url |
A link to the documentation for the code. |
HTTP status codes
| Status |
Meaning |
Typical type |
200 |
OK |
|
201 |
Created |
|
202 |
Accepted for asynchronous processing |
|
400 |
The request is malformed |
invalid_request_error |
401 |
Authentication failed |
authentication_error |
403 |
Authenticated, but not allowed |
permission_error, compliance_error |
404 |
The resource does not exist |
not_found_error |
409 |
Conflicts with current state |
conflict_error |
422 |
Well-formed, but cannot be processed |
invalid_request_error |
429 |
Rate limit exceeded |
rate_limit_error |
500 |
Ledgerline had an internal error |
api_error |
502, 503, 504 |
Temporary upstream or availability problem |
api_error |
Ledgerline does not use 3xx responses for API calls and does not return 5xx for anything caused by your input.
Error types
| Type |
Meaning |
invalid_request_error |
The request or its parameters are wrong. Fix the request. |
authentication_error |
The key is missing, invalid, revoked or expired. |
permission_error |
The key is valid but lacks scope or is outside its IP allowlist. |
not_found_error |
The id does not exist in this environment. |
conflict_error |
The request conflicts with existing state or an in-flight request. |
rate_limit_error |
You are sending requests too fast. |
compliance_error |
A verification or screening rule blocked the action. |
api_error |
Something went wrong on our side. Retry. |
Error codes
Authentication and permissions
| Code |
Status |
Meaning |
api_key_invalid |
401 |
The key is missing or malformed. |
api_key_revoked |
401 |
The key was revoked. |
api_key_expired |
401 |
The key passed its expires_at. |
scope_missing |
403 |
A restricted key lacks the required scope. |
ip_not_allowed |
403 |
The caller's IP is outside the key's allowlist. |
permission_denied |
403 |
The operation needs a secret key or the dashboard. |
Ledger and transactions
| Code |
Status |
Meaning |
entries_unbalanced |
422 |
Debits and credits are not equal in some currency. |
insufficient_funds |
422 |
The available balance is too low. |
account_frozen |
422 |
The account is frozen. |
currency_mismatch |
422 |
An entry's currency differs from its account's. |
period_closed |
422 |
effective_at is more than 90 days in the past. |
resource_already_reversed |
409 |
The transaction was already reversed. |
Money movement and compliance
| Code |
Status |
Meaning |
counterparty_unverified |
422 |
The bank account has not been verified. |
limit_exceeded |
422 |
A per-payout or daily limit would be exceeded. |
rail_unavailable_for_recipient |
422 |
The receiving bank does not support the rail. |
kyb_required |
403 |
Business verification is not approved. |
sanctions_hit |
403 |
Screening matched a restricted party. |
fx_market_closed |
422 |
Quotes are unavailable while FX markets are closed. |
quote_expired |
422 |
The FX quote is older than 30 seconds. |
Requests and rates
| Code |
Status |
Meaning |
parameter_missing |
400 |
A required field is absent. |
parameter_invalid |
400 |
A field has the wrong type or value. |
resource_not_found |
404 |
No such id. Check you are using the right environment. |
idempotency_conflict |
409 |
The first request with this key is still running. |
idempotency_key_mismatch |
422 |
The key was reused with different parameters. |
rate_limit_exceeded |
429 |
Over the limit. Honor Retry-After. |
internal_error |
500 |
An unexpected error on our side. |
service_unavailable |
503 |
Temporarily unavailable. Retry with backoff. |
Handling errors in code
try {
await ll.payouts.create(params);
} catch (error) {
if (error instanceof LedgerlineError) {
switch (error.code) {
case "insufficient_funds":
return notifyTreasury(error.requestId);
case "counterparty_unverified":
return startVerification(params.counterpartyBankAccountId);
default:
throw error;
}
}
throw error;
}
Which errors to retry is covered in Retries and backoff. Practical fixes for the common ones are in Troubleshooting common errors.
Reporting a problem
When you open a support ticket, include the request_id. It lets us find the exact request and the logs around it, which are kept for 30 days.