Participants and enrollment
Participants
A participant exists once in your organization, identified by your own externalId (1–128 characters, unique in your
organization). In paths, URL-encode it. Create a participant here, then enroll them in each
program.
These endpoints cover your whole organization, so they need an admin key. A read-only key gets 404 on reads and 403
on changes; it reads participants through its program.
The Participant object
| Field | Type | Description |
|---|---|---|
externalId |
string | Your ID for the participant |
kybStatus |
string | pending, approved, declined or expired |
kybApprovedOn |
date or null | Set only when kybStatus is approved |
tags |
string[] | Your labels for the participant |
removedAt |
timestamp or null | Set when the participant is removed |
createdAt, updatedAt |
timestamp | |
programs |
array | Every program the participant is enrolled in, including removed enrollments: { programId, enrolledOn, eligible, removedAt } |
addresses |
array | Single-participant endpoints only. Every wallet address linked to the participant, in any program: { address, attachedOn } |
List participants
GET /participants → 200 { participants: Participant[], total }
| Query | Description |
|---|---|
limit, offset |
Paging. Default 50, max 500. |
kybStatus |
pending, approved, declined or expired |
tag |
Participants with this tag |
includeRemoved |
true to include removed participants. Default false. |
Sorted by externalId. List items don't include addresses.
Get a participant
GET /participants/{externalId} → 200 Participant
{
"externalId": "P-1001",
"kybStatus": "approved",
"kybApprovedOn": "2026-08-01",
"tags": ["gold"],
"removedAt": null,
"createdAt": "2026-10-13T16:06:03.196Z",
"updatedAt": "2026-10-13T16:06:03.196Z",
"programs": [
{
"programId": "5a30079b-940a-4570-95dc-ba59e019852a",
"enrolledOn": "2026-08-01",
"eligible": true,
"removedAt": null
},
{
"programId": "c5bfa72c-c742-4a43-a031-2350ea524e55",
"enrolledOn": "2026-09-15",
"eligible": true,
"removedAt": null
}
],
"addresses": [{ "address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU", "attachedOn": "2026-08-01" }]
}
Removed participants are still returned, with removedAt set. Returns 404 participant_not_found if there's no
participant with that ID.
Create a participant
POST /participants → 201 Participant
| Body field | Type | Required | Description |
|---|---|---|---|
externalId |
string | Yes | 1–128 characters, unique in your organization |
kybStatus |
string | No | Default pending |
kybApprovedOn |
date | No | Only with kybStatus: approved. Not in the future. Defaults to today. |
tags |
string[] | No | Up to 20, each 1–64 characters |
POST /api/v1/rewards/participants
Idempotency-Key: create-P-1001
{ "externalId": "P-1001", "kybStatus": "approved", "kybApprovedOn": "2026-08-01", "tags": ["gold"] }
Errors: 409 participant_already_exists if the externalId is taken, including by a removed participant; 400 if
kybApprovedOn is in the future or set without kybStatus: approved.
To create many participants at once, upload a CSV.
Update a participant
PATCH /participants/{externalId} → 200 Participant
| Body field | Type | Description |
|---|---|---|
kybStatus |
string | Changing it to anything other than approved clears kybApprovedOn |
kybApprovedOn |
date | Required when changing to approved |
tags |
string[] | Replaces the whole list |
Send at least one field. externalId can't change. A change applies in every program the participant is enrolled in. A
KYB change recalculates the open periods of each of those programs; a tag change recalculates nothing. Returns
404 participant_not_found for an unknown or removed participant.
PATCH /api/v1/rewards/participants/P-1002
Idempotency-Key: kyb-approved-P-1002
{ "kybStatus": "approved", "kybApprovedOn": "2026-10-12" }
Remove a participant
DELETE /participants/{externalId} → 204
Removes the participant from every program they're still enrolled in, then removes the participant. They stop earning
everywhere, and their open periods are recalculated. Their history is kept: activity, rewards and wallet addresses stay,
and the participant can still be read. A removed participant can't be updated or enrolled, and their externalId can't
be used again.
To take a participant out of one program only, remove the enrollment.
Program enrollment
An enrollment puts a participant in one program. It holds what differs per program: the enrollment date, eligibility and removal. Program reads return a combined view, with KYB status and tags from the participant.
The Enrollment object
| Field | Type | Description |
|---|---|---|
externalId |
string | The participant's ID |
enrolledOn |
date | Enrollment date. The participant earns in this program from this day. |
kybStatus |
string | From the participant |
kybApprovedOn |
date or null | From the participant |
eligible |
boolean | Your own eligibility flag, for filtering. It doesn't affect reward calculation. |
tags |
string[] | From the participant |
removedAt |
timestamp or null | Set when the participant is removed from this program |
createdAt, updatedAt |
timestamp |
Enrollment detail. The single-enrollment endpoints (get, enroll, update) also return:
| Field | Type | Description |
|---|---|---|
addresses |
array | The wallet addresses this program's activity names: { address, attachedOn }, attached from the first day this program reports them |
currentPeriod |
object | The participant's reward in the program's current period, below |
earningBalance |
amount or null | The participant's latest reported balance across their wallets |
balancesThrough |
date or null | Date of the latest balance included in earningBalance |
currentPeriod field |
Description |
|---|---|
periodId |
The current period |
status |
The period's status: OPEN, FINALIZING or FINALIZED |
estimatedReward |
The participant's reward so far: all reward lines plus adjustments |
calculatedAt |
When it was last calculated |
pendingRecalculation |
true while a newer calculation is queued |
lines |
Reward lines |
adjustments |
Adjustments in the period |
List enrolled participants
GET /programs/{id}/participants → 200 { participants: Enrollment[], total }
| Query | Description |
|---|---|
limit, offset |
Paging. Default 50, max 500. |
kybStatus |
pending, approved, declined or expired |
eligible |
true or false |
tag |
Participants with this tag |
includeRemoved |
true to include participants removed from the program. Default false. |
Sorted by externalId. List items don't include the detail fields.
Get an enrolled participant
GET /programs/{id}/participants/{externalId} → 200 Enrollment detail
This is the call to power a participant's own rewards view.
{
"externalId": "P-1001",
"enrolledOn": "2026-08-01",
"kybStatus": "approved",
"kybApprovedOn": "2026-08-01",
"eligible": true,
"tags": ["gold"],
"removedAt": null,
"createdAt": "2026-10-13T16:06:03.196Z",
"updatedAt": "2026-10-13T16:06:03.196Z",
"addresses": [{ "address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU", "attachedOn": "2026-08-01" }],
"currentPeriod": {
"periodId": "2026-10",
"status": "OPEN",
"estimatedReward": "1.917808",
"calculatedAt": "2026-10-13T16:06:06.349Z",
"pendingRecalculation": false,
"lines": [
{
"ruleKey": "usdpt_holdings",
"ruleVersion": 1,
"address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"amount": "1.917808",
"explanation": {
"measure": { "type": "daily_balance", "value": "20000.000000" },
"condition": { "type": "always", "met": true },
"payout": { "type": "annual_rate", "rate": "0.035", "base": "20000.000000", "beforeLimits": "1.917808" },
"limits": {}
}
}
],
"adjustments": []
},
"earningBalance": "20000.000000",
"balancesThrough": "2026-10-05"
}
Participants removed from the program are still returned, with removedAt set. Returns 404 Participant not found if
the participant isn't enrolled in the program.
Enroll a participant
POST /programs/{id}/participants → 201 Enrollment detail
The participant must already exist: create them first.
| Body field | Type | Required | Description |
|---|---|---|---|
externalId |
string | Yes | An existing participant |
enrolledOn |
date | No | Default today. Not in the future, and within the program's dates. |
eligible |
boolean | No | Default true |
POST /api/v1/rewards/programs/2e369621-ef27-499f-88c0-15032f767c54/participants
Idempotency-Key: enroll-P-1001
{ "externalId": "P-1001", "enrolledOn": "2026-08-01" }
Errors:
404 participant_not_found: no participant with thatexternalId409 participant_already_enrolled: already in the program, or removed from it409 participant_removed: the participant has been removed-
400 field_moved_to_org_participant:kybStatus,kybApprovedOnortagsin the body. Set them on the participant. 400 enrolledOn cannot be in the future,400 enrolledOn cannot be before the program starts on <date>409 Program has ended; it cannot enroll participants
To enroll many participants at once, upload a CSV.
Update an enrollment
PATCH /programs/{id}/participants/{externalId} → 200 Enrollment detail
| Body field | Type | Description |
|---|---|---|
eligible |
boolean | Required |
enrolledOn can't change. kybStatus, kybApprovedOn or tags return 400 field_moved_to_org_participant:
update the participant instead.
PATCH /api/v1/rewards/programs/2e369621-ef27-499f-88c0-15032f767c54/participants/P-1002
Idempotency-Key: mark-P-1002-ineligible
{ "eligible": false }
Remove from a program
DELETE /programs/{id}/participants/{externalId} → 204
The participant stops earning in this program, and its open periods are recalculated. They stay enrolled in their other
programs. Their history in the program is kept: they can still be read, and appear in lists with includeRemoved=true.
A removed enrollment can't be added again.