API & authentication

Everything you can do in the dashboard is backed by a REST API under /v1. The dashboard is just one client; your own services are first-class callers too.

Authentication #

Authenticate every request with an API key, sent as a bearer token:

Authorization: Bearer sk_test_...

Each key carries exactly the scopes (read and/or write) you grant it when you create it.

API keys #

Create and manage keys in the dashboard Settings screen, or over the API:

GET    /v1/api-keys           # list (secrets never shown again)
POST   /v1/api-keys           # create; returns the secret ONCE
DELETE /v1/api-keys/:id       # revoke (key stops working immediately)

Create a key:

curl -X POST https://your-host/v1/api-keys \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "ci-pipeline", "scopes": ["read", "write"] }'

The plaintext secret is returned only in the create response — store it securely. Afterward the API exposes just the key’s prefix and metadata. Revoking a key takes effect immediately; revoked keys can later be purged from the list with DELETE /v1/api-keys/:id?purge=true.

Use a key by sending it as a bearer token:

curl https://your-host/v1/webhook-endpoints \
  -H "Authorization: Bearer sk_test_..."

Conventions #

Error envelope #

Errors return a consistent shape with a stable machine-readable code:

{ "error": { "code": "not_found", "message": "Resource not found", "details": null } }

Common codes: bad_request (400), unauthorized (401), forbidden (403), not_found (404), conflict (409), rate_limited (429).

Idempotency #

Send an Idempotency-Key header on mutating requests. The first request with a given key runs normally; a replay with the same key returns the original stored response (flagged with an Idempotent-Replayed: true header) instead of executing again. A request that arrives while the first is still in flight gets a 409 conflict.

curl -X POST https://your-host/v1/api-keys \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: 1f9c0b6e-..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "ci-pipeline", "scopes": ["read"] }'

Pagination #

List endpoints use keyset (cursor) pagination. Each response is an envelope:

{
  "data": [ /* … up to `limit` items … */ ],
  "page": { "nextCursor": "eyJ…", "hasMore": true }
}

Pass ?limit= (1–100, default 25) for the page size. To fetch the next page, pass the returned page.nextCursor back as ?cursor=; keep going until page.hasMore is false (at which point nextCursor is null). Cursors are opaque and seek by key, so rows inserted between requests never shift, duplicate, or skip. Applies to /v1/sync-runs, /v1/sync-runs/{id}/records, /v1/webhook-deliveries, /v1/webhook-endpoints, and /v1/api-keys.

Rate limiting #

The /v1 API is rate-limited per organization with a token bucket. When you exceed it you get 429 rate_limited with a Retry-After header; successful responses include X-RateLimit-Limit and X-RateLimit-Remaining.

API reference #

The full machine-readable spec and an interactive explorer ship with the app:

Resource Location
OpenAPI spec /v1/openapi.json
Swagger UI /v1/docs
  • Webhooks — receive Stripe events and emit signed events.