Errors
Every error the Railbed API returns, with its HTTP status, what caused it and what to do next.
#The error object
Every error has an HTTP status of 400 or above and the same JSON body:
An error
{
"error": {
"code": "no_payout_wallet",
"message": "Add a payout wallet in the dashboard before taking live payments."
}
}
| Field | Type | Description |
|---|
code | string | Stable and machine-readable. Branch on this |
message | string | A sentence for people. It can be reworded at any time, so show it or log it, but never parse it |
field | string | The request field at fault, when there is one: amount, metadata, starting_after |
#Handling errors
Decide by status first, then by code where it matters:
| Status | Meaning | What your code should do |
|---|
400 | The request was invalid | Fix the request. Retrying it unchanged fails the same way |
401 | The key is missing, malformed or revoked | Check the key and its mode. Don't retry |
403 | Not allowed for this account or key | Don't retry |
404 | Not found in this account and mode | Check the id and whether the key's mode matches the object's |
409 | The object's state doesn't allow this | Read the object and act on its current status |
410 | The session can't be paid any more | Create a new session |
413, 415 | The body is too large or isn't JSON | Fix the request |
429 | Rate limited | Wait for Retry-After seconds, then retry |
500, 502, 503 | Something failed on our side or upstream | Retry with backoff. Creates are safe to retry with the same Idempotency-Key |
A small error handler
async function railbed(path, init = {}) {
const res = await fetch(`https://pay.railbed.io/v1${path}`, {
...init,
headers: {
Authorization: `Bearer ${process.env.RAILBED_SECRET_KEY}`,
'Content-Type': 'application/json',
...init.headers,
},
});
const body = await res.json();
if (res.ok) return body;
const err = Object.assign(new Error(body.error.message), {
status: res.status,
code: body.error.code,
field: body.error.field,
});
err.retryable = res.status === 429 || res.status >= 500;
err.retryAfter = Number(res.headers.get('Retry-After')) || null;
throw err;
}
#Every code
#400 Bad Request
| Code | Endpoint | Cause |
|---|
invalid_json | Any with a body | The body isn't a JSON object |
invalid_amount | Create | amount isn't a string, or isn't from "1.00" to "100000.00" with at most two decimal places |
invalid_currency | Create | currency isn't USD, EUR, GBP, CAD or AUD |
missing_description | Create | description is missing, blank or not a string |
too_long | Create | description or reference is over 120 characters. See field |
invalid_reference | Create | reference isn't a string |
invalid_email | Create, Start | customer_email isn't a valid address, or Start needs one and none is on the session |
invalid_url | Create | success_url or cancel_url isn't a full http(s):// address, has a username or password, is over 1,000 characters, or isn't https:// in Live mode |
invalid_metadata | Create | Over 20 keys, a blank key, a key over 40 characters or starting with __, or a value that isn't a string of at most 500 characters |
invalid_idempotency_key | Create | The Idempotency-Key header is blank, over 120 characters or has characters other than printable ASCII |
invalid_country | Start | country isn't a two-letter code such as US |
invalid_limit | List payments | limit isn't a whole number from 1 to 100 |
invalid_cursor | List payments | starting_after isn't one of your payments in this mode |
invalid_outcome | Simulate | outcome isn't paid, underpaid or failed |
#401 Unauthorized
| Code | Cause |
|---|
invalid_api_key | No Authorization: Bearer header, a malformed key, or a revoked one. Create a key in Developers |
#403 Forbidden
| Code | Cause |
|---|
suspended | The account is suspended. Contact support |
live_payment | Simulate was called on a Live payment. Only Test payments can be simulated |
#404 Not Found
| Code | Cause |
|---|
not_found | No such session or payment in your account in this key's mode, or no such endpoint. A Test key can't see Live objects and the other way round |
#409 Conflict
| Code | Endpoint | Cause |
|---|
idempotency_conflict | Create | The key was used with a different body. Rarely, the first request with the key hadn't finished saving: retry in a moment |
no_payout_wallet | Create | Live mode needs a payout wallet. Add one in Settings |
unavailable | Start | The account can't take payments now |
no_providers | Start | No card provider can take this amount and currency right now. Nothing was started; try later or a different amount |
already_paid | Start | The payment is complete |
held | Start | The money arrived and the payment is held for review |
failed | Start | The payment was declined. Create a new session |
not_started | Simulate | Start the session before simulating |
#410 Gone
| Code | Cause |
|---|
expired | The session passed its expires_at without being paid |
canceled | The payment link was canceled in the dashboard |
#Size, type and rate
| Status | Code | Cause |
|---|
| 413 | request_too_large | The body is over 64 KiB |
| 415 | unsupported_media_type | The body isn't sent as Content-Type: application/json |
| 429 | rate_limited | Over about 120 requests a minute, or 12 starts a minute. Wait for Retry-After |
#5xx
| Status | Code | Cause |
|---|
| 500 | internal | Something failed on our side. Retry with backoff; contact support if it continues |
| 502 | network_unavailable | The card network didn't answer while starting. Retry in a minute |
| 503 | misconfigured | Card payments are briefly unavailable. Retry later |
Updated · This page as Markdown