More in Developers

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 FORBIDDEN with error.details.ability naming 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 are 403 FORBIDDEN with 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_id is refused with "The organization is determined by your credentials and cannot be set on a request." An id that belongs to another workspace is answered 404, 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: paid on 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 masked payment_hint on 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 for approved is 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 with GET /partners/lookup?email=sam@example.com, which answers the partner or 404.
  • 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.

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_ONLY cannot occur yet.