Less boo. More booked. Halloween offer: 30 days free on any paid plan. Claim 30 days freeEvery new account gets one free mailbox for warmup and replies. No domain to buy. See how it works

Skip to content

Usage & limits

API reference5 min read4 sections

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 prospects, LinkedIn accounts, WhatsApp numbersA 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: 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.

API reference

#Every monthly allowance

text
GET /usage/quotas
bash
curl https://app.warmerly.com/api/v1/usage/quotas \
  -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
json
{
  "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, in UTC. 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 (never below 0), or null when the limit is null.
quotas[].overflowRemainingUnits 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[].unitsPerCreditHow 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.

API reference

#Plan capacity

text
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.

bash
curl "https://app.warmerly.com/api/v1/billing/me?limits=1" \
  -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
json
{
  "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 }
}
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.
limits[].breakdownOnly on whatsappAccounts when the workspace has bought extra WhatsApp slots: a sentence such as "2 on your plan + 1 purchased".
limits rowsBesides 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.
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.

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.

API reference

#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.

API reference
Was this page helpful?