Programs and metrics
Organization
Your organization holds your programs and participants. It can have one organization budget, in one asset, that tracks rewards across all of your programs.
Get the organization
GET /organization → 200
{
"id": "324a2411-d244-4bc1-bc4e-4ef439baa61d",
"name": "Partner Network",
"budget": { "amount": "1000000.000000", "asset": "USDPT" },
"budgetUsage": {
"amount": "1000000.000000",
"used": "160.885888",
"remaining": "999839.114112",
"over": false,
"programCount": 7
}
}
| Field | Description |
|---|---|
budget |
{ amount, asset }, or null with no budget |
budgetUsage |
Budget usage, plus programCount: the number of programs counted. null with no budget. |
used adds up the reward totals of every period, estimated and finalized, of each program whose rewardAsset is the
budget's asset. Test programs (isTest) don't count.
Admin keys only. A read-only key gets 404 Organization not found.
Update the organization budget
PATCH /organization → 200 Organization
| Body field | Type | Required | Description |
|---|---|---|---|
budget |
object or null | Yes | { amount, asset }: amount greater than zero, asset an uppercase asset symbol. null removes the budget. |
PATCH /api/v1/rewards/organization
Idempotency-Key: set-org-budget-2027
{ "budget": { "amount": "1000000", "asset": "USDPT" } }
The change applies immediately and only affects budget reporting. There's no expectedVersion: the last write wins.
Programs
The Program object
| Field | Type | Description |
|---|---|---|
id |
UUID | Program ID |
name |
string | Display name, 1–200 characters |
rewardAsset |
string | The asset rewards are calculated in, for example USDPT |
cadence |
string | monthly |
periodAnchorDay |
integer | Day of the month periods start, 1–28 |
graceDays |
integer | Days after a period ends before it can be finalized, 0–31 |
isTest |
boolean | Marks a test program |
programBudget |
amount or null | Budget for the whole program; null for none. For tracking only. |
status |
string | active or ended |
startsOn |
date | First day of the program |
endsOn |
date or null | Last day, set when the program is ended |
version |
integer | Increases with every change to the program or its rules. Send it as expectedVersion when you update the program. |
currentPeriodId |
string | The period the program is in today. Before the program starts, its first period; after it ends, its last. |
rules |
ProgramRule[] | For each rule key, the rule in force in the current period |
scheduledRules |
ProgramRule[] | For each rule key, the next scheduled change (attach, upgrade or detach) |
periods |
object | count, byStatus (OPEN, FINALIZING, FINALIZED counts) and current (the current period: periodId, startsOn, endsOn, status, graceEndsAt) |
createdAt, updatedAt |
timestamp |
ProgramRule
| Field | Description |
|---|---|
ruleKey |
The program’s name for the rule |
ruleId |
Library rule ID |
version |
Library version in use; null for a detach |
action |
attach, upgrade or detach |
effectivePeriodId |
The period this change applies from |
name |
The library rule’s current name |
definition |
The rule definition; null for a detach |
summary |
The rule in one sentence |
template, params, cap |
The template form, when the rule matches one; otherwise null |
List programs
GET /programs → 200 { programs: Program[] }
Returns all your programs, oldest first. A read-only key sees only its own program.
Get a program
GET /programs/{id} → 200 Program
{
"id": "2e369621-ef27-499f-88c0-15032f767c54",
"name": "Partner Rewards",
"rewardAsset": "USDPT",
"cadence": "monthly",
"graceDays": 5,
"periodAnchorDay": 1,
"isTest": false,
"programBudget": "150000.000000",
"status": "active",
"startsOn": "2026-08-01",
"endsOn": null,
"version": 6,
"createdAt": "2026-10-13T16:03:31.676Z",
"updatedAt": "2026-10-13T16:03:35.815Z",
"currentPeriodId": "2026-10",
"rules": [
{
"ruleKey": "usdpt_holdings",
"version": 1,
"effectivePeriodId": "2026-08",
"ruleId": "1e154cbc-0202-4504-84f7-a478d3f99b54",
"name": "USDPT holdings",
"action": "attach",
"definition": {
"measure": { "type": "daily_balance", "metricKey": "usdpt_balance" },
"condition": { "type": "always" },
"payout": { "type": "annual_rate", "rate": "0.035" },
"limits": []
},
"summary": "3.5% a year on the daily balance of usdpt_balance",
"template": "holdings",
"params": { "metricKey": "usdpt_balance", "annualRate": "0.035" },
"cap": null
}
],
"scheduledRules": [
{
"ruleKey": "settlement_growth",
"version": 2,
"effectivePeriodId": "2026-11",
"ruleId": "695c549d-9e03-4818-ba1f-701c64d9b3cc",
"name": "Settlement growth",
"action": "upgrade",
"definition": {
"measure": { "type": "sum", "metricKey": "settlement_volume" },
"condition": { "type": "growth_vs_previous_period", "minGrowthPercent": "10" },
"payout": { "type": "percent_of_measure", "of": "growth", "rate": "0.001" },
"limits": [{ "type": "cap_per_period", "amount": "900" }]
},
"summary": "0.1% of settlement_volume growth when it beats the previous period by at least 10%, at most 900 per period",
"template": null,
"params": null,
"cap": null
}
],
"periods": {
"count": 3,
"byStatus": { "OPEN": 2, "FINALIZING": 0, "FINALIZED": 1 },
"current": {
"periodId": "2026-10",
"startsOn": "2026-10-01",
"endsOn": "2026-10-31",
"status": "OPEN",
"graceEndsAt": "2026-11-06T00:00:00.000Z"
}
}
}
Returns 404 Program not found if the program doesn’t exist in your account.
Create a program
POST /programs → 201 Program
| Body field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | 1–200 characters |
rewardAsset |
string | Yes | Uppercase asset symbol, for example USDPT |
startsOn |
date | Yes | First day of the program |
periodAnchorDay |
integer | No | Day of the month periods start, 1–28. Default 1. |
graceDays |
integer | No | 0–31. Default 5. |
isTest |
boolean | No | Default false |
programBudget |
amount or null | No | Budget for the whole program. Must be greater than zero. Default null (no budget). |
rules |
array | Yes | 0–50 rules, each either an attach item or a template item. [] is allowed. |
- Attach item:
{ ruleKey, ruleId, ruleVersion }attaches a version of a library rule. -
Template item:
{ ruleKey, template, params, cap? }creates a library rule from a template and attaches it.capis an optional per-period cap.
Rules in a new program apply from its first period. Creating a program also creates its built-in <asset>_balance
metric.
POST /api/v1/rewards/programs
x-api-key: <admin key>
Idempotency-Key: create-partner-rewards-2027
{
"name": "Partner Rewards 2027",
"rewardAsset": "USDPT",
"startsOn": "2027-01-15",
"periodAnchorDay": 15,
"graceDays": 7,
"programBudget": "150000",
"rules": [
{
"ruleKey": "holdings",
"template": "holdings",
"params": { "metricKey": "usdpt_balance", "annualRate": "0.035" },
"cap": "2500"
},
{ "ruleKey": "kyb_bonus", "ruleId": "de3b5ba5-fc7f-479a-8df8-7009c50da521", "ruleVersion": 1 }
]
}
The response is the new Program, with version: 1 and both rules in rules, effective from
2027-01.
Errors:
400invalid fields, or a rule key listed twice404 Rule not found,404 rule_version_not_found409 rule_archived,409 library_full-
422a rule doesn’t fit the program, withpathpointing to it, for example{"error":"metric_not_found","message":"Rule holdings: metric eurc_balance does not exist","path":"rules[0].definition.measure.metricKey"}
End a program
POST /programs/{id}/end → 200 Program
Ends the program at the end of its current period: status becomes ended and endsOn is set to the period’s last
day. The current period still earns and can be finalized as usual. After this, the program’s rules can’t change. Send no
body.
Errors: 404 Program not found; 409 Program has already ended.
Update a program
PATCH /programs/{id} → 200 Program
| Body field | Type | Required | Description |
|---|---|---|---|
expectedVersion |
integer | Yes | The program’s current version |
name |
string | No | Applies immediately |
graceDays |
integer | No | Applies to periods created from now on |
periodAnchorDay |
integer | No | Only before the program starts |
programBudget |
amount or null | No | Sets or changes the budget; null removes it. Applies immediately and only affects budget reporting. It can be changed after a program has ended. |
rules |
array | No | 1–50 template items. Each takes effect from the next period. Rule keys you don’t list are unchanged. |
To attach, upgrade or detach library rules, use the program rules endpoints.
PATCH /api/v1/rewards/programs/220a893a-6072-49ee-b71f-ccd06e6d9343
Idempotency-Key: rename-partner-rewards-2027
{ "expectedVersion": 2, "name": "Partner Rewards 2027 (pilot)", "programBudget": "200000" }
Errors:
404 Program not found409 Program is at version 6, not 2: read the program again and retry409 Program has ended; its rules cannot change400 programBudget: must be greater than zero409 period_anchor_locked: …: the anchor day can’t change once the program has started409 period_anchor_moves_first_period: …: the new anchor day would move the program’s first period422a template rule doesn’t fit the program, ortoo_many_rules
Delete a program
DELETE /programs/{id} → 204
Permanently deletes a program that hasn’t started, for example one created with the wrong settings. Rules in your library are kept. Once a program has started, end it instead.
Errors: 404 Program not found; 409 program_started: started on <date>;
409 program_in_use: has <participants, activity, …>.
Metrics
A metric is a data series you report for a program. Rules read metrics to calculate rewards.
| Property | Values | Meaning |
|---|---|---|
kind |
snapshot |
One value per day (date), such as a daily balance |
event |
One value per occurrence (occurredAt plus your eventId), such as a settlement |
|
total |
One value per period (periodId), such as a monthly count |
|
subject |
participant |
The value belongs to the participant |
address |
The value belongs to one of the participant’s wallet addresses (Solana) | |
unit |
count or an asset code |
count values are whole numbers; asset values (for example USDPT) have up to 6 decimal places |
Every program comes with a built-in metric, <asset>_balance (for example usdpt_balance): a daily snapshot of each
wallet’s balance in the reward asset.
The Metric object
| Field | Type | Description |
|---|---|---|
key |
string | Lowercase snake_case, unique in the program |
kind |
string | snapshot, event or total |
subject |
string | participant or address |
unit |
string | count, or an uppercase asset or currency code (2–12 characters) |
name |
string or null | Display name |
description |
string or null | |
builtIn |
boolean | true for the program’s <asset>_balance metric |
deprecatedAt |
timestamp or null | Set when the metric is deprecated |
List metrics
GET /programs/{id}/metrics → 200 { metrics: Metric[] }
All of the program’s metrics, including deprecated ones, sorted by key.
{
"metrics": [
{
"key": "settlement_volume",
"kind": "event",
"subject": "participant",
"unit": "USD",
"name": "Settlement volume",
"description": "USD value of each completed settlement",
"builtIn": false,
"deprecatedAt": null
},
{
"key": "usdpt_balance",
"kind": "snapshot",
"subject": "address",
"unit": "USDPT",
"name": null,
"description": null,
"builtIn": true,
"deprecatedAt": null
}
]
}
Add a metric
POST /programs/{id}/metrics → 201 Metric
| Body field | Type | Required | Description |
|---|---|---|---|
key |
string | Yes | Lowercase snake_case, 1–63 characters |
kind |
string | Yes | snapshot, event or total |
subject |
string | Yes | participant or address |
unit |
string | Yes | count or an uppercase code such as USD or USDPT |
name |
string | No | 1–200 characters |
description |
string | No | 1–2,000 characters |
POST /api/v1/rewards/programs/2e369621-ef27-499f-88c0-15032f767c54/metrics
Idempotency-Key: add-settlement-volume
{ "key": "settlement_volume", "kind": "event", "subject": "participant", "unit": "USD", "name": "Settlement volume" }
Errors: 409 Metric settlement_volume already exists (including a deprecated metric with that key).
Deprecate a metric
POST /programs/{id}/metrics/{key}/deprecate → 200 Metric
New activity for the metric is rejected and new rules can’t use it. Existing rules and activity keep working. You can’t
deprecate a metric that a current or scheduled rule reads: 409 Metric <key> is read by rule <ruleKey>. Deprecating a
deprecated metric changes nothing.
Update a metric
PATCH /programs/{id}/metrics/{key} → 200 Metric
Changes name and/or description (null clears them). A metric’s key, kind, subject and unit can’t change;
add a new metric instead.
Delete a metric
DELETE /programs/{id}/metrics/{key} → 204
Only for a metric you added that no rule or activity has ever used. Otherwise deprecate it.
Errors: 409 Metric <key> is built in and cannot be deleted;
409 Metric <key> is read by rule <ruleKey>; deprecate it instead;
409 Metric <key> has activity rows; deprecate it instead.