Epochs & Claims
The $RUN reward system operates on epochs. Each epoch is a fixed time window where companies accumulate CEOScore. At epoch end, scores are finalized on-chain and companies can claim proportional $RUN rewards.
GET /api/epochs/current
Returns the current epoch info from the EpochDistributor contract on-chain.
Response
{
"success": true,
"data": {
"epochNumber": 5,
"startTime": 1711584000,
"endTime": 1712188800,
"rewardPool": "1000000000000000000000",
"totalScore": "8750",
"totalClaimed": "250000000000000000000",
"companyCount": 42,
"finalized": false,
"epochDuration": 604800,
"nextEpochIn": 172800
}
}All bigint values are returned as strings. rewardPool, totalScore, and totalClaimed are in wei. startTime, endTime, and epochDuration are in seconds. nextEpochIn is seconds remaining until the current epoch ends.
curl https://ceos.run/api/epochs/currentGET /api/epochs/claimable/[companyId]
Returns claimable $RUN for a company across all finalized epochs (last 12 max).
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
companyId | path | string | Yes | Company ID |
Response
{
"success": true,
"data": {
"companyId": "clx9...",
"treasuryAddress": "0x...",
"claimable": [
{
"epochNumber": 3,
"amount": "50000000000000000000",
"alreadyClaimed": false
},
{
"epochNumber": 4,
"amount": "75000000000000000000",
"alreadyClaimed": false
}
],
"totalClaimable": "125000000000000000000"
}
}If the company has no treasury address:
{
"success": true,
"data": {
"companyId": "clx9...",
"treasuryAddress": null,
"claimable": [],
"totalClaimable": "0",
"reason": "no_treasury_address"
}
}curl https://ceos.run/api/epochs/claimable/clx9abc123POST /api/epochs/claim
Claims $RUN rewards for a specific company and epoch. The oracle key calls EpochDistributor.claimReward() on-chain. Rewards always mint to the registered treasury address.
Authentication
Wallet signature required. Only the company creator can claim.
Request Body
{
"companyId": "clx9...",
"epochNumber": 4
}| Field | Type | Required | Description |
|---|---|---|---|
companyId | string | Yes | Company ID |
epochNumber | integer | Yes | Epoch number to claim (min 0) |
Response
{
"success": true,
"data": {
"success": true,
"alreadyClaimed": false,
"txHash": "0xabc...",
"amount": "75000000000000000000",
"epoch": 4
}
}If already claimed:
{
"success": true,
"data": {
"success": true,
"alreadyClaimed": true,
"txHash": null,
"amount": "0",
"epoch": 4
}
}Error Codes
| Status | Code | Description |
|---|---|---|
| 400 | BAD_REQUEST | Epoch not finalized or no treasury address |
| 403 | FORBIDDEN | Caller is not the company creator |
| 404 | NOT_FOUND | Company not found |
POST /api/cron/epoch-distribute
Weekly $RUN distribution. Calls EpochDistributor.finalizeEpoch() on-chain. This is a pull-based model: one transaction finalizes the epoch, then companies claim rewards individually.
Authentication
Vercel CRON_SECRET or ADMIN_SECRET in Authorization: Bearer <secret> header.
How It Works
- Reads the current on-chain epoch number
- Checks if the previous epoch is already finalized (idempotent)
- Loads CEOScore snapshots from the last epoch window
- Builds score arrays for eligible companies (those with treasury addresses)
- Calls
finalizeEpoch(epoch, companies[], scores[], claimTo[])on-chain - Creates
RevenueEpochand pendingRevenueClaimDB records
Response
{
"success": true,
"data": {
"epoch": 4,
"participantCount": 42,
"txHash": "0xdef...",
"status": "finalized"
}
}Idempotent responses:
{ "success": true, "data": { "epoch": 4, "status": "already_finalized" } }{ "success": true, "data": { "epoch": 4, "status": "already_processed" } }Error Codes
| Status | Code | Description |
|---|---|---|
| 401 | UNAUTHORIZED | Invalid cron/admin secret |