Integrations
API & MCP docs
QuotaCanary exposes a read-only REST API and a remote MCP server. Your balances, surfaced wherever you already work.
Overview
Everything here is read-only and scoped to your account. The API and MCP server can fetch your credit-pool data and nothing else. They cannot connect tools, modify thresholds, or touch any account in any connected service.
Two ways to reach your balances programmatically:
- REST API - a single JSON endpoint at
https://app.quotacanary.com/api/v1/pools. Good for scripts, dashboards, and any HTTP client. - Remote MCP server - a Streamable HTTP connector Claude and Cursor can call natively. Useful for morning-brief prompts and agent workflows that need to know what is running low before deciding what to do.
Both use the same personal access tokens, issued from Settings → Integrations in your dashboard.
Authentication & tokens
Create a personal access token (PAT) in your dashboard at https://app.quotacanary.com/settings#integrations. The token is shown once at creation - copy it before you close the dialog. Only a hash is stored, so there is no way to retrieve it later. Revoke it from the same page at any time.
Tokens start with qc_live_. Send one in the Authorization header on every request:
Authorization: Bearer qc_live_...
Rate limit
60 requests per minute per token. Exceed that and you get a 429 with a Retry-After header telling you how many seconds to wait.
REST API
GET /api/v1/pools
Returns every watched credit pool for the token's owner.
curl https://app.quotacanary.com/api/v1/pools \ -H "Authorization: Bearer qc_live_..."
Filter by status with the optional ?status= query parameter (comma-separated). Valid values: healthy, low, critical, stale, error, disconnected, nodata. An invalid value returns 400.
curl "https://app.quotacanary.com/api/v1/pools?status=low,critical" \ -H "Authorization: Bearer qc_live_..."
Response shape
{ "pools": [ <PoolPayload>, ... ] }Fields on each pool object:
| Field | Type | Meaning |
|---|---|---|
connectionId | string | UUID of the connected tool. |
connectionName | string | Display name you gave the connection. |
tool.id | string | Internal tool slug (e.g. apollo, neverbounce). |
tool.name | string | Human-readable tool name. |
tool.topupUrl | string | null | Vendor billing URL for topping up, if known. |
creditType | string | null | Which credit pool within the tool (null when not yet read). |
label | string | null | Human label for the pool, if the tool exposes one. |
unit | string | null | Unit for the balance (e.g. "credits", "emails"). |
balance | number | null | Current balance. null before the first successful read. |
balanceLimit | number | null | Pool cap or monthly limit, if the vendor exposes one. |
recordedAt | string | null | ISO 8601 timestamp of the last successful balance read. |
status | string | One of: healthy, low, critical, stale, error, disconnected, nodata. |
burn.perDay | number | null | Average credits consumed per day (computed from history). |
burn.daysLeft | number | null | Estimated days until the balance hits zero at the current burn rate. |
eta.short | string | Punchy burn-out estimate (e.g. "burns out Friday" or "~2 weeks"). |
eta.long | string | Sentence form (e.g. "Empties in ~12 days."). |
thresholds.low | number | null | Balance at which status flips to low. null = default. |
thresholds.critical | number | null | Balance at which status flips to critical. null = default. |
Example response
{
"pools": [
{
"connectionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"connectionName": "Hunter",
"tool": {
"id": "hunter",
"name": "Hunter",
"topupUrl": "https://hunter.io/billing"
},
"creditType": "verifications",
"label": "Verifications",
"unit": "credits",
"balance": 620,
"balanceLimit": 1000,
"recordedAt": "2026-06-17T08:02:11.000Z",
"status": "healthy",
"burn": { "perDay": 52, "daysLeft": 12 },
"eta": {
"short": "~2 weeks",
"long": "Empties in ~12 days."
},
"thresholds": { "low": 100, "critical": 10 }
}
]
}Error shape
{ "error": { "code": "...", "message": "..." } }| HTTP status | code | When |
|---|---|---|
401 | unauthorized | Missing or invalid token. |
400 | invalid_status | ?status= contains an unrecognized value. |
429 | rate_limited | Over 60 requests per minute. |
500 | internal | Something went wrong on our end. |
MCP server
QuotaCanary runs a remote Streamable HTTP MCP server. Add it to Claude or Cursor and your agent can check what's running low before deciding what to do next.
Connector URL
https://app.quotacanary.com/mcp
Auth is the same personal access token from Settings → Integrations, passed as a bearer token in the connector configuration.
Available tools
| Tool | What it does |
|---|---|
list_balances | Returns all pools. Accepts an optional status filter (same values as the REST API). The morning-brief tool - "what's running low and needs topping up?" |
get_tool_balance | Returns pools for a single tool by name. Useful when an agent already knows which tool it is about to call and wants to check headroom first. |
Add to Claude Code
One command wires it up, with your token as a bearer header:
claude mcp add --transport http quotacanary \ https://app.quotacanary.com/mcp \ --header "Authorization: Bearer qc_live_..."
Add to Cursor
Add a remote server to your mcp.json with an auth header:
{
"mcpServers": {
"quotacanary": {
"url": "https://app.quotacanary.com/mcp",
"headers": { "Authorization": "Bearer qc_live_..." }
}
}
}Add to the Claude desktop app
The Add custom connector dialog only speaks OAuth and has no field for a token, so it cannot connect QuotaCanary in v1. Bridge it through mcp-remote in your claude_desktop_config.json instead:
{
"mcpServers": {
"quotacanary": {
"command": "npx",
"args": [
"mcp-remote",
"https://app.quotacanary.com/mcp",
"--header",
"Authorization: Bearer qc_live_..."
]
}
}
}Auth note
Auth is the static qc_live_... token from Settings → Integrations, sent as a bearer header. Any client that lets you set an Authorization header works. Connectors that only do full OAuth are not supported in v1.
Try it
Once connected, paste this into your agent:
What's running low in my QuotaCanary stack and needs topping up?
The agent calls list_balances, reads the status of every pool, and surfaces whatever needs attention. No hardcoded endpoint - the morning brief is emergent.
Webhooks
Webhooks are the push side of QuotaCanary. When a watched pool crosses a threshold, we POST the alert to every endpoint you have set up. Add them under Settings → Integrations → Webhooks - a generic HTTPS endpoint or a Slack incoming webhook - and choose whether each one fires on low + critical or critical only.
Delivery is a POST with content-type: application/json and a user-agent: QuotaCanary/1.0 header. There is no signature header - the endpoint URL is the secret, so keep it private.
A generic webhook receives this JSON body:
{
"event": "quota.alert.low",
"id": "f1e2d3c4-e5f6-7890-abcd-ef1234567890",
"level": "low",
"title": "Hunter is running low",
"body": "Hunter (production) crossed a low threshold.",
"tool": { "name": "Hunter" },
"connection": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Hunter (production)"
},
"pools": [
{ "label": "Verifications", "balance": 420, "threshold": 500, "unit": "credits" }
],
"dashboard_url": "https://app.quotacanary.com/dashboard",
"topup_url": "https://hunter.io/billing",
"created_at": "2026-06-17T09:30:00.000Z"
}event is quota.alert.low or quota.alert.critical, and topup_url is null when we do not know the vendor billing page. Slack incoming webhooks get a natively formatted message instead of this payload - no setup beyond pasting the webhook URL.
Need a token? Head to Settings → Integrations.
Open Integrations settings