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, LinkedIn accounts, WhatsApp numbers | 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: the plan and ceilings come from your active workspace (send X-Workspace-Id to choose one), so they take X-Api-Key and no X-Project-Id. The month-to-date counts on the monthly meters are kept per user, not per workspace.
Plain-English background on what each limit means and what happens when you reach one: Plans, limits and allowances.
#Every monthly allowance
GET /usage/quotascurl 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, in UTC. 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 (never below 0), or null when the limit is null. |
quotas[].overflowRemaining | Units left on a credit-bought block for this quota. It does not reset on the 1st and is not included in remaining. Present on verifications, emailLookups, linkedinLookups, leadExports and placementTests, and 0 when none has been bought. |
quotas[].unitsPerCredit | How many units one credit buys for this quota. Present on the same five quotas as overflowRemaining. |
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=1Without ?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 },
{ "key": "linkedinAccounts", "label": "LinkedIn accounts", "used": 0, "limit": 0 },
{ "key": "whatsappAccounts", "label": "WhatsApp numbers", "used": 0, "limit": 2 },
{ "key": "campaignSends", "label": "Campaign sends", "used": 4120, "limit": null },
{ "key": "verifications", "label": "Email verifications", "used": 812, "limit": 25000 },
{ "key": "emailLookups", "label": "Email lookups", "used": 95, "limit": 10000 },
{ "key": "linkedinLookups", "label": "LinkedIn lookups", "used": 0, "limit": 10000 },
{ "key": "leadExports", "label": "Lead exports", "used": 1500, "limit": 5000 },
{ "key": "placementTests", "label": "Placement tests", "used": 3, "limit": 25 },
{ "key": "credits", "label": "AI credits", "used": 40, "limit": 500 }
],
"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. |
limits[].breakdown | Only on whatsappAccounts when the workspace has bought extra WhatsApp slots: a sentence such as "2 on your plan + 1 purchased". |
limits rows | Besides the capacity rows (mailboxes, campaigns, prospects, campaignLeads, linkedinAccounts, whatsappAccounts), the list repeats the seven monthly meters from GET /usage/quotas with used and limit only. |
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. |
The response carries more fields than shown, for example isAdmin, trialAvailable, billedThrough (set on an Agency client workspace, which reports its parent's plan), pausedFrom and pausedUntil, and discountPercentOff and discountEndsAt. A workspace with no plan chosen yet returns plan: null, status: "none" and, with ?limits=1, Free's ceilings.
Workspaces belonging to staff or granted unlimited access return plan: null, planName: "Unlimited" and limits: null.
#Quotas are not rate limits
A 402 (quota_exhausted or payment_required, depending on the endpoint) 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