Reference
REST API
Base URL: your Proofline platform plus /api/v1, https://app.proofline.link/api/v1. JSON in, JSON
out. In the examples, $PROOFLINE_URL is your platform URL, https://app.proofline.link.
Authentication
Create a secret key in Settings → API keys. It is shown once and stored only as a SHA-256 hash.
curl "$PROOFLINE_URL/api/v1/creators" \
-H "Authorization: Bearer sk_live_…"
Keys have scopes. read reads creators, programs, links, codes, attributions, commissions and
receipts. write does everything read does, creates resources, reviews commissions
(POST /commissions/{id}/review) and reads payouts: payout batches are finance data, so even
GET /payouts needs a write key. Full access (*) is everything the API allows. A key without
the scope gets 403; a missing, invalid or revoked key gets 401.
Idempotency
Every POST requires an Idempotency-Key header (1–255 characters). Keys are scoped to the API key
that sends them: two API keys never share an answer, even with the same Idempotency-Key. The
first response for a key is stored and replayed for 24 hours, 4xx errors included, but 401 and
403 answers are never stored (nor are 5xx ones): once the API key or its scope is fixed, a retry
with the same Idempotency-Key runs. Reusing a key with a different body returns 409 conflict.
Bodies are JSON up to 256 KB; a larger one gets 413.
curl -X POST "$PROOFLINE_URL/api/v1/creators" \
-H "Authorization: Bearer sk_live_…" \
-H "Idempotency-Key: invite-sarah-2026-10-21" \
-H "Content-Type: application/json" \
-d '{"programId":"6f1c…","email":"sarah@example.com","name":"Sarah Lee","handle":"sarahmoves"}'
Pagination
List endpoints accept ?limit= (1–200, default 50) and ?cursor=, plus optional appId,
programId and creatorId filters (payout batches belong to the organization and take none of
these three), and return:
{ "data": [ … ], "nextCursor": "…" }
Errors
Every error has the same shape:
{ "error": { "code": "validation_failed", "message": "Request validation failed", "details": [ … ] } }
| Status | code |
When |
|---|---|---|
| 400 | bad_request |
Malformed JSON, or no Idempotency-Key |
| 401 | unauthorized |
Missing, invalid or revoked key |
| 403 | forbidden |
The key lacks the scope |
| 404 | not_found |
No such resource, or an unknown /api/v1 path |
| 405 | bad_request |
The route doesn’t take that method; Allow lists the ones it does |
| 409 | conflict |
Idempotency key reused, or a state conflict |
| 413 | bad_request |
Body over 256 KB |
| 422 | validation_failed |
Body or query failed validation (details) |
| 429 | rate_limited |
Over 600 requests a minute per key |
| 503 | feature_disabled, unavailable |
Not configured, or temporarily unavailable |
| 500 | internal |
Our fault |
Resources
| Resource | List | Get | Create |
|---|---|---|---|
creators |
GET /creators |
GET /creators/{id} |
POST /creators (invite) |
programs |
GET /programs |
GET /programs/{id} |
POST /programs |
links |
GET /links |
GET /links/{id} |
POST /links |
codes |
GET /codes |
GET /codes/{id} |
POST /codes |
attributions |
GET /attributions |
GET /attributions/{id} |
— |
commissions |
GET /commissions |
GET /commissions/{id} |
— |
payouts |
GET /payouts (batches) |
GET /payouts/{id} |
— |
receipts |
GET /receipts |
GET /receipts/{commissionId} |
— |
Filters
Besides the filters above, lists take status, and GET /commissions also takes kind. An
unknown value is a 422.
| List | status |
|---|---|
GET /creators |
invited, applied, active, rejected, removed |
GET /programs |
active, paused, archived |
GET /links |
active, disabled |
GET /codes |
active, exhausted, expired, revoked |
GET /attributions |
provisional, attributed, locked, void |
GET /commissions |
pending, approved, locked, paid, reversed, void |
GET /payouts |
draft, pending_approval, approved, funding, paid, failed, cancelled |
kind on commissions is earning, reversal or adjustment. GET /receipts takes no status.
Create bodies
- creators:
programId,email,name, optionalhandle,country,planOverrideId,sendEmail(defaulttrue) andactivate(defaultfalse). Withactivatethe membership startsactive, so you can issue links and codes at once. A creator who has already claimed their profile is invited instead: joining needs their consent, so they accept the invitation themselves. - programs:
appId,name, optionalcurrency(defaultUSD) andrules(see windows and holds). A new program has no commission plan yet, so it earns nothing until you add one under Programs → Commission plan in the dashboard; the API can’t add plans, and the Integration Doctor flags active programs without one. - links:
programId,creatorId, optionalslug,landing,destination. - codes:
programId,creatorId,store(apple,googleorinternal),code, optionalstoreOfferRef,offerLabel,redemptionLimit,expiresAt.
Reviewing commissions
POST /commissions/{id}/review (a write key and an Idempotency-Key) approves or voids a
commission held for review: an earning in pending (no autopay, contested, over the monthly cap,
estimated store money, a renewal Proofline could not place in its subscription) or a reinstated
refund. Body: decision (approve or void) and reason (3–500 characters). It answers
200 with { "data": { "commission": …, "changed": [ … ] } }; changed lists every row the
decision moved, such as linked partial-refund rows.
- approve: the commission becomes
approvedand locks when its hold ends.409 conflictwhile an open fraud flag holds it. - void: the commission and its unlocked linked rows become
void, the creator's balance is reduced andcommission.reversedis sent. Locked or paid commissions return409; correct them with an adjustment in the dashboard.
GET /receipts lists commission receipts, newest first, with the usual appId, programId,
creatorId filters and cursor paging.
Money fields are integer minor units with an ISO currency; rates are basis points.