Conventions and errors
Conventions
-
JSON. Request and response bodies are JSON with camelCase fields, except CSV uploads (
multipart/form-data) and the rewards export (CSV). -
IDs. Programs, rules, imports and adjustments have UUIDs. Participants are identified by your own
externalId(1–128 characters), URL-encoded in paths. Periods areYYYY-MM. Rule keys and metric keys are lowercase snake_case (1–63 characters). -
Amounts are decimal strings, never JSON numbers: up to 6 decimal places in requests, exactly 6 in responses
(
"17.500000"). Rates are fractions with up to 18 decimal places ("0.035"= 3.5%). - Dates and times are UTC. Dates are
YYYY-MM-DD; timestamps are ISO 8601 (2026-10-13T16:03:31.676Z). - Strict validation. Unknown body fields and unknown query parameters are rejected with
400. - Wallet addresses are Solana addresses (base58, 32–44 characters).
Idempotency
Every POST, PATCH and DELETE requires an Idempotency-Key header (1–255 characters), so a retried request never
runs twice.
-
Repeating a request with the same key, method, URL and body within 24 hours returns the original response, with
the header
Idempotent-Replayed: true. - Reusing a key with a different request returns
422 idempotency_key_reused. - Repeating a request while the first is still running returns
409 idempotency_request_in_progress. Retry shortly. - Successful responses and
4xxerrors are stored and replayed. After fixing a request that failed with a4xx, send it with a new key.5xxerrors are not stored, so you can retry them with the same key. - Keys are scoped to your API key.
Versioning
PATCH /programs/{id} and PATCH /rules/{ruleId} require expectedVersion, the version you last read. If the record
has changed since, the request returns 409; read it again and retry.
Pagination
| Style | Endpoints | Parameters | Response |
|---|---|---|---|
| Offset | Participants, period rewards, adjustments | limit (default 50, max 500), offset (default 0) |
The collection plus total, the number of matching records |
| Cursor | Activity | limit (default 100, max 1000), cursor |
rows plus nextCursor (null on the last page) |
Other lists return everything in one response.
Background processing
-
Reward calculation runs in the background, usually within seconds.
pendingRecalculationon rewards andpendingRecalculationson periods show work in progress. - CSV imports are queued: the upload returns
202with animportId, which you poll for status and results. - Finalizing a period waits for its calculations to finish, so it can take up to about 60 seconds. Use a client timeout above that.
- There are no webhooks. Poll the read endpoints for changes; activity supports
updatedSincefor incremental sync.
Limits
| Limit | Value |
|---|---|
| Request body | 1 MiB (activity: 4 MiB) |
| Activity rows per request | 1,000 |
Activity metadata |
2,048 bytes per row |
| CSV upload | One file, up to 20 MiB |
| Rule keys per program | 50 |
| Rules per library | 500 |
| Participant tags | 20, each up to 64 characters |
| Rule preview | 10,000 participants per period |
Errors
Errors return a JSON body with an error field. Many also carry a message, and rule-definition errors carry a path
pointing into your request.
{ "error": "Program not found" }
{ "error": "version_conflict", "message": "Rule is at version 2, not 1" }
{ "error": "tiers_gap", "message": "tier 1 must start at 10", "path": "definition.payout.tiers[1].from" }
-
Validation errors (
400) describe the first problem found, as"<field>: <problem>", for example"limit: Too big: expected number to be <=500". -
Some
409errors start with a code followed by details, such as"grace_window_open: ends at 2026-11-06T00:00:00.000Z". Match on the text before the first colon. -
Batch endpoints (activity and imports) report problems per row in a
200response instead of failing the whole request. See Activity.
| HTTP | Meaning |
|---|---|
| 400 | Invalid request: validation, a missing or too-long Idempotency-Key, or a bad CSV header |
| 401 | Missing or invalid API key |
| 403 | A read-only key tried to change something |
| 404 | The resource doesn’t exist in your account, or isn’t visible to your key |
| 409 | Conflict with the current state: version conflict, period not ready, rule archived, program ended |
| 413 | Request body or file too large |
| 422 | A rule definition that isn’t valid, a reused idempotency key, or an atomic activity batch that was rejected |
| 500 | Unexpected error. Retry with the same Idempotency-Key |
The full list of codes is in Error codes.
Error codes
| HTTP | error |
Meaning |
|---|---|---|
| 400 | idempotency_key_required |
Idempotency-Key is missing |
| 400 | idempotency_key_too_long |
Idempotency-Key is over 255 characters |
| 401 | Missing API key / Invalid API key |
No key, or a key that isn’t valid |
| 403 | A read-only API key cannot modify resources |
A read-only key tried to change something |
| 404 | Program not found, Participant not found, Period not found, Metric not found, Rule not found, Import not found, Organization not found |
Doesn’t exist in your account |
| 404 | participant_not_found |
No participant with that externalId |
| 404 | rule_not_attached |
The rule key isn’t attached to the program |
| 404 | rule_version_not_found |
The rule has no such version |
| 409 | idempotency_request_in_progress |
The same request is still running |
| 409 | version_conflict |
The rule changed since you read it |
| 409 | Program is at version <n>, not <m> |
The program changed since you read it |
| 409 | participant_already_exists |
The externalId is already taken |
| 409 | participant_already_enrolled |
The participant is already in the program, or was removed from it |
| 409 | participant_removed |
The participant has been removed |
| 409 | rule_archived |
The rule is archived |
| 409 | rule_key_taken |
The rule key is linked to a different rule |
| 409 | rule_already_attached |
The rule key is already attached |
| 409 | program_ended |
The program has ended, so its rules can’t change |
| 409 | library_full |
Your library already has 500 rules |
| 409 | preview_too_large |
The period has more than 10,000 participants |
| 409 | period_not_finalized |
Export needs a finalized period |
| 409 | period_already_finalized, grace_window_open, previous_period_not_finalized, recalculation_pending, recalculation_failed |
See Finalize a period |
| 409 | no_open_period |
No open current period for an adjustment |
| 409 | program_started, program_in_use |
The program can’t be deleted |
| 409 | period_anchor_locked, period_anchor_moves_first_period |
The anchor day can’t change |
| 400 | field_moved_to_org_participant |
KYB status or tags sent to a program enrollment endpoint |
| 413 | File exceeds 20971520 bytes |
CSV over 20 MiB |
| 422 | idempotency_key_reused |
The key was used for a different request |
| 422 | too_many_rules |
Over 50 rule keys in the program |
| 422 | invalid_definition, condition_not_allowed, payout_not_allowed, limit_not_allowed, duplicate_limit, growth_payout_needs_growth_condition, tiers_overlap, tiers_gap |
The rule definition isn’t valid |
| 422 | metric_not_found, metric_deprecated, metric_kind_mismatch, metric_subject_mismatch, unit_mismatch |
The rule doesn’t fit the program’s metrics |
| 422 | Batch rejected; no rows were applied |
An atomic activity batch had a rejected row |
| 500 | Internal server error |
Unexpected error; retry with the same Idempotency-Key |
Endpoint index
Paths are relative to /api/v1/rewards. Read-only endpoints can be called with either key type; Admin endpoints
need an admin key.
| Method and path | Key role | Purpose |
|---|---|---|
| Organization | ||
GET /organization |
Admin | Get your organization and its budget usage |
PATCH /organization |
Admin | Set or clear the organization budget |
| Programs | ||
GET /programs |
Read-only | List programs |
GET /programs/{id} |
Read-only | Get a program with its current and scheduled rules |
POST /programs |
Admin | Create a program |
POST /programs/{id}/end |
Admin | End a program |
PATCH /programs/{id} |
Admin | Update a program’s name, grace days, anchor day or template rules |
DELETE /programs/{id} |
Admin | Delete a program that hasn’t started |
| Rule library | ||
GET /rule-blocks |
Read-only | Catalog of measures, conditions, payouts and limits |
GET /rule-templates |
Read-only | Catalog of rule templates |
GET /rules |
Read-only | List library rules |
GET /rules/{ruleId} |
Read-only | Get a rule at its latest version |
GET /rules/{ruleId}/versions |
Read-only | List every version of a rule |
POST /rules |
Admin | Create a rule |
POST /rules/validate |
Admin | Check a rule definition without saving it |
POST /rules/{ruleId}/archive |
Admin | Archive a rule |
PATCH /rules/{ruleId} |
Admin | Update a rule; a new definition creates a new version |
| Program rules | ||
GET /programs/{id}/rules |
Read-only | Rules in force and scheduled changes |
POST /programs/{id}/rules |
Admin | Attach a rule version from the next period |
POST /programs/{id}/rules/preview |
Admin | Preview what a rule would pay, without saving |
PATCH /programs/{id}/rules/{ruleKey} |
Admin | Move a rule key to another version from the next period |
DELETE /programs/{id}/rules/{ruleKey} |
Admin | Detach a rule from the next period |
| Metrics | ||
GET /programs/{id}/metrics |
Read-only | List a program’s metrics |
POST /programs/{id}/metrics |
Admin | Add a metric |
POST /programs/{id}/metrics/{key}/deprecate |
Admin | Stop accepting activity for a metric |
PATCH /programs/{id}/metrics/{key} |
Admin | Update a metric’s name or description |
DELETE /programs/{id}/metrics/{key} |
Admin | Delete a metric that has never been used |
| Participants | ||
GET /participants |
Admin | List participants across your organization |
GET /participants/{externalId} |
Admin | Get a participant, with their programs and wallets |
POST /participants |
Admin | Create a participant |
PATCH /participants/{externalId} |
Admin | Update KYB status or tags |
DELETE /participants/{externalId} |
Admin | Remove a participant from every program |
| Program enrollment | ||
GET /programs/{id}/participants |
Read-only | List participants enrolled in a program |
GET /programs/{id}/participants/{externalId} |
Read-only | Get an enrolled participant with their current-period reward |
POST /programs/{id}/participants |
Admin | Enroll a participant in a program |
PATCH /programs/{id}/participants/{externalId} |
Admin | Update eligibility in a program |
DELETE /programs/{id}/participants/{externalId} |
Admin | Remove a participant from a program |
| Activity | ||
GET /programs/{id}/activity |
Read-only | List activity rows |
GET /programs/{id}/activity/summary |
Read-only | Totals per participant for one metric and period |
POST /programs/{id}/activity |
Admin | Report up to 1,000 activity rows |
POST /programs/{id}/activity/deletions |
Admin | Delete up to 1,000 activity rows |
| CSV imports | ||
GET /participants/imports/{importId} |
Admin | Status and results of a participants import |
POST /participants/imports |
Admin | Upload a participants CSV |
GET /programs/{id}/participants/imports/{importId} |
Read-only | Status and results of an enrollment import |
GET /programs/{id}/activity/imports/{importId} |
Read-only | Status and results of an activity import |
POST /programs/{id}/participants/imports |
Admin | Upload an enrollment CSV |
POST /programs/{id}/activity/imports |
Admin | Upload an activity CSV |
| Periods and rewards | ||
GET /programs/{id}/periods |
Read-only | List periods |
GET /programs/{id}/periods/{periodId}/rewards |
Read-only | Rewards per participant for a period |
GET /programs/{id}/periods/{periodId}/rewards/export |
Read-only | Download a finalized period’s rewards as CSV |
POST /programs/{id}/periods/{periodId}/finalize |
Admin | Finalize a period |
| Adjustments | ||
GET /programs/{id}/adjustments |
Read-only | List adjustments |
POST /programs/{id}/adjustments |
Admin | Add an adjustment to the current period |
| Statistics | ||
GET /programs/{id}/stats |
Read-only | Program totals and a per-period series |