REST API reference and scopes
The OpenPromoter REST API v1: authentication with API keys and scopes, rate limits and every endpoint. Copy the requests and start building.
The REST API covers programs, partners, tracking, commissions, payouts, campaigns and Autopilot. The base URL is https://app.openpromoter.com/api/v1 or <APP_URL>/api/v1 on your own instance.
The machine-readable spec is at /api/openapi.json (OpenAPI 3.1). Import it into Postman, Insomnia or a client generator.
Authentication
Create a key in Settings > API & webhooks. The full key is shown once; OpenPromoter stores only a SHA-256 hash. Send it as a Bearer token:
curl https://app.openpromoter.com/api/v1/programs \
-H "Authorization: Bearer $OPENPROMOTER_API_KEY"
A key belongs to one workspace and can be revoked at any time.
Scopes
| Scope | Allows |
|---|---|
read |
Read everything |
partners:write |
Create and update partners, links and codes; mint embed tokens; create campaigns; apply Autopilot suggestions |
tracking:write |
Record clicks, leads, conversions and refunds |
commissions:write |
Create commissions and bonuses |
payouts:request |
Prepare payouts. A person still approves them in the dashboard |
No scope can approve or send a payout.
Rate limits
600 requests per minute per key. Over the limit the API returns 429 with code rate_limited.
Errors
Errors return an HTTP status and a JSON body like { "error": { "code": "not_found", "message": "Program not found" } }. Validation errors also include issues (a list of { path, message }).
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request |
The body or query failed validation, or the action isn't allowed |
| 401 | unauthorized |
Missing or invalid API key |
| 403 | forbidden |
The key lacks the scope for this endpoint |
| 404 | not_found |
The program, partner or code does not exist in this workspace |
| 409 | conflict |
A unique value, such as a link key, is already taken |
| 422 | unparseable |
A campaign sentence could not be turned into a campaign |
| 429 | rate_limited |
Too many requests |
Pagination
List endpoints return { "data": [...], "hasMore": true }. Pass limit (1 to 100, default 50) and before=<id of the last item> for the next page.
Money
Amounts are integers in minor units (cents for USD) with a three-letter currency. Conversion amounts exclude tax.
Endpoints
| Method | Path | Scope | Description |
|---|---|---|---|
GET |
/events |
read |
Events (same payloads as webhooks) after a cursor with the oldest first. Used by openpromoter listen. Query: after (event id), limit (1 to 100, default 50) |
GET |
/programs |
read |
List programs in the workspace |
GET |
/programs/{programId}/stats |
read |
Totals for a period (revenue, commissions, clicks, signups, customers, MRR, owed). Query: days (1 to 730, default 30) |
GET |
/programs/{programId}/partners |
read |
List partners with the newest first. Query: status (pending, approved, rejected, paused, banned), recruitedBy (a partner id: only the partners they recruited) |
POST |
/programs/{programId}/partners |
partners:write |
Invite (create and approve) a partner. Body: email, name, website?, country?, recruiterCode? (two-tier: the code of the approved partner who recruited them) |
GET |
/partners/{partnerId} |
read |
Get a partner with links |
PATCH |
/partners/{partnerId} |
partners:write |
Change a partner's status or tier. Body: status?, groupId? |
POST |
/partners/{partnerId}/links |
partners:write |
Create a short or deep link for a partner. Body: destination, subId?, key? |
POST |
/programs/{programId}/embed-tokens |
partners:write |
Mint a 1-hour token for the embeddable partner dashboard. Creates (and approves) the partner if needed so customers can refer from inside your app. Body: email, name |
GET |
/programs/{programId}/referrals |
read |
List referred signups and customers. Query: status (lead, trialing, active, canceled, refunded) |
POST |
/clicks |
tracking:write |
Record a click server-side (e.g. from your own redirect). Body: programId, code, url?, visitorId?, ip?, userAgent? |
POST |
/leads |
tracking:write |
Record a signup (lead) before payment. Body: programId, email, name?, clickId?, visitorId?, partnerCode?, customerId? |
POST |
/conversions |
tracking:write |
Record a payment from a billing system without a built-in integration. Idempotent on paymentId |
POST |
/refunds |
tracking:write |
Report a refund for a payment sent to /conversions. Body: programId, paymentId, refundId, refundedTotal, chargedTotal |
GET |
/programs/{programId}/commissions |
read |
List commissions (the ledger). Query: status (pending, approved, held, paid, reversed), kind (sale, lead, bounty, bonus, adjustment, override), partnerId |
POST |
/programs/{programId}/commissions |
commissions:write |
Add a manual commission or bonus (approved immediately). Idempotent with idempotencyKey. Body: partnerId, amount, description |
GET |
/programs/{programId}/payouts |
read |
List payouts. Query: status (awaiting_approval, processing, paid, failed, canceled) |
POST |
/programs/{programId}/payouts |
payouts:request |
Prepare payouts for partners over the minimum. They wait for a person to approve them in the dashboard; no money moves. Body: partnerIds? |
GET |
/programs/{programId}/campaigns |
read |
List campaigns |
POST |
/programs/{programId}/campaigns |
partners:write |
Create a campaign from one sentence (e.g. "double commissions for annual plans this month"). Body: prompt, launch (default false) |
GET |
/programs/{programId}/autopilot |
read |
Autopilot suggestions and recent actions |
POST |
/programs/{programId}/autopilot/{actionId}/apply |
partners:write |
Apply an Autopilot suggestion (payout suggestions only prepare a batch) |
POST |
/programs/{programId}/ask |
read |
Ask a plain-language question about the program. Body: question |
Recording a conversion
curl https://app.openpromoter.com/api/v1/conversions \
-H "Authorization: Bearer $OPENPROMOTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"programId": "prg_123",
"paymentId": "inv_981",
"customerId": "cus_42",
"amount": 4900,
"currency": "usd",
"interval": "month",
"email": "jo@example.com",
"clickId": "clk_8f2k"
}'
| Field | Type | Notes |
|---|---|---|
programId |
string | Required |
paymentId |
string | Required. Your unique id for this payment; sending it twice is safe |
customerId |
string | Required. Your customer id; renewals with the same id stay with the same partner |
amount |
integer | Required. Minor units excluding tax |
currency |
string | Required. Three letters such as usd |
interval |
month or year |
Optional. Marks the payment as recurring |
email |
string | Optional. Matches an earlier lead |
clickId |
string | Optional. window.openpromoter.clickId |
partnerCode |
string | Optional. Credit a partner by code |
promoCode |
string | Optional. A partner coupon code used at checkout |
paidAt |
ISO 8601 | Optional. Defaults to now |
The response is { "duplicate": false, "effects": [...] }. effects lists what changed, such as the commission that was booked.
SDK
The TypeScript SDK wraps every endpoint with types.