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:

KindExampleShapeRead it from
Monthly meterEmail verifications, AI creditsMonth-to-date count vs. a ceiling, resets on the 1stGET /usage/quotas
Capacity limitMailboxes, active campaigns, active prospectsA live count, right now, no resetGET /billing/me?limits=1
Per-item ceilingLeads per campaignA maximum with no workspace-wide "used" figureGET /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 }
  ]
}
FieldDescription
monthKeyThe month these counts cover, YYYY-MM. Counts reset when this changes — on the 1st, not on your renewal date.
planThe plan key whose ceilings apply, or null for a workspace with no ceilings (staff / granted unlimited).
planNameDisplay name for that plan, or "Unlimited".
quotas[].keyStable identifier: campaignSends, verifications, emailLookups, linkedinLookups, leadExports, placementTests, credits.
quotas[].usedMonth-to-date count.
quotas[].limitThe ceiling, or null for unmetered — never read null as zero or unknown. Campaign sends are null on every paid plan.
quotas[].remaininglimit - 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 }
}
FieldDescription
limits[].usedLive count. null for a per-item ceiling like campaignLeads, which has no single workspace-wide figure.
limits[].limitThe ceiling, or null for unlimited.
limitsGraceUntilISO 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.
statusThe Stripe subscription status (active, trialing, past_due, …).
usage.warmupUnlimitedWarmup 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.