Usage & limits
Two endpoints answer "how much have I got left?". Which one you want depends on which kind of limit you mean — and there are three kinds, which behave differently enough that mixing them up produces a wrong number:
| Kind | Example | Shape | Read it from |
|---|---|---|---|
| Monthly meter | Email verifications, AI credits | Month-to-date count vs. a ceiling, resets on the 1st | GET /usage/quotas |
| Capacity limit | Mailboxes, active campaigns, active prospects | A live count, right now, no reset | GET /billing/me?limits=1 |
| Per-item ceiling | Leads per campaign | A maximum with no workspace-wide "used" figure | GET /billing/me?limits=1 |
Both endpoints are pure reads — neither ever debits an allowance — and both are
workspace-scoped, not project-scoped. They describe your whole active workspace, so they
take X-Api-Key and no X-Project-Id.
Plain-English background on what each limit means and what happens when you reach one: Plans, limits and allowances.
Every monthly allowance
GET /usage/quotas
curl https://app.warmerly.com/api/v1/usage/quotas \
-H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
{
"monthKey": "2026-09",
"plan": "growth-v2",
"planName": "Growth",
"quotas": [
{ "key": "campaignSends", "label": "Campaign sends", "unit": "sends", "tool": "Campaigns", "used": 4120, "limit": null, "remaining": null },
{ "key": "verifications", "label": "Email verifications", "unit": "verifications", "tool": "Verify", "used": 812, "limit": 25000, "remaining": 24188 },
{ "key": "emailLookups", "label": "Email lookups", "unit": "lookups", "tool": "Email finder", "used": 95, "limit": 10000, "remaining": 9905 },
{ "key": "linkedinLookups", "label": "LinkedIn lookups", "unit": "lookups", "tool": "LinkedIn search", "used": 0, "limit": 10000, "remaining": 10000 },
{ "key": "leadExports", "label": "Lead exports", "unit": "leads", "tool": "Leads", "used": 1500, "limit": 5000, "remaining": 3500 },
{ "key": "placementTests", "label": "Placement tests", "unit": "tests", "tool": "Accounts", "used": 3, "limit": 25, "remaining": 22 },
{ "key": "credits", "label": "AI credits", "unit": "credits", "tool": "AI", "used": 40, "limit": 500, "remaining": 460 }
]
}
| Field | Description |
|---|---|
monthKey | The month these counts cover, YYYY-MM. Counts reset when this changes — on the 1st, not on your renewal date. |
plan | The plan key whose ceilings apply, or null for a workspace with no ceilings (staff / granted unlimited). |
planName | Display name for that plan, or "Unlimited". |
quotas[].key | Stable identifier: campaignSends, verifications, emailLookups, linkedinLookups, leadExports, placementTests, credits. |
quotas[].used | Month-to-date count. |
quotas[].limit | The ceiling, or null for unmetered — never read null as zero or unknown. Campaign sends are null on every paid plan. |
quotas[].remaining | limit - used, or null when the limit is null. |
The plan resolved here matches the one the write gates enforce: a workspace with no live
subscription is held to Free's allowances rather than the plan it used to have, so this
endpoint and a 402 from POST /verify can never disagree.
The per-tool endpoints — GET /verify/usage,
GET /email-finder/quota, GET /leads/quota — are
unchanged and still the right call when you only care about one meter.
Plan capacity
GET /billing/me?limits=1
Without ?limits=1 you get the cheap plan/subscription summary and limits: null. The
flag is opt-in because building the rows runs several aggregate queries, including a
COUNT(DISTINCT email) across every lead in every active campaign — don't poll it.
curl "https://app.warmerly.com/api/v1/billing/me?limits=1" \
-H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
{
"plan": "growth-v2",
"planName": "Growth",
"status": "active",
"hasActiveSubscription": true,
"currency": "usd",
"billingInterval": "month",
"currentPeriodEnd": "2026-10-04T00:00:00.000Z",
"cancelAtPeriodEnd": false,
"limitsGraceUntil": null,
"workspaceId": "4ce44435-b783-4523-ba49-fefe23accfc5",
"limits": [
{ "key": "mailboxes", "label": "Connected mailboxes", "used": 12, "limit": 25 },
{ "key": "campaigns", "label": "Active campaigns", "used": 3, "limit": 25 },
{ "key": "prospects", "label": "Active prospects", "used": 4210, "limit": 50000 },
{ "key": "campaignLeads", "label": "Leads per campaign", "used": null, "limit": 25000 }
],
"usage": { "monthKey": "2026-09", "campaignSends": 4120, "campaignLimit": null, "warmupSends": 980, "warmupUnlimited": true }
}
| Field | Description |
|---|---|
limits[].used | Live count. null for a per-item ceiling like campaignLeads, which has no single workspace-wide figure. |
limits[].limit | The ceiling, or null for unlimited. |
limitsGraceUntil | ISO timestamp when the over-limit grace period expires, or null when the workspace is inside its plan. While it is set, everything keeps running; when it passes, campaign sending and warmup pause until the workspace is back inside its limits or upgraded. |
status | The Stripe subscription status (active, trialing, past_due, …). |
usage.warmupUnlimited | Warmup is never metered — it is here so a UI can say so rather than drawing an empty bar. |
Workspaces belonging to staff or granted unlimited access return plan: null,
planName: "Unlimited" and limits: null.
Quotas are not rate limits
A 402 payment_required means you are out of a monthly allowance; a 429 too_many_requests means you called too fast. They have different causes, different
remedies and different reset behaviour — see Errors & rate limits.
Related
- Plans, limits and allowances — what the numbers are and how they behave
- Verify, Email Finder, Leads — the per-tool meters
- Errors & rate limits
