Appearance
Credits
Prepaid letters. An API-created campaign spends them; a campaign the balance cannot cover is refused with 402 insufficient_credits before anything is rendered or charged.
Why prepaid. An integration cannot stop and wait for someone to enter a card, so the money moves first. The balance is also the ceiling: a runaway loop can spend what was bought and no more.
Permissions needed: campaigns:read.
Credits are bought in the web app, not through the API. A key may spend the balance it was given; it can never charge the account's card to grow it.
Retrieve the balance
GET
/api/v1/creditsbash
curl -s -H "x-api-key: $DRSE_API_KEY" \
"$DRSE_BASE_URL/api/v1/credits"ts
const res = await fetch(`${BASE}/api/v1/credits`, {
headers: { "x-api-key": KEY },
});
const { data } = await res.json();json
{ "data": { "balance": 4200, "welcomeLetterClaimed": true } }welcomeLetterClaimed says whether the account has taken the single free letter offered in the web app; there is nothing for an API key to claim.
balance is a count of letters, not money. One letter costs one credit regardless of what it cost to buy.
Check before a large send
A campaign of 5 000 letters against a balance of 4 200 fails as a whole — the balance is checked once, for the entire campaign. Reading the balance first turns that into a decision you make rather than an error you handle.
List recent movements
GET
/api/v1/credits/ledgerThe account's statement: every purchase, every campaign that spent letters, and every refund, newest first. Up to 50 entries.
bash
curl -s -H "x-api-key: $DRSE_API_KEY" \
"$DRSE_BASE_URL/api/v1/credits/ledger"ts
const res = await fetch(`${BASE}/api/v1/credits/ledger`, {
headers: { "x-api-key": KEY },
});
const { data } = await res.json();json
{
"data": [
{
"delta": -1200,
"reason": "consumption",
"campaignId": "550e8400-e29b-41d4-a716-446655440000",
"note": null,
"createdAt": "2026-08-13 21:04:11"
},
{
"delta": 5000,
"reason": "purchase",
"campaignId": null,
"note": null,
"createdAt": "2026-08-12 09:31:02"
}
]
}| Field | Meaning |
|---|---|
delta | Signed letters. Negative spends, positive adds. |
reason | purchase, consumption, refund, adjustment or welcome (the one free letter a new account claims in the web app) |
campaignId | The campaign a consumption or refund belongs to; null otherwise |
note | Set on operator adjustments only |
The entries sum to the balance. That is the point of publishing them: a balance you disagree with can be checked line by line rather than argued about.
Running out
A campaign that needs more letters than the balance holds is refused whole — no partial send, nothing rendered, nothing charged:
json
{
"error": {
"code": "insufficient_credits",
"message": "The campaign needs 1200 letters but the balance is 300. Top up and retry."
}
}The message carries both numbers so the shortfall needs no arithmetic. Top up in the web app, then retry the identical request — including its Idempotency-Key, which is still safe to reuse: the first attempt created no campaign.