# Usage & limits

> Read every monthly allowance and every plan limit for a Warmerly workspace over HTTP: GET /usage/quotas for the meters, GET /billing/me?limits=1 for capacity.

Source: https://warmerly.com/docs/usage

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](https://warmerly.com/docs/plans-and-limits).

## 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 }
  ]
}
```

| 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`](https://warmerly.com/docs/verify#usage--quota), [`GET /email-finder/quota`](https://warmerly.com/docs/email-finder), [`GET /leads/quota`](https://warmerly.com/docs/leads#export-quota), are unchanged and still the right call when you only care about one meter.

## 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 }
}
```

| 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](https://warmerly.com/docs/errors).

## Related

- [Plans, limits and allowances](https://warmerly.com/docs/plans-and-limits): what the numbers are and how they behave
- [Verify](https://warmerly.com/docs/verify), [Email Finder](https://warmerly.com/docs/email-finder), [Leads](https://warmerly.com/docs/leads), the per-tool meters
- [Errors & rate limits](https://warmerly.com/docs/errors)

## Each part of this reference has its own page (2)

- [Every monthly allowance](https://warmerly.com/docs/usage/every-monthly-allowance)
- [Quotas are not rate limits](https://warmerly.com/docs/usage/quotas-are-not-rate-limits)

## More in API reference

- [Webhooks](https://warmerly.com/docs/webhooks): API reference for Warmerly webhook subscriptions: get HTTP-signed notifications for mailbox health events…
- [Workspaces & Projects](https://warmerly.com/docs/workspaces): API reference for Warmerly workspaces and projects: the tenant/billing hierarchy that mailboxes, campaigns,…
- [API Keys](https://warmerly.com/docs/keys): API reference for managing Warmerly API keys programmatically: create, list, and revoke keys used to…
