Reseller API
Last updated 17 October 2026
Check your balance, buy keys and manage them from your own shop or Discord bot, without opening the portal. The API takes and answers JSON over HTTPS. You need reseller access to use it. If you do not have it yet, see the reseller program.
You do not need the API to be a reseller. Everything it does, you can also do by hand on your Reseller page. It is for a shop or bot that sells keys while you are away. Keys bought through the API follow the same rules as keys bought by hand, so read the reseller program before you sell.
1. Basics
- Every endpoint lives under
https://cerise.lol/api/v1/reseller. - Requests and answers are JSON.
- Money is in whole euro cents, so 3000 is €30.00.
- Times are ISO 8601, in UTC.
2. A sale, start to finish
What your shop or bot does when somebody buys a key from you.
- Your buyer pays you, in your shop or bot, however you take payment.
- Your server buys one key with Buy keys, sending your own order id as the
Idempotency-Keyheader. The cost comes off your balance. - Your server sends the key from the answer to your buyer.
- Your buyer redeems the key on their own Cerise account, on their License page. Its days start then.
- If the payment is reversed later, your server pauses or revokes the key with Update a key.
Your balance has to cover the key before step 2. The API cannot top it up. Top up in crypto on your Reseller page, and check what is left with Get balance.
3. Authentication
Send your API key as a bearer token on every request. Make it, or replace it, on your Reseller page. Keys start with rsk_ and are shown once, when they are made.
Keep it on your server. Anyone who has it can spend your balance, so never put it in a web page, an app, or code other people can read. If it leaks, make a new one and the old one stops at once. The examples read it from an environment variable called CERISE_API_KEY.
Header
Authorization: Bearer rsk_YOUR_KEY4. Errors
A refused request comes with an HTTP status and a JSON body holding error and message. Branch on error. The message is written for people and may be reworded.
- unauthorized 401. The API key is missing, wrong, or has been replaced by a new one.
- reseller_suspended 403. Your reseller access, or the account behind it, is suspended.
- rate_limited 429. More than 120 requests in a minute, or more than 30 with a missing or wrong key from one address. Wait the seconds in the Retry-After header, then send it again.
- payload_too_large 413. The request body is larger than any valid request.
- bad_request 400. The body, a query parameter or the Idempotency-Key header is not valid.
- not_found 404. None of your keys has that id, or no Cerise account matches that username and member number.
- insufficient_balance 409. Your balance does not cover the batch. Top up and send it again.
- invalid_state 409. The key is not in a state that action applies to, like unpausing a key that is not paused, or sending a key that is already sold.
- key_held_by_staff 403. Cerise staff paused this key, or its buyer’s account. Only staff can lift it.
- idempotency_key_reused 409. That Idempotency-Key was already used for a different plan, count or buyer, or more than 24 hours ago.
- method_not_allowed 405. That path does not take that method. The Allow header lists the ones it does. To revoke a key, send PATCH with the revoke action, not DELETE.
- internal_error 500. Something failed on our side. Send it again. A batch sent again with the same Idempotency-Key is never charged twice.
Response409 Conflict
{
"error": "insufficient_balance",
"message": "Your balance does not cover these keys. Top up first."
}5. Rate limits
Up to 120 requests a minute for each reseller. Past that, requests get 429 with rate_limited and a Retry-After header saying how many seconds to wait.
Requests with a missing or wrong key count separately, up to 30 a minute from one address. Past that, every request from that address waits the minute out, a working key included, so fix a bad key before retrying.
A shop that keeps its key list in step should page through with a high limit now and then, not ask for one key at a time.
6. Buying safely
A request can time out after the batch was already bought. Send it again blind and you pay twice. Send your own order id as the Idempotency-Key header on every purchase, and a retry is safe.
- The same id within 24 hours gets the same keys back and is not charged again. The answer carries
Idempotent-Replayed: true. - The same id with a different plan or count is refused with
idempotency_key_reused. - Up to 255 characters, with no spaces.
7. Get your balance
GET /api/v1/reseller/balance
Your balance, your discount, and what one key costs you on each plan. Check it before a big batch.
Returns
Your balance and prices.
- balanceCents number. What you can spend, in euro cents.
- currency string. Always EUR.
- discountBps number. Your discount in basis points, so 2000 is 20%.
- prices array. One entry per plan: plan, name, durationDays, listCents and your unitCents.
Can fail with unauthorized, reseller_suspended, rate_limited or internal_error. See section 3.
curl "https://cerise.lol/api/v1/reseller/balance" \
-H "Authorization: Bearer $CERISE_API_KEY"Response200 OK
{
"balanceCents": 5000,
"currency": "EUR",
"discountBps": 2000,
"prices": [
{
"plan": "trial",
"name": "Trial",
"durationDays": 7,
"listCents": 1500,
"unitCents": 1200
},
{
"plan": "monthly",
"name": "Monthly",
"durationDays": 30,
"listCents": 3000,
"unitCents": 2400
}
]
}8. Sell a key to a member
POST /api/v1/reseller/orders
Buys one key from your balance and puts it straight on your buyer’s account. Call it once your own checkout is paid. Your buyer gives you their username and member number at checkout and never types a key. The buyer is checked before anything is charged, so a typo costs nothing.
Headers
- Idempotency-Key string, optional. Your own order id, up to 255 characters with no spaces. A retry with it is neither charged nor sent twice. Always send one.
Body
- plan string, required. Which plan: trial or monthly.
- username string, required. The buyer’s Cerise username. They find it on their License page.
- memberNo integer, required. The buyer’s member number, shown beside their username. "#0431" and "431" are read the same.
Returns
The key and where it landed, with status 201. A replay also has the header Idempotent-Replayed: true.
- batchId string. The purchase’s id.
- unitCents number. What the key cost, in euro cents.
- balanceCents number. Your balance after it.
- licenseKey string. The key, for your own receipt. Your buyer does not need it, it is already on their account.
- id string. The key’s id.
- plan string. Its plan.
- start string. started when its days began now, or waiting when the buyer still had time. A waiting key starts by itself once that time runs out.
- expiresAt string | null. When its days run out. Null while it waits.
- memberNo number. The buyer’s member number.
Can fail with bad_request, not_found, insufficient_balance, invalid_state, idempotency_key_reused, unauthorized, reseller_suspended, rate_limited or internal_error. See section 3.
curl -X POST "https://cerise.lol/api/v1/reseller/orders" \
-H "Authorization: Bearer $CERISE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1042" \
-d '{"plan":"monthly","username":"lilcerise","memberNo":431}'Response201 Created
{
"batchId": "c1d7e0a4-5f2b-4e8a-9c63-b2f0d4a7e815",
"unitCents": 2400,
"balanceCents": 2600,
"licenseKey": "CERISE-7KQ2-M9XD-4TPA",
"id": "3f6c2a1e-8b7d-4c52-9a0e-5d1b7e4f2c90",
"plan": "monthly",
"start": "started",
"expiresAt": "2026-10-29T16:40:03.117Z",
"memberNo": 431
}9. Buy keys
POST /api/v1/reseller/keys
Buys a batch from your balance and answers with every key in full. This is the only time the full batch comes back together, so store the keys before anything else.
Headers
- Idempotency-Key string, optional. Your own order id, up to 255 characters with no spaces. Makes a retry safe, see Buying safely.
Body
- plan string, required. Which plan: trial or monthly.
- count integer, required. How many keys, 1 to 100.
Returns
The batch, with status 201. A replay of an earlier batch also has the header Idempotent-Replayed: true.
- batchId string. The batch’s id.
- plan string. The plan you bought.
- count number. How many keys.
- unitCents number. What each key cost, in euro cents.
- totalCents number. What the batch cost, in euro cents.
- balanceCents number. Your balance after the batch.
- keys array. Each key’s id and key.
Can fail with bad_request, insufficient_balance, idempotency_key_reused, payload_too_large, unauthorized, reseller_suspended, rate_limited or internal_error. See section 3.
curl -X POST "https://cerise.lol/api/v1/reseller/keys" \
-H "Authorization: Bearer $CERISE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1042" \
-d '{"plan":"monthly","count":2}'Response201 Created
{
"batchId": "c1d7e0a4-5f2b-4e8a-9c63-b2f0d4a7e815",
"plan": "monthly",
"count": 2,
"unitCents": 2400,
"totalCents": 4800,
"balanceCents": 200,
"keys": [
{
"id": "3f6c2a1e-8b7d-4c52-9a0e-5d1b7e4f2c90",
"key": "CERISE-7KQ2-M9XD-4TPA"
},
{
"id": "9a41d0b3-2c6e-47f1-b8d5-0e7c3a9f1b26",
"key": "CERISE-H3VN-2WZC-8RJE"
}
]
}10. List your keys
GET /api/v1/reseller/keys
One page of your keys, newest first. Pass nextCursor back as cursor for the next page, until it comes back null.
Query parameters
- state string, optional. Only keys in this state: unredeemed, active, paused, revoked or expired.
- cursor string, optional. The nextCursor from the page before.
- limit integer, optional. Keys per page, 1 to 100. 50 when left out.
Returns
A page of key objects.
- keys array. Key objects. See The key object.
- nextCursor string | null. Pass back as cursor for the next page. Null on the last page.
Can fail with bad_request, unauthorized, reseller_suspended, rate_limited or internal_error. See section 3.
curl "https://cerise.lol/api/v1/reseller/keys?state=active&limit=50" \
-H "Authorization: Bearer $CERISE_API_KEY"Response200 OK
{
"keys": [
{
"id": "3f6c2a1e-8b7d-4c52-9a0e-5d1b7e4f2c90",
"key": "CERISE-7KQ2-M9XD-4TPA",
"plan": "monthly",
"state": "active",
"createdAt": "2026-09-29T14:02:11.482Z",
"redeemedAt": "2026-09-29T16:40:03.117Z",
"redeemedBy": 431,
"queuedAt": null,
"expiresAt": "2026-10-29T16:40:03.117Z",
"timeLeftMs": 2591000000,
"durationDays": 30,
"machines": 1,
"heldByStaff": false
},
{
"id": "9a41d0b3-2c6e-47f1-b8d5-0e7c3a9f1b26",
"key": "CERISE-H3VN-2WZC-8RJE",
"plan": "monthly",
"state": "unredeemed",
"createdAt": "2026-09-29T14:02:11.482Z",
"redeemedAt": null,
"redeemedBy": null,
"queuedAt": null,
"expiresAt": null,
"timeLeftMs": null,
"durationDays": 30,
"machines": 0,
"heldByStaff": false
}
],
"nextCursor": "WyIyMDI2LTA5LTI5VDE0OjAyOjExLjQ4MjkxMyIsIjlhNDFkMGIzLTJjNmUtNDdmMS1iOGQ1LTBlN2MzYTlmMWIyNiJd"
}11. Pause, unpause or revoke a key
PATCH /api/v1/reseller/keys/{id}
Changes one of your keys. A paused key stops working until you unpause it, and its days keep counting down. A revoked key stops for good, and revoking never refunds your balance.
Path parameters
- id string, required. The key’s id.
Body
- action string, required. What to do: pause, unpause or revoke.
Returns
The key’s id and its new state.
- id string. The key’s id.
- state string. Its state now.
Can fail with bad_request, not_found, invalid_state, key_held_by_staff, unauthorized, reseller_suspended, rate_limited or internal_error. See section 3.
curl -X PATCH "https://cerise.lol/api/v1/reseller/keys/3f6c2a1e-8b7d-4c52-9a0e-5d1b7e4f2c90" \
-H "Authorization: Bearer $CERISE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"action":"pause"}'Response200 OK
{
"id": "3f6c2a1e-8b7d-4c52-9a0e-5d1b7e4f2c90",
"state": "paused"
}12. Send a key to a member
POST /api/v1/reseller/keys/{id}/deliver
Puts one of your unsold keys straight on your buyer’s account, for a key you already bought. Sending it again to the same buyer changes nothing and answers the same.
Path parameters
- id string, required. The key’s id.
Body
- username string, required. The buyer’s Cerise username. They find it on their License page.
- memberNo integer, required. The buyer’s member number, shown beside their username. "#0431" and "431" are read the same.
Returns
The key and where it landed.
- id string. The key’s id.
- plan string. Its plan.
- start string. started when its days began now, or waiting when the buyer still had time. A waiting key starts by itself once that time runs out.
- expiresAt string | null. When its days run out. Null while it waits.
- memberNo number. The buyer’s member number.
Can fail with bad_request, not_found, invalid_state, unauthorized, reseller_suspended, rate_limited or internal_error. See section 3.
curl -X POST "https://cerise.lol/api/v1/reseller/keys/9a41d0b3-2c6e-47f1-b8d5-0e7c3a9f1b26/deliver" \
-H "Authorization: Bearer $CERISE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"username":"lilcerise","memberNo":431}'Response200 OK
{
"id": "9a41d0b3-2c6e-47f1-b8d5-0e7c3a9f1b26",
"plan": "monthly",
"start": "waiting",
"expiresAt": null,
"memberNo": 431
}13. The key object
What listing your keys answers with for each key. A buyer only ever shows up as a member number.
- id string. The key’s id, for updating it.
- key string. The key your buyer redeems.
- plan string. Its plan: trial or monthly.
- state string. Where it stands: unredeemed, active, paused, revoked or expired.
- createdAt string | null. When you bought it, ISO 8601.
- redeemedAt string | null. When a buyer redeemed it, or null.
- redeemedBy number | null. The buyer’s member number, including a buyer you sent it to who has it waiting, or null. Never their name or email.
- queuedAt string | null. When a buyer redeemed it to start once their current time runs out, while it waits. Its state stays unredeemed until then, but nobody else can redeem it. Null otherwise.
- expiresAt string | null. When its days run out, once redeemed.
- timeLeftMs number | null. Milliseconds of access left, once redeemed.
- durationDays number | null. Days the key is worth.
- machines number. PCs using it right now. A count, never which ones.
- heldByStaff boolean. True when Cerise staff paused it. Only staff can lift that.
Example
{
"id": "3f6c2a1e-8b7d-4c52-9a0e-5d1b7e4f2c90",
"key": "CERISE-7KQ2-M9XD-4TPA",
"plan": "monthly",
"state": "active",
"createdAt": "2026-09-29T14:02:11.482Z",
"redeemedAt": "2026-09-29T16:40:03.117Z",
"redeemedBy": 431,
"queuedAt": null,
"expiresAt": "2026-10-29T16:40:03.117Z",
"timeLeftMs": 2591000000,
"durationDays": 30,
"machines": 1,
"heldByStaff": false
}