The Server API
Manage partners, programs, enrolments, links and codes from your own backend, and read commissions and payouts, with a secret key. For a developer keeping a merchant's own systems in step with Refficks.
Authentication
- Base URL:
https://go.refficks.com/api/v1/server - Header:
Authorization: Bearer <SECRET_KEY>, a secret key (rk_live_…) from Settings › Developer › API keys. See Create and manage API keys. - What the key may do: a key made on Settings › Developer › API keys
may use every endpoint below. A key made for Zapier, Make, n8n or Pabbly Connect may not
approve applicants, change a partner's lifecycle stage, create or change
programs, choose a tier, or suspend a membership: those requests are
403 FORBIDDENwitherror.details.abilitynaming what was missing. It also may not archive or restore a partner, switch a link or code on or off, or claim a Stripe promotion for a code: those are403 FORBIDDENwith a sentence such as "A key made for Zapier cannot archive a partner. That is for a person to decide, in Refficks or with a secret key." - A publishable key is refused here, whatever it is used for.
- Your workspace comes from the key. No endpoint takes an organization id;
sending
organization_idis refused with "The organization is determined by your credentials and cannot be set on a request." An id that belongs to another workspace is answered404, exactly as one that does not exist.
Two older endpoints take the same key and are not under /server:
POST /api/v1/events (Send events from your server)
and POST /api/v1/referral-tokens, which mints the token the referral widget
needs (Subscriptions, coupon codes and referral tokens).
What it covers
| Area | Endpoints |
|---|---|
| The key | GET /me: which workspace the key belongs to, its name and what it may do |
| Partners | GET /partners, GET /partners/lookup, POST /partners, GET /partners/{id}, PATCH /partners/{id}, POST /partners/{id}/transition, POST /partners/{id}/archive, POST /partners/{id}/restore |
| Programs | GET /programs, POST /programs, GET /programs/{id}, PATCH /programs/{id} |
| Enrolments | GET /programs/{id}/partners, POST /programs/{id}/partners, PATCH /programs/{id}/partners/{membership} (status only) |
| Commission plans | GET /programs/{id}/commission-plans, GET /programs/{id}/commission-plans/{plan} (read only) |
| Tracking links | GET /tracking-links, POST /tracking-links, GET /tracking-links/{id}, PATCH /tracking-links/{id} |
| Coupon codes | GET /coupon-codes, POST /coupon-codes, GET /coupon-codes/{id}, PATCH /coupon-codes/{id}. See Coupon codes over the Server API. |
| Commissions | GET /commissions, GET /commissions/{id} (read only) |
| Payouts | GET /payouts, GET /payouts/{id}, GET /payouts/{id}/entries (read only) |
| Webhook subscriptions | GET /hooks, GET /hooks/events, GET /hooks/samples, POST /hooks, GET /hooks/{id}, DELETE /hooks/{id}. See Receive Refficks events with webhooks. |
What it never does
No key can do these, because no endpoint exists for them. A person does them in the dashboard, and History records who.
- Approve, reject, reverse or recalculate a commission.
- Create, approve, send, mark paid or void a payout. Refficks moves no money:
status: paidon a payout means the merchant recorded that they sent it. - Write a commission plan, a partner's own rate, or a partner's tier after
enrolment. Writing to a commission plan's address is answered
405. - Erase a partner. Archive them instead; their conversions, commissions and payouts stay attached.
- Return a partner's scores (
score,potential_score,risk_score), or a partner's payout details beyond the maskedpayment_hinton a payout. - Create a discount in your payment provider. A coupon code here is Refficks's record of which partner a code belongs to.
Conventions
Envelope. One item is { "data": { … } }. A list is:
{
"data": [ { "object": "partner", "id": "01jc…" } ],
"meta": { "total": 137, "per_page": 25, "current_page": 1, "last_page": 6 },
"links": { "next": "https://go.refficks.com/api/v1/server/partners?page=2", "prev": null }
}
Every object carries object, naming what it is. Ids are 26-character ULIDs,
written in lower case.
Every field is always present. A field with nothing to say is null, never
missing.
Money is an integer in the currency's minor unit, with the currency beside
it: "amount": 1425, "currency": "GBP" is £14.25. Amounts in different
currencies are never added together, so the commission list has no total.
Pagination is by page on every list: ?page= (from 1) and ?per_page=
(default 25, at most 100). Lists are oldest first, so a page you have read
stays read while new records are added. Follow links.next until it is
null.
Filtering is by query parameter, per list:
| List | Filters |
|---|---|
GET /partners |
status, lifecycle_stage, email |
GET /programs |
status, module |
GET /programs/{id}/partners |
status, partner_id |
GET /tracking-links, GET /coupon-codes |
partner_id, program_id, status, code |
GET /commissions |
partner_id, program_id, payout_id, status, type, currency, occurred_from, occurred_to |
GET /payouts |
partner_id, status, currency |
occurred_from and occurred_to are dates and filter on when the sale
happened, not when it was recorded.
Refused rather than ignored. A field you may not set is answered 422
with a message saying where it is set, never silently dropped. For example,
status on POST /partners ("A partner cannot be created with a status.
Create them, then archive them if that is what you mean."), and tier_id on a
membership PATCH.
History. A change made through this API appears in Settings › Developer › History under the key's name, for example "Your “Production server” API key".
Rate limit: 300 requests a minute, counted separately from
POST /events. See API errors and limits
for how it is counted today.
Worked examples
Create a partner
curl -X POST https://go.refficks.com/api/v1/server/partners \
-H "Authorization: Bearer <SECRET_KEY>" \
-H "Content-Type: application/json" \
-d '{
"email": "sam@example.com",
"first_name": "Sam",
"last_name": "Rivera",
"country": "us",
"tags": ["newsletter"]
}'
Responds: 201 with the partner (shortened here):
{
"data": {
"object": "partner",
"id": "01jc9a7k…",
"status": "active",
"lifecycle_stage": "approved",
"email": "sam@example.com",
"first_name": "Sam",
"last_name": "Rivera",
"country": "US",
"currency": "USD",
"roles": ["affiliate"],
"tags": ["newsletter"],
"created_at": "2026-10-16T09:30:00+00:00"
}
}
Worth knowing before a first sync:
- No portal invitation is sent unless you send
"send_portal_invitation": true. Creating an approved partner still counts as "Partner approved", so your own automation rules and webhooks for that event run. - Approved or applicant, nothing else. With a full key the partner arrives
approved. Send
"lifecycle_stage": "applicant"to make them an applicant. A key made for an automation platform always creates applicants, and asking it forapprovedis refused with "This key cannot approve applicants (partners:stage), so it cannot create an approved partner. Leave lifecycle_stage out and they arrive as an applicant." - A second partner with the same email is
409 CONFLICT: "A partner with that email already exists." Look them up instead withGET /partners/lookup?email=sam@example.com, which answers the partner or404. - If your workspace has exactly one program that is not archived, a partner created as approved is enrolled in it at once, and their first link is made.
Enrol them in a program
curl -X POST https://go.refficks.com/api/v1/server/programs/<PROGRAM_ID>/partners \
-H "Authorization: Bearer <SECRET_KEY>" \
-H "Content-Type: application/json" \
-d '{ "partner_id": "01jc9a7k…" }'
Responds: 201 with a program_membership whose status is approved.
Add "tier_id" to enrol at a tier of that program; left out, they join on the
program's default terms.
| Answer | Why |
|---|---|
409 "That partner is already in this program." |
They were enrolled already, for example automatically on creation. Enrolment is not repeated silently. |
409 "Approve this partner before adding them to a program." |
The partner is still an applicant. |
409 "That tier belongs to a different program." |
The tier_id is another program's. |
404 |
The program or partner id is not in your workspace. |
Get their link
Enrolling makes the partner's first link, so read it:
curl "https://go.refficks.com/api/v1/server/tracking-links?partner_id=01jc9a7k…&program_id=<PROGRAM_ID>" \
-H "Authorization: Bearer <SECRET_KEY>"
Each tracking_link has code and url, the address the partner shares.
url takes the form Settings › Partners and payouts › Partner links sets:
your own site with the partner's code added (for example
https://example.com/?rfi=sam-rivera), or a redirect on your workspace's own
Refficks address ending /r/sam-rivera
(Set up your partner links). A link already
made moves with that setting. destination_url is where a click lands.
A link whose destination is a Stripe Payment Link (buy.stripe.com) is given
in the redirect form whatever that setting says, because Stripe drops a code
written into its address. Once an owner or admin has chosen a guide that
sells through Stripe Payment Links, under Integrations › Where you sell,
the redirect adds the partner's code as client_reference_id, which Stripe
hands back with the payment.
To make another link, POST /tracking-links with program_id and
partner_id, and optionally destination_url, name, code and
attribution_window_days. A link needs somewhere to go: without a
destination_url, the program's default destination is used, and if it has
none the request is 409 "This link needs a destination. Set one on the
program to have it filled in automatically." A code another link or coupon
already uses is 409 "That code is already in use." A link's code cannot be
changed later. Retire the link with "status": "archived" and make another.
List a partner's approved commissions for September
curl "https://go.refficks.com/api/v1/server/commissions?partner_id=01jc9a7k…&status=approved&occurred_from=2026-09-01&occurred_to=2026-09-30&per_page=100" \
-H "Authorization: Bearer <SECRET_KEY>"
Each commission_entry carries its own arithmetic: amount, the base_amount
it was worked out from, and the rate as a percentage (10 means ten per
cent), with one currency. A reversal is a separate entry with a negative
amount and parent_entry_id pointing at the original. Sum amount per
currency to reconcile; never across currencies.
The full reference
Every endpoint, field and status is in docs/openapi.yaml (OpenAPI 3.1) in the
Refficks repository, and docs/api.md §12 explains the rules behind them.
Not yet documented: Refficks does not yet serve the OpenAPI file from app.refficks.com or from the API, and no download is offered in the product. If you need the file, ask support@refficks.com.
Errors
| HTTP | error.code |
When | What to do |
|---|---|---|---|
| 401 | UNAUTHENTICATED |
No key, an unknown key, or a revoked one | Check the Authorization header and the key's row on Settings › Developer › API keys. |
| 402 | BILLING_READ_ONLY |
The workspace is read-only because its trial or subscription ended. Writes are refused; reads and webhook subscriptions carry on. | Subscribe under Settings › Workspace › Billing. |
| 403 | FORBIDDEN |
A publishable key, a key that lacks the ability the endpoint needs (error.details.ability names it), or an automation-platform key asking for one of the four things kept for a person |
Use a key made on Settings › Developer › API keys, or do it in the dashboard. |
| 403 | ACCOUNT_CLOSED |
The account was closed, so its secret keys no longer work | A new key will not help. |
| 404 | NOT_FOUND |
The id is not in your workspace, or a lookup matched nobody | Check the id came from this workspace. |
| 405 | (see note) | A write to a read-only address, such as a commission plan | The address is real and the method is not offered. |
| 409 | CONFLICT |
Duplicate partner, enrolment already exists, applicant not yet approved, an illegal lifecycle move, a link with no destination, a code already in use | Read error.message; it says which. |
| 422 | VALIDATION_ERROR |
A field failed validation or is not yours to set | error.details names each field. |
| 429 | RATE_LIMITED |
More than 300 requests in a minute | Wait and retry. |
| 503 | MAINTENANCE |
Refficks is in a maintenance window. POST /events stays up; this API does not. |
Retry after the Retry-After header, when given. |
The whole table, and the retry advice, is in API errors and limits. For a refusal you need to fix now, start at Why was my API call refused?.
Switched off: Billing is off on Refficks until the owner turns it on, so
BILLING_READ_ONLYcannot occur yet.
Related
- Create and manage API keys — the key, and what an automation-platform key cannot do.
- Send events from your server — reporting sales, which this API does not do.
- Subscriptions, coupon codes and referral tokens — claiming a Stripe promotion for a code, and the referral widget's token.
- Receive Refficks events with webhooks — being told about new partners and commissions instead of polling for them.
- API errors and limits — codes, request ids and retries.