One REST API runs everything in Lucra, and the Lucra app is built on it. Call it from your server with the SDK or plain HTTP, or connect an AI assistant over MCP.
Your first request
Create a key in Settings → Developers → API, then call GET /v1/account to see the account it belongs to:
curl https://api.onlucra.com/v1/account \
-H "Authorization: Bearer $LUCRA_API_KEY" \
-H "Lucra-Version: 2026-10-01"
Every request sends two headers:
Authorization: Bearer …with an API key or an OAuth access token. See Authentication.Lucra-Version, so your integration keeps working as the API changes. The SDK sends it for you.
The same call with the SDK:
import { Lucra } from "@lucra/sdk"
const lucra = new Lucra(process.env.LUCRA_API_KEY!)
const { data: account } = await lucra.account.retrieve()
IDs
Every ID starts with a prefix naming its resource, like prog_00lhuAJjQgdtNt5WnqTBc95 for a program. Treat the rest as opaque.
| Prefix | Resource |
|---|---|
acct_ | account |
ad_ | ad |
adauth_ | ad authorization |
adset_ | ad set |
agac_ | agreement acceptance |
agr_ | agreement |
appl_ | application |
camp_ | campaign |
conn_ | connection |
conv_ | conversion |
crtr_ | creator |
emb_ | embed |
ern_ | earning |
evt_ | webhook event |
file_ | file |
key_ | key |
msg_ | message |
pay_ | payment |
post_ | post |
pout_ | payout |
prod_ | product |
prog_ | program |
pxev_ | pixel event |
pxl_ | pixel |
ret_ | retainer |
smpl_ | sample |
sub_ | submission |
thr_ | thread |
txn_ | transaction |
wdr_ | withdrawal |
Objects
{
"id": "prog_00lhuAJjQgdtNt5WnqTBc95",
"name": "Spring launch",
"status": "open",
"version": 3,
"createdAt": "2026-10-01T15:04:05.000Z"
}
- A reference to another object is its bare ID, in a field named for it:
program,creator,account. - Money is an integer number of cents, next to a
currency. Rates are percents. - Times are UTC ISO 8601, in fields ending in
At. - Enums can gain values, so handle ones you don't recognize.
Lists
A list returns data and a nextCursor. Pass that cursor back as cursor to get the next page; it's null on the last one. limit sets the page size (default 25, up to 100).
{ "data": [{ "id": "prog_00lhuAJjQgdtNt5WnqTBc95" }], "nextCursor": "eyJpZCI6…" }
Filter by a field's name, with commas for several values, like status=pending,approved. Every list also takes createdAfter and createdBefore. The SDK's paginate walks every page for you.
Most objects aren't deleted. Set their status to archived instead; lists leave archived objects out unless you filter for them.
Updates
PATCH changes only the fields you send. To make sure nobody changed the object since you read it, send its version; if it's stale you get 409 version_conflict, and you read it again.
Idempotency
Send an Idempotency-Key header on a write you might retry. A retry with the same key returns the first response instead of writing twice. Writes that move money require one: payments, retainer offers and withdrawals.
Errors
A failed request returns a problem with a stable code to branch on, a readable detail, and a fix when there's a clear next step:
{
"status": 422,
"code": "invalid_request",
"detail": "type: expected one of \"organic\"|\"paid_ads\"",
"fix": "Fix `type` in the request body.",
"retryable": false,
"errors": [{ "field": "type", "message": "Expected one of \"organic\"|\"paid_ads\"" }]
}
| Status | Means |
|---|---|
| 401 | Missing, invalid or expired key or token |
| 403 | The key can't do this, or can't reach that account |
| 404 | Not found, or not visible to this key |
| 409 | A conflict, or a setup step left first (requirement_… codes) |
| 422 | Invalid request; its errors array lists every bad field |
| 429 | Rate limited; wait Retry-After seconds |
retryable says whether sending the same request again can succeed. Every problem also has a requestId; include it when you contact support.
OpenAPI
The full OpenAPI document is at https://api.onlucra.com/v1/openapi.json. Every endpoint, with its fields, is in All endpoints.