API

Read your team's data from your own code, over HTTPS, with JSON responses, and get its events as signed webhooks.

Authentication

Create an API key in your team's settings, under API keys, and send it as a bearer token. A key belongs to one team, has scopes, can expire, and can be revoked at any time. It's shown only once, so store it somewhere safe.

curl https://app.farinu.com/api/v1/members \
  -H "Authorization: Bearer $API_KEY"

Endpoints

GET /api/v1/members
The team's members. Needs a key with the read scope.
GET /api/v1/audit-log
The team's audit log. Needs a key with the read scope, on a plan that includes the audit log.
POST /api/v1/webhooks/:id/test
Sends a test event to one of the team's webhook endpoints and returns its delivery's ID. Needs a key with the write scope, on a plan that includes webhooks.
GET /api/v1/assistant/instructions
The team's instructions to the assistant, null when it set none. Needs a key with the read scope.

Every endpoint, parameter, and response is described in the OpenAPI 3.1 document: openapi.json

Pagination

Lists return a page of items, oldest first, with the cursor to continue from and whether more items are waiting.

limit
How many items to return, from 1 to 100. The default is 50.
after
A page's next cursor, to get the items that follow it. Keep polling with the last one to get new items as they arrive.
{ "data": [ … ], "next": "1767225600000.…", "hasMore": true }

Versioning

The API is versioned by date. Send an API-Version header to pin one; without it you get the current version. Every response names its version in API-Version, and an unknown version is a 400.

Changes that could break your code (removing or renaming a field, changing a type or an error) ship under a new date. Additions, such as new fields, endpoints, or events, come to every version. An older version keeps working for at least 12 months after a newer one ships.

curl https://app.farinu.com/api/v1/members \
  -H "Authorization: Bearer $API_KEY" \
  -H "API-Version: 2026-10-06"

Idempotency

Send an Idempotency-Key header (up to 255 printable characters, such as a UUID) with a POST to retry it safely. For 24 hours the same key and request return the first response again, with Idempotent-Replayed: true, without running it twice; the same key with a different request is a 422, and one sent while the first is still running a 409.

curl -X POST https://app.farinu.com/api/v1/webhooks/$ENDPOINT_ID/test \
  -H "Authorization: Bearer $API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

Webhooks

Owners and admins add HTTPS endpoints in the team's settings, under Webhooks, and choose the events to send. Each event is a POST of JSON with its type, time, and data, signed the Standard Webhooks way. Answer with any 2xx within 15 seconds; anything else is retried after 1, 5, 25, 125, and 625 minutes, and an endpoint whose deliveries keep failing is turned off and its admins emailed.

POST /your/endpoint
webhook-id: whd_…
webhook-timestamp: 1767225600
webhook-signature: v1,…

{ "type": "member.added", "timestamp": "2026-01-01T00:00:00.000Z", "data": { "orgId": "…", "memberId": "…", "userId": "…", "role": "member" } }

To check a request, compute the base64 HMAC-SHA256 of webhook-id, webhook-timestamp, and the raw body joined by dots, keyed with the bytes of the secret after whsec_ (base64), and compare it in constant time with the value after v1, in webhook-signature. Refuse a timestamp more than 5 minutes off, and skip a webhook-id you already handled.

Events: member.added, member.removed, subscription.changed, file.uploaded, and webhook.test, plus the product's own. The OpenAPI document describes each body.

Errors

An error returns its status code and a JSON body with a code to check and a message to show.

{ "error": { "code": "forbidden", "message": "…" } }
400 badRequest
A query parameter is invalid.
401 unauthorized
The key is missing, wrong, revoked, or expired.
402 upgradeRequired
The team's plan doesn't include the API or this endpoint.
403 forbidden
The key doesn't have the scope this endpoint needs.
409 conflict
The resource is in a state that doesn't allow this, or a request with the same Idempotency-Key is still running.
422 idempotencyMismatch
The Idempotency-Key was already used for a different request.
429 rateLimited
Too many requests. Each key can make 100 a minute; wait the seconds in the Retry-After header.