API reference
REST endpoints, webhook events, normalized data shapes. Everything you need to read from OrangeRails programmatically.
OrangeRails is an Apache 2.0 connector hub. The hosted API lives at https://api.orangerails.com (which fronts the Supabase Edge Functions at https://lcdicqalreskibdfxkzb.supabase.co/functions/v1/*). Self-hosters point their clients at their own deployment.
Authentication
Two modes, picked by which header you send:
| Mode | Header | Who uses it |
|---|---|---|
| Platform | X-Platform-API-Key: <hex> + subaccount_id in body |
Integrating apps (V3, BitBooks Personal, third-party SaaS) |
| Direct | Authorization: Bearer <supabase-jwt> |
Consumer users signed in to orangerails.com/app |
Full breakdown of how the layers fit together: How authentication works (for integrators).
Endpoints
All endpoints are POST + JSON unless noted.
Catalog (no auth)
GET /functions/v1/or-providers
Returns the public manifest of every source OrangeRails supports. CDN-friendly, no auth.
{
"providers": [
{
"slug": "blink",
"displayName": "Blink",
"description": "Lightning + on-chain",
"status": "live",
"category": "lightning_wallet",
"tags": ["live", "lightning"],
"popularity": 80,
"multiWallet": true,
"credentialFields": [
{ "name": "api_key", "type": "secret", "label": "Blink API key" }
]
}
],
"categories": [
{
"slug": "exchange",
"displayName": "Exchanges",
"description": "Crypto exchanges with fiat on/off-ramps",
"providerCount": 98
}
]
}
Connections
POST /functions/v1/or-connection-create
Body: { subaccount_id, provider_type, encrypted_credentials, label? }. Returns connection_id.
The credential ciphertext is opaque to OR — the widget produces it client-side before this call.
POST /functions/v1/or-connection-list
Body: { subaccount_id }. Returns the user's active connections.
POST /functions/v1/or-connection-delete
Body: { subaccount_id, connection_id }. Soft-deletes; underlying transactions remain queryable for compliance until explicitly purged.
Sync + read
POST /functions/v1/or-sync
Body: { subaccount_id, connection_id? }. Fetches new transactions from upstream and persists them (encrypted). Idempotent on (connection_id, external_id).
POST /functions/v1/or-transactions-list
Body: { subaccount_id, connection_id?, since?, limit? }. Returns normalized transactions:
{
"transactions": [
{
"id": "uuid",
"external_id": "provider-side-id",
"adapter": "blink",
"direction": "in",
"type": "lightning",
"amount_sats": 21000,
"currency": "BTC",
"description": "Payment from alice",
"counterparty": "[email protected]",
"status": "settled",
"timestamp": "2026-05-14T18:32:00Z"
}
],
"next_cursor": null
}
Platform admin
POST /functions/v1/or-provision
Body: { external_user_id, label? }. Creates (or returns existing) subaccount for the given user identifier. Idempotent.
POST /functions/v1/or-platform-display
Body: {}. Returns the calling platform's display info (name, logo, allowed return URLs) for the widget header.
Stealth Sync endpoints (xpub blind mode)
For on-chain Bitcoin connections, OR uses Stealth Sync — the xpub never reaches the server.
POST /functions/v1/or-stealth-connection-create— store a sealed envelope (encrypted xpub)POST /functions/v1/or-stealth-connection-listPOST /functions/v1/or-stealth-connection-deletePOST /functions/v1/or-stealth-envelope-fetch— retrieve a sealed envelope for browser decryptionPOST /functions/v1/or-stealth-transactions-store— store sealed transaction bytes from browser-side BIP 158 filter matching
All these speak in opaque bytes. The server cannot decrypt any of them.
Errors
Every error is a JSON body with error (machine-readable code) and message (human-readable):
{ "error": "INVALID_PLATFORM_KEY", "message": "Platform API key not recognized" }
| HTTP | Error codes |
|---|---|
| 400 | MISSING_FIELD, INVALID_PROVIDER, INVALID_SUBACCOUNT_FORMAT |
| 401 | INVALID_PLATFORM_KEY, INVALID_JWT, EXPIRED_TOKEN |
| 403 | SUBACCOUNT_NOT_OWNED, RATE_LIMIT_EXCEEDED |
| 404 | SUBACCOUNT_NOT_FOUND, CONNECTION_NOT_FOUND |
| 502 | UPSTREAM_PROVIDER_ERROR |
Webhooks (planned, Phase 1)
Outbound webhook delivery is on the public roadmap for Phase 1 (α). Today, clients poll /or-transactions-list with a cursor. Webhook events under design:
connection.createdconnection.deletedsync.completedsync.failed
Rate limits
- Platform mode: 600 calls/minute per platform_id (steady), 100 burst.
- Direct mode: 60 calls/minute per user.
Rate limit headers on every response:
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 597
X-RateLimit-Reset: 1715800000
Exceeded → 429 with RATE_LIMIT_EXCEEDED.
SDK
Official TypeScript SDK is on the public roadmap for Phase 1. Until then, the API is plain HTTP + JSON — any HTTP client works.
A canonical Node.js example client lives at github.com/MorningRevolution/orangerails/tree/dev/examples.
Open spec
The OpenAPI 3.1 schema is auto-generated from the edge function code and published as part of this repo. Currently v0 (pre-1.0). Once 1.0 ships, the schema is versioned and additive changes only.
- OpenAPI spec →
github.com/MorningRevolution/orangerails/blob/dev/docs/openapi.yaml(TBD) - Protocol design discussion → OrangeRails Protocol