REFERENCE
Refrain API
A JSON API over HTTPS. Every endpoint lives under api.refrain.dev/v1, every response is JSON, and every request needs a key. The sections below run in the order you will need them.
Introduction
A project is the unit everything hangs off. Keys, pipelines, webhooks and usage are all scoped to one, and an organisation can hold as many as it needs. Nothing you do in one project is visible from another.
Versions move forward, never sideways. A v1 endpoint keeps its shape until the major version changes, and breaking changes are announced in the changelog with a removal date before they land.
Authentication
Added in v2.2.0
Creating a key
Open the project, go to Settings, then API keys. A key is shown once when it is created and never again, so store it before you close the dialog.
Sending the key
Send the key in the Authorization header as a bearer token. Keys sent in a query string are rejected, because query strings end up in server logs.
GET /v1/projects
curl
curl https://api.refrain.dev/v1/projects \
-H "Authorization: Bearer $REFRAIN_KEY"
{ "data": [ { "id": "prj_31a", "name": "Acme" } ] }
BREAKING IN v3
The X-Api-Token header is accepted until v3.0.0 and removed after it. Move to Authorization: Bearer before you upgrade.
Rotating without downtime
Create the replacement key first, deploy it, then revoke the old one. Two keys can be active at once, so there is no window where requests fail.
Your first request
List the projects the key can see. The response carries a data array and a cursor, and the cursor is null when there is nothing left to page through.
GET /v1/projects
curl
curl https://api.refrain.dev/v1/projects \
-H "Authorization: Bearer $REFRAIN_KEY"
{ "data": [ { "id": "prj_31a", "name": "Acme" } ], "cursor": null }
Pagination
Changed in v2.2.1
Every list endpoint is cursor based. Pass the cursor from the previous response as the starting_after parameter, and stop when the response returns a null cursor. Cursors are anchored to a monotonic sequence rather than an offset, so a record deleted mid-pagination cannot cause a row to be skipped.
Page size defaults to 50 and caps at 200. A batch write accepts up to 500 rows, and anything above that returns 413 with the accepted maximum in the body.
GET /v1/events
curl
curl "https://api.refrain.dev/v1/events?limit=50" \
-H "Authorization: Bearer $REFRAIN_KEY"
{ "data": [ ... ], "cursor": "evt_9f21c4" }
Rate limits
Limits are per project, not per key, so rotating a key does not reset a limit. Every response carries the remaining quota and the reset time in headers.
A batch of rows costs one unit of quota rather than one per row. When you exceed the limit the API returns 429 with a Retry-After header, and retrying before that time does not extend the block.
Webhooks
Register an endpoint per project and choose the events it receives. Deliveries are signed, retried on a backoff for 24 hours, and recorded for 90 days so you can see what was sent and what came back.
Verify the signature before you trust the body. Compare the timestamp and payload against the header using a constant time comparison, and reject anything older than five minutes.
POST /your-endpoint
node
const sig = req.headers["refrain-signature"]
const body = req.rawBody
if (!verify(sig, body, process.env.WEBHOOK_SECRET)) return res.status(400).end()
Errors
Errors return a status code, a stable machine readable code, and a message written for a person. The code is the part to branch on. The message is the part to log.
400 invalid request. 401 missing or bad key. 403 the key is valid but scoped elsewhere. 404 no such record. 413 the batch is too large. 429 rate limited. 500 our fault, and safe to retry.
Migrating to v3
v3 removes the X-Api-Token header. Send the key as Authorization: Bearer instead. Both headers are accepted until v3.0.0 and requests still using the old one return a Deprecation header carrying the removal date.
Nothing else in the request or response shape changes. If your client already sends the bearer header, there is no work to do.
Buy this template