Billing API Endpoints
REST API endpoints for managing credits and subscriptions. All authenticated endpoints use the auth token to identify the user — no userId parameter is needed.
Get Balance Status
Get current Dollarinos balance and subscription info.
GET /api/v1/billing/balance
Example
curl https://clanker.engineer/api/v1/billing/balance \
-H "x-auth-token: YOUR_AUTH_TOKEN"
Response
{
"balance": 450,
"totalSpent": 50,
"canExecute": true,
"isAdmin": false
}
Response Fields
| Field | Type | Description |
|---|---|---|
balance | number | Total available Dollarinos |
totalSpent | number | Total Ds spent all-time |
canExecute | boolean | Whether user has sufficient credits to execute |
isAdmin | boolean | Whether user is an admin |
List Credit Packs
Get available credit packs for purchase.
GET /api/v1/billing/packs
Example
curl https://clanker.engineer/api/v1/billing/packs \
-H "x-auth-token: YOUR_AUTH_TOKEN"
Response
{
"packs": [
{
"id": "pack_100",
"name": "100 Ds",
"dollarinos": 100,
"priceCents": 100
},
{
"id": "pack_250",
"name": "250 Ds",
"dollarinos": 250,
"priceCents": 225
},
{
"id": "pack_500",
"name": "500 Ds",
"dollarinos": 500,
"priceCents": 425
},
{
"id": "pack_1000",
"name": "1000 Ds",
"dollarinos": 1000,
"priceCents": 800
},
{
"id": "pack_5000",
"name": "5000 Ds",
"dollarinos": 5000,
"priceCents": 3750
}
]
}
Purchase Credits
Purchase a credit pack.
POST /api/v1/billing/purchase
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
packId | string | Yes | Pack ID to purchase |
email | string | Yes | User email for receipt |
successUrl | string | Yes | Redirect URL on success |
cancelUrl | string | Yes | Redirect URL on cancel |
Example
curl -X POST https://clanker.engineer/api/v1/billing/purchase \
-H "x-auth-token: YOUR_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"packId": "pack_500",
"email": "user@example.com",
"successUrl": "https://clanker.engineer/payment-success",
"cancelUrl": "https://clanker.engineer/payment-cancel"
}'
Response
{
"checkoutUrl": "https://checkout.example.com/c/pay/cs_xxxxxxxxxxxx"
}
Errors
| Status | error.code | Description |
|---|---|---|
| 401 | UNAUTHORIZED | Missing or invalid credentials |
| 400 | INVALID_INPUT | Unknown packId, or checkout could not be created |
| 503 | SERVICE_UNAVAILABLE | The deployment cannot honour a charge, so none was started — see below |
| 500 | INTERNAL_ERROR | Checkout session creation failed unexpectedly |
A 503 here is about the deployment, not your request. It is returned when the server holds a live payment key but no webhook signing secret: payment confirmations could not be verified, so a completed charge would never grant credits. Rather than take money it cannot honour, the server refuses before contacting the payment provider. No charge is made and no customer record is created. Retrying will not help until the deployment is configured; every other part of the API is unaffected.
Everything downstream of the redirect — card decline, abandonment, refunds — is
settled by the payment provider and reported back through the webhook, not by
this call. A checkoutUrl means the session was created, nothing more.
Return URLs
successUrl and cancelUrl are where the payment provider sends the browser
when checkout finishes. Which pair you send depends on where checkout was
opened from:
| Client | successUrl / cancelUrl |
|---|---|
| Web | https://clanker.engineer/payment-success / https://clanker.engineer/payment-cancel — app screens served by the SPA |
| Native (iOS/Android) | https://clanker.engineer/api/v1/billing/return/success / https://clanker.engineer/api/v1/billing/return/cancel |
The example above is the web pair: the browser is already inside the app, so the provider can redirect straight to a screen.
Native cannot use those — the checkout runs in a system browser with no route
back into the app. It sends the provider to /api/v1/billing/return/:outcome
instead, where :outcome is success or cancel. That endpoint needs no auth
and returns an HTML page that redirects to the net.clanker://payment-success
(or …/payment-cancel) deep link, with a tappable fallback link for browsers
that block the automatic hop.
Neither page settles the payment. Credits are granted by the webhook, so a user who closes the browser before the redirect still gets what they paid for.
Subscribe to Based Mode
Starts a recurring $29/month subscription and returns a hosted checkout URL. The tier activates when the payment provider confirms the charge, not when this call returns.
POST /api/v1/billing/subscribe
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
email | string | yes | Billing email; used to create the customer record on first purchase |
successUrl | string | yes | Absolute URL on this deployment’s origin |
cancelUrl | string | yes | Absolute URL on this deployment’s origin |
Example
curl -X POST https://clanker.engineer/api/v1/billing/subscribe \
-H "x-api-key: ck_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"email": "you@example.com",
"successUrl": "https://clanker.engineer/payment-success",
"cancelUrl": "https://clanker.engineer/payment-cancel"
}'
Response
{
"checkoutUrl": "https://checkout.example.com/c/pay/cs_xxxxxxxxxxxx"
}
Errors
| Status | error.code | Description |
|---|---|---|
| 401 | UNAUTHORIZED | Missing or invalid credentials |
| 400 | INVALID_INPUT | A redirect URL is not on this origin, or the account already has an active subscription |
| 503 | SERVICE_UNAVAILABLE | The deployment cannot honour a charge, so none was started |
| 500 | INTERNAL_ERROR | Checkout session creation failed unexpectedly |
The same successUrl / cancelUrl rules as Purchase Credits apply — see
Return URLs.
An account may hold only one subscription. Subscribing while one is still active is refused rather than charged, because the tier is derived from a single expiry date and a second subscription would be billed with nowhere to be recorded.
Monthly credits are granted on activation and again on each renewal. Cancelling stops renewals; the tier remains until the current period’s expiry.
Manage Subscription
Returns a URL to the payment provider’s hosted billing portal, where the subscriber can update their card, download invoices and cancel.
POST /api/v1/billing/portal
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
returnUrl | string | yes | Absolute URL on this deployment’s origin; where the portal sends the browser when the user is finished |
Example
curl -X POST https://clanker.engineer/api/v1/billing/portal \
-H "x-api-key: ck_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "returnUrl": "https://clanker.engineer/settings" }'
Response
{
"portalUrl": "https://billing.example.com/p/session/xxxxxxxxxxxx"
}
Errors
| Status | error.code | Description |
|---|---|---|
| 401 | UNAUTHORIZED | Missing or invalid credentials |
| 400 | INVALID_INPUT | returnUrl is not on this origin, or the account has no billing history |
| 500 | INTERNAL_ERROR | Portal session creation failed unexpectedly |
A cancellation made in the portal is reported back through the webhook, so the tier is updated without the client doing anything further.
Get Transaction History
Get credit transaction history.
GET /api/v1/billing/transactions
Example
curl https://clanker.engineer/api/v1/billing/transactions \
-H "x-auth-token: YOUR_AUTH_TOKEN"
Response
Returns the 50 most recent transactions, newest first. There is no pagination parameter.
{
"transactions": [
{
"id": 1,
"userId": "3ab91c22-…",
"amount": -45,
"type": "usage",
"description": "README Generator execution",
"executionId": "b71e0c94-…",
"stripeSessionId": null,
"createdAt": "2024-01-15T10:31:30.000Z"
},
{
"id": 2,
"userId": "3ab91c22-…",
"amount": 500,
"type": "purchase",
"description": "500 Ds",
"executionId": null,
"stripeSessionId": "cs_xxxxxxxxxxxx",
"createdAt": "2024-01-10T14:00:00.000Z"
}
]
}
A row is a delta, not a balance snapshot — amount is signed and there is
no running-total field. Read the current balance from
GET /api/v1/billing/balance.
Transaction Types
| Type | Description |
|---|---|
usage | Ds spent on a run (negative amount, usually linked by executionId) |
purchase | Ds added — a credit pack, or a Based Mode activation/renewal grant |
Get Subscription Tier
Get current subscription mode.
GET /api/v1/billing/subscription/tier
Example
curl https://clanker.engineer/api/v1/billing/subscription/tier \
-H "x-auth-token: YOUR_AUTH_TOKEN"
Response
{
"tier": "paygmode",
"info": {
"name": "PAYG MODE",
"priceCents": 0,
"priceDisplay": "Free",
"monthlyCredits": 0,
"packDiscount": 0
},
"subscriptionExpiresAt": null,
"isTestMode": false
}
Modes
| Mode | Price | Monthly Ds | Pack Discount |
|---|---|---|---|
byokmode | Free | 0 | — |
paygmode | Free | 0 | — |
basedmode | $29/mo | 2,000 | 20% |
sitelicense | Licensed | 0 | — |
sitelicense can come back from this endpoint but is deliberately absent from
/subscription/info, which lists only the three self-serve modes.
Tier derivation: the tier is computed at runtime — not stored as a column. A valid
subscriptionExpiresAtin the future →basedmode. A stored BYOK key →byokmode. Otherwise →paygmode. There is no public endpoint to set the mode directly; it follows from your BYOK key and subscription state.
Get Subscription Info
Get pricing and details for each billing mode.
GET /api/v1/billing/subscription/info
Example
curl https://clanker.engineer/api/v1/billing/subscription/info \
-H "x-auth-token: YOUR_AUTH_TOKEN"
Response
{
"modes": {
"byokmode": {
"name": "BYOK MODE",
"priceCents": 0,
"priceDisplay": "Free",
"monthlyCredits": 0,
"packDiscount": 0
},
"paygmode": {
"name": "PAYG MODE",
"priceCents": 0,
"priceDisplay": "Free",
"monthlyCredits": 0,
"packDiscount": 0
},
"basedmode": {
"name": "BASED MODE",
"priceCents": 2900,
"priceDisplay": "$29/mo",
"monthlyCredits": 2000,
"packDiscount": 0.2
}
}
}
BYOK Key Management
Manage your Bring-Your-Own-Key (BYOK) LLM API key. Available on every mode — a
key is the only way to reach a provider this deployment holds no key for. What
a key changes is billing (0 Ds charged for calls that ride on it) and which
provider your requests are routed to; storing one promotes paygmode to
byokmode.
Get BYOK Status
GET /api/v1/byok
Example
curl https://clanker.engineer/api/v1/byok \
-H "x-auth-token: YOUR_AUTH_TOKEN"
Response
{
"hasKey": true,
"keyPreview": "sk-...xxxx",
"provider": "anthropic"
}
provider is null when no key is stored.
Set BYOK Key
Store or replace your LLM API key. Switches the account to byokmode.
PUT /api/v1/byok
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | LLM API key. sk-ant-… → Anthropic, any other sk-… → OpenAI |
The provider is decided by the key’s prefix, not by anything you send — the
sk-ant- test runs first, because Anthropic keys also start with sk-.
Example
curl -X PUT https://clanker.engineer/api/v1/byok \
-H "x-auth-token: YOUR_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "key": "sk-ant-..." }'
Response
{
"hasKey": true,
"keyPreview": "sk-...xxxx",
"provider": "anthropic"
}
Errors
| Status | error.code | Description |
|---|---|---|
| 400 | INVALID_INPUT | key missing, not a string, or not a recognised sk- prefix |
| 404 | NOT_FOUND | User record not found |
Delete BYOK Key
Remove the stored BYOK key. Account reverts to paygmode.
DELETE /api/v1/byok
Example
curl -X DELETE https://clanker.engineer/api/v1/byok \
-H "x-auth-token: YOUR_AUTH_TOKEN"
Response
{ "hasKey": false, "keyPreview": null }
Get Publishable Key
Get the publishable key for client-side payment forms.
GET /api/v1/billing/publishable-key
Example
curl https://clanker.engineer/api/v1/billing/publishable-key \
-H "x-auth-token: YOUR_AUTH_TOKEN"
Response
{
"publishableKey": "pk_live_xxxxxxxxxxxx"
}