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 |
Related #
- Webhooks — receive Stripe events and emit signed events.