RailbedDocs

API reference

The Railbed REST API. JSON over HTTPS, one secret key per server, idempotent creates, cursor pagination and plain error codes.

Base URL

Base URL
https://pay.railbed.io/v1

Every request uses HTTPS. The API has two resources: checkout sessions, which you create for each order, and payments, which you read to fulfil them. A session and its payment share one id (pay_…).

EndpointWhat it does
POST /v1/checkout_sessionsCreate a checkout session
GET /v1/checkout_sessions/:idRetrieve a checkout session
POST /v1/checkout_sessions/:id/startStart a checkout session and get its card providers
GET /v1/payments/:idRetrieve a payment
GET /v1/paymentsList payments
POST /v1/payments/:id/simulateSimulate a payment (Test mode)

Authentication

Send your secret key as a bearer token on every request. Keys are created and revoked in Developers, and each is shown once, when it's created.

An authenticated request
curl "https://pay.railbed.io/v1/payments?limit=1" \
  -H "Authorization: Bearer rb_test_…"

The key decides everything about the request: which account it belongs to and whether it works on Test or Live payments. rb_test_… keys see only Test data and rb_live_… keys only Live data; nothing else can switch the mode. A missing, malformed or revoked key gets 401 invalid_api_key; a suspended account gets 403 suspended.

Requests and responses

  • Send JSON bodies with Content-Type: application/json, up to 64 KiB. Other types get 415, larger bodies 413, and bodies that aren't a JSON object 400 invalid_json.
  • Responses are JSON and are never cached (Cache-Control: no-store).
  • Unknown fields in a request are ignored. Build against the fields documented here.
  • Money is a decimal string, never a number, so no amount is ever rounded in transit. Prices have two places ("49.00"); settlement amounts in the coin can have more (merchant_received has six).
  • Currencies are USD, EUR, GBP, CAD and AUD.
  • Timestamps in the API are Unix seconds. (A webhook event's created is Unix seconds too, but the payment inside it uses milliseconds; see event payloads.)
  • Ids are prefixed: pay_ for sessions and payments, evt_ for webhook events.

Idempotency

Networks fail. To retry a create safely, send an Idempotency-Key header: a value unique to the order attempt, 1–120 printable ASCII characters. Save it with your order before the first call.

SituationResponse
First request with a key201 and the new session
Same key, same body (a retry)200 and the same session, with its current status
Same key, different body409 idempotency_conflict. Use a new key for a different order
Same key and body while the first request is still runningThe same session: one request gets 201, the others 200. Rarely, 409 idempotency_conflict asks you to retry in a moment
Key longer than 120 characters, blank, or with other characters400 invalid_idempotency_key (never truncated)

Keys are scoped to your account and the key's mode, and remembered for 24 hours; after that the same key creates a new session. Metadata key order doesn't matter when comparing bodies. Without the header, every create makes a new session, and your reference alone doesn't prevent duplicates.

Pagination

GET /v1/payments returns the newest payments first, up to limit (1–100, default 20) at a time:

A page
{
  "data": [{ "id": "pay_…", "object": "payment", "status": "paid" }],
  "has_more": true,
  "next_cursor": "pay_Q3cNtwPT0bRqGmS5eZkB"
}
200 OK

Pass next_cursor as starting_after to get the next page, until next_cursor is null. The order is stable, even for payments created in the same second. A limit outside 1–100, or a cursor that isn't one of your payments in this mode, gets 400 rather than a silent first page.

Rate limits

Each account can make about 120 requests a minute across all its keys and modes, and 12 session starts a minute. Over the limit, requests get 429 rate_limited with a Retry-After: 60 header. Queue requests on your server and back off with a little randomness; never make a request per animation frame or per page view.

The limits protect the service rather than set a quota: they're enforced per region and are approximate, so plan well below them.

Errors

Errors use HTTP status codes and a JSON body with a stable code, a sentence for people, and sometimes the field at fault:

An error
{
  "error": {
    "code": "invalid_amount",
    "message": "amount must be a decimal string between \"1.00\" and \"100000.00\".",
    "field": "amount"
  }
}
400 Bad Request

Branch on code and the status, never on message, which may be reworded. Errors lists every code.

Versioning

The API is v1. Changes within v1 only add things: new fields in responses, new optional request fields, new error codes, new webhook event types. Write your integration to ignore fields and event types it doesn't know. A change that could break an integration would come as a new version.

Updated · This page as Markdown