API errors and limits
What every error from the Refficks API looks like, what each code means, how
many requests you may make, and which failures to retry. For a developer
calling POST /api/v1/events or the Server API.
The error envelope
Every error has one shape:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "The request could not be processed.",
"details": {
"properties.amount": ["Amounts are integer minor units: 29900 for $299.00."]
},
"request_id": "01M51YGRBST524ZXY5YB283XR0"
}
}
codeis the one to switch on. It never changes for a given situation.messageis written for a person and can change. Log it; do not parse it.detailsis present only when there is more to say: the failing fields on a422, the missing ability on a403, the existing event on an idempotency conflict.request_idis the same value as theX-Request-Idresponse header.
Request ids
Every response, success or error, carries an X-Request-Id header. Refficks
writes the same id in its logs for that request, including the work it queues
afterwards, so support can follow one request from end to end.
If you send an X-Request-Id header yourself, Refficks uses yours and sends it
back. Log the id beside every call you make. It is the first thing support
asks for.
Error codes
| HTTP | error.code |
When | Retry? |
|---|---|---|---|
| 401 | UNAUTHENTICATED |
No Authorization: Bearer header ("An API key is required."), a key Refficks does not know or that was revoked ("That API key is not valid."), or one that expired ("That API key has expired."). |
No. Fix the key. |
| 402 | ENTITLEMENT_EXCEEDED |
Your plan's limit for something is reached, such as live API keys or partners. details gives feature, limit and usage. |
No. |
| 402 | BILLING_READ_ONLY |
The workspace is read-only because its trial or subscription ended unpaid, for example "Your free trial has ended, so this workspace is read-only and its tracking is paused. Partner links still take people to your site, but no clicks or sales are recorded, and what was already approved can still be paid. After three months unpaid, the account closes. Subscribe in Settings › Billing to make changes and start tracking again." Server API changes are refused; reads still answer. | No. Subscribe under Settings › Workspace › Billing. |
| 402 | TRACKING_PAUSED |
POST /events for something that happened while the workspace's tracking was paused, because it was unpaid: "Tracking was paused when this happened, because the workspace was unpaid, so it was not recorded." A sale made then is never recorded, even after subscribing. Refunds, disputes and credit notes are still recorded. |
No. Subscribing starts tracking again for what happens from then on. |
| 403 | FORBIDDEN |
A key that may not use this endpoint: a publishable key or an automation-platform key on POST /events ("That key is not permitted to perform this action."), a key without the ability a Server API endpoint needs (details.ability names it, for example partners:stage), or an automation-platform key asking for something kept for a person ("A key made for Zapier cannot archive a partner. That is for a person to decide, in Refficks or with a secret key."). |
No. Use the right key, or do it in the dashboard. |
| 403 | ACCOUNT_CLOSED |
"This account has been closed, so its API keys no longer work." Its secret keys stop; its publishable key keeps recording. | No. A new key will not help. |
| 404 | NOT_FOUND |
No such address, no such record, or a record that belongs to another workspace. The last two look the same on purpose. | No. |
| 405 | SERVER_ERROR (see below) |
The address exists but not for that method, such as a write to a read-only commission plan. | No. |
| 409 | CONFLICT |
The request clashes with what is already true: a partner with that email exists, the partner is already in the program, an applicant is not yet approved, a link has nowhere to go, a code is already in use. message says which. |
No. Read the message and change the request. |
| 409 | IDEMPOTENCY_CONFLICT |
An Idempotency-Key reused with a different body on POST /events: "This idempotency key was already used with a different payload." details.event_id is the event already recorded under it. |
No. Use a new key for a new event. |
| 422 | VALIDATION_ERROR |
A field is missing, malformed, or not yours to set. details lists each field and why. |
No. Fix the fields. |
| 429 | RATE_LIMITED |
"Too many requests." See Rate limits. | Yes, after waiting. |
| 500 | SERVER_ERROR |
Something broke on Refficks's side. | Yes, with backoff. |
| 502 | INTEGRATION_ERROR |
A provider Refficks had to ask did not answer properly. | Yes, later. |
| 503 | MAINTENANCE |
Refficks is in a maintenance window. message says why, details.until says until when if somebody said, and Retry-After is set when there is a time. |
Yes, after the window. |
Switched off: Billing is off on Refficks until the owner turns it on, so
BILLING_READ_ONLYandTRACKING_PAUSEDcannot occur yet. When it is on, the Server API keeps answering reads and still lets an automation platform end its webhook subscriptions.
About 405. A request with the wrong method on a real address is
answered with HTTP 405, but its error.code reads SERVER_ERROR and its
message "An unexpected error occurred.", and it carries no Allow header. Go
by the HTTP status in this one case: nothing broke, the method is not offered.
During maintenance the Server API is down, while POST /api/v1/events,
the tracking links and the tracking script stay up, so sales are not lost.
Codes only the dashboard and portal see
A key never receives these; they are listed so a developer reading logs from the dashboard's own calls knows them.
| HTTP | error.code |
Meaning |
|---|---|---|
| 401 | SSO_REQUIRED |
"Your organization now signs in through its own provider." A session a password made has ended because the company requires single sign-on. |
| 401 | PORTAL_PREVIEW_ENDED |
A merchant's look at a partner's portal has ended. |
| 403 | NOT_A_PARTNER_HERE |
Signed in on a merchant's portal as somebody who is not that merchant's partner. |
| 403 | FORBIDDEN |
During a support session, making anything that outlasts it, for example "Refficks support cannot make an API key, because it would keep working after this session ends. Ask them to make it when they next sign in." |
Rate limits
| Surface | Limit |
|---|---|
POST /api/v1/events and POST /api/v1/referral-tokens |
600 requests a minute, shared |
/api/v1/server/* |
300 requests a minute |
Tracking: partner links (/r/{code}) and the tracking script's requests |
1,200 requests a minute, per visitor's IP address |
| Helpdesk sidebar lookups | 300 requests a minute |
| The Refficks WordPress plugin | 300 requests a minute |
The two server buckets are separate, so a large partner sync on the Server API cannot use up the allowance your shop needs to report orders.
How they are counted today. Refficks means the server, helpdesk and
plugin limits to be counted per API key. On main they are counted per IP
address the requests come from: two keys used from one server share one
allowance, and one key used from two servers has two. This was confirmed for
POST /events and the Server API by running them; the helpdesk and plugin
limits are built the same way. If your integration uses several keys from one
machine, budget for the limit once, not once per key.
A successful response carries X-RateLimit-Limit and
X-RateLimit-Remaining. Slow down when X-RateLimit-Remaining gets low.
A 429 carries no Retry-After and no X-RateLimit-* headers. Wait for
the minute to pass (up to 60 seconds) before retrying.
Other limits worth knowing:
| Limit | Value |
|---|---|
Idempotency-Key |
255 characters |
per_page on any Server API list |
100 (default 25) |
| Live webhook subscriptions per key | 200 |
| Events in one subscription request | 50 |
| Events a month | No limit is enforced |
Retrying safely
- Retry a
429, a500, a502, a503, a timeout, or a dropped connection. Do not retry any other4xxunchanged: the same request gets the same answer. - Back off. Wait a second, then double the wait after each failure, and
add a little randomness so many clients do not retry at once. For a
429, wait until the minute has passed. For a503withRetry-After, wait that many seconds. - For
POST /api/v1/events, reuse the sameIdempotency-Keyand the same body. A retry of an event that did arrive is answered200with"status": "duplicate", and nothing is counted twice. Changing the body under the same key is a409 IDEMPOTENCY_CONFLICT. - For Server API writes, look before you retry. They take no idempotency
key. Some repeats are refused (a second partner with the same email, a
second enrolment), but a second
POST /server/tracking-linksmakes a second link. After a timeout, read first, for exampleGET /server/partners/lookup?email=…orGET /server/tracking-links?partner_id=…, and only create what is missing.
When to contact support
Write to support@refficks.com when a 500 keeps happening for the same
request, or when an answer contradicts this page. Include:
- the
X-Request-Id(orerror.request_id) of a failing request; - the method and path, such as
POST /api/v1/events, and the time in UTC; - the HTTP status and the
error.codeanderror.message; - the request body, with customer emails removed.
Never send a secret key, a webhook signing secret, or a partner's payout details. Support does not need them and will not ask for them. The first 12 characters of a key, as shown on Settings › Developer › API keys, are enough to say which key it was.
Related
- Why was my API call refused? — the same codes, starting from the symptom.
- Create and manage API keys — fixing a
401or a403. - Send events from your server — idempotency keys in practice.
- The Server API — the endpoints these errors come from.
- Why are Refficks webhook deliveries failing? — errors the other way round: your server answering Refficks.