Billing
How billing works in Competitor Tracker & Co.: coins fund each tracked competitor, how and when charges apply and the endpoints that surface your balance.
Tracking a competitor costs coins. Our lead detective C. T. Lucky lays out the model: where coins come from, when they go out, what happens when the meter runs low, and which read endpoints surface the picture for your account view.
Shape
Every organization has a coin balance and an append-only transaction ledger. One coin pays for one competitor for one calendar month, starting the first time the pack tails it. Adding a paid competitor at subscribe time requires the balance to cover one more competitor than the organization is already tailing; the API rejects with 402 InsufficientCoinBalance otherwise.
A new organization starts with twenty-five coins on signup — enough to tail twenty-five competitors for a month while you find your footing.
The billing period anchors on whatever calendar day the first tail lands on. A subscription whose first tail is the 15th has its window expire on the 15th of each following month — the deduction itself rides along with the next weekly pass on or after that date. When the anchor day doesn't exist in a target month — the 31st in February, for instance — the window clamps to the last day of that month and re-anchors back to the original day the next month it fits. So a competitor first charged on Jan 31 has windows expiring Feb 28, then Mar 31, then Apr 30, then May 31 and so on. Stripe's anchor model, with the actual deduction sliding to the next weekly pass.
The own-app exception
An organization can declare an ownAppDomain. Subscriptions whose host matches that domain do not consume coins and never get paused — your own product is free to track. The domain is locked once set: write it once, live with it.
A competitor whose host matches the organization's own-app domain is flagged paused: false and nextChargeAt: null on every read.
The owner of the organization claims the domain through POST /v1/org/own-app-domain — see Claim your own application domain for the self-serve flow, the email-domain match rules, the personal-mail excludelist, and what happens to existing subscriptions when the claim lands.
How charges happen
The pack tails every subscribed competitor once a week — at the end of each week, so the data is fresh for the week that follows. The deduction rides along with that pass. If a competitor's window has expired (or has never been stamped), the next weekly pass is what advances the window by one calendar month and takes the coin. If the window is still in the future, the weekly pass runs and nothing is deducted.
In practice that means a coin comes out during the first weekly pass on or after the anchor day's expiry, not on the anchor day itself. A subscription anchored to the 15th sees its coin come out during whichever weekly pass falls on or after the 15th each month — so the actual deduction date can slide up to a week past the anchor.
The deduction is one charge per competitor per calendar month per organization — full stop. Retries, parallel passes and weird race conditions can't produce a second charge for the same competitor in the same month. Charges show up in the ledger with reason: "subscription_charge".
How pause and resume happen
Each weekly pass walks every organization's subscriptions, partitions them by own-app vs. paid, sorts the paid ones by addedAt ascending — the oldest you added is first in line — and keeps the first balance of them. The rest go into pause: paused: true, pauseReason: "insufficient_balance". Top up the balance and the next weekly pass resumes the previously-paused rows in the same addedAt order.
A paused row sees nothing fresh. Changes and snapshots detected after the moment we paused you are filtered out of every read endpoint until you top up. Anything you paid for before the pause is still visible — your history is yours, but the meter has to be running for the new findings to show up. So if two organizations are tailing the same site and one runs out of coins, the site still gets tailed (the funded organization keeps paying), and only the funded organization sees the new changes.
A notification fires the moment a competitor flips into pause and again when it resumes — once per transition. If a competitor stays paused across multiple weekly passes, you don't get a steady stream of "still paused" pings.
Read the balance
GET /v1/billing/balance
curl "$CT_API/billing/balance" \
-H "Authorization: Bearer $CT_TOKEN"{
"balance": 7,
"paidCompetitorCount": 3,
"ownAppCompetitorCount": 1,
"pausedCompetitorCount": 0,
"nextChargeAt": "2026-06-12T00:00:00.000Z",
"upcomingChargesNextMonth": 3
}nextChargeAt is the earliest current-window expiry across the organization's paid, non-paused, already-tailed competitors — the moment the next charge becomes due. The actual deduction rides along with the next weekly pass on or after that date, so there's up to a week of slippage between the expiry and the deduction itself.
upcomingChargesNextMonth counts paid competitors whose current window expires within the next calendar month — including any whose window has already lapsed but haven't yet been re-charged. Compare it against balance to know whether a pause is coming.
balance: 7, upcomingChargesNextMonth: 3 is comfortable. balance: 2, upcomingChargesNextMonth: 5 means three competitors will fall into pause across the next weekly passes unless you top up.
Read the ledger
GET /v1/billing/transactions
Cursor-paginated, newest-first. Useful for auditing what changed and when.
curl "$CT_API/billing/transactions?limit=20" \
-H "Authorization: Bearer $CT_TOKEN"{
"items": [
{
"id": "01HF...",
"delta": -1,
"balanceAfter": 6,
"reason": "subscription_charge",
"createdAt": "2026-05-13T02:00:14.000Z",
"metadata": { "competitorId": "01HC..." },
"competitorName": "Acme Corp",
"chargeKind": "renewal"
},
{
"id": "01HG...",
"delta": 10,
"balanceAfter": 10,
"reason": "signup_bonus",
"createdAt": "2026-04-29T10:18:02.000Z",
"metadata": null,
"competitorName": null,
"chargeKind": null
}
],
"nextCursor": null
}The reason slug is one of signup_bonus, referral_bonus, manual, purchase, subscription_charge, dlq_refund. Positive delta for credits, negative for charges. balanceAfter is the running balance immediately after that row landed.
competitorName carries the display name of the tracked competitor for subscription_charge and dlq_refund rows; null otherwise or when the competitor can no longer be resolved. chargeKind is "first" on the opening charge for a competitor, "renewal" on subsequent monthly charges, and null for all other row types.
Referrals
When someone you referred buys their first batch of coins, your organization receives three coins on the same call. The bonus lands in your ledger as a referral_bonus row, no action required. Send referrals through POST /v1/org/referrals.
Errors you might see
| Code | HTTP | When |
|---|---|---|
InsufficientCoinBalance | 402 | Subscribing another paid competitor would push paid count past the current balance. |
InvalidGrantAmount | 422 | A coin grant request carried zero or a negative amount. |
OwnAppDomainImmutable | 409 | An organization's own-app domain is already set and cannot be changed. |
OwnAppDomainEmailMismatch | 403 | The submitted domain doesn't match the verified email-domain on your account. |
PersonalEmailDomainNotAllowed | 422 | The submitted domain is a personal-mail or disposable-mail provider. |
The numbers above are the v1 defaults — twenty-five coins on signup, one month per coin, three coins on referral. The pack tunes them in one place, so the shape of the read endpoints stays the same when they shift.
Dispatches
Configure the Monday brief in Competitor Tracker & Co.: choose recipients, scope a dispatch with labels, add webhooks and use the thumbs feedback links.
Organization
Manage your Competitor Tracker & Co. organization: rename it, list members, change a role, invite or remove people, leave, transfer ownership or close it.