API reference

Last updated on May 16, 2026

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-list
  • POST /functions/v1/or-stealth-connection-delete
  • POST /functions/v1/or-stealth-envelope-fetch — retrieve a sealed envelope for browser decryption
  • POST /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.created
  • connection.deleted
  • sync.completed
  • sync.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