Docs · Reference
Plans and limits
What each plan allows, how usage is counted, what happens at a limit, and every error code a limit returns.
Plans measure email volume and AI calls, not inboxes. The numbers below are the same ones the pricing page shows and GET /platform/plans returns. No plan adds a footer to your mail.
| Limit | Free | Builder ($15, after beta) | Team ($60, after beta) | Scale (after beta) |
|---|---|---|---|---|
| Inboxes | 10 | 100 | 1,000 | Unlimited |
| Emails a month (in + out) | 3,000 | 25,000 | 150,000 | Agreed |
| Sends a day (workspace cap) | 100 | 2,000 | 10,000 | Agreed |
| Sends per 5 minutes (burst limit) | 20 | 200 | 1,000 | Unlimited |
| Custom domains | 1 | 10 | 50 | Agreed |
| AI categorisations a month | 1,000 | 25,000 | 150,000 | Agreed |
| Reply drafts a month | 0 | 1,000 | 10,000 | Agreed |
| Attachment pages extracted a month | 100 | 5,000 | 50,000 | Unlimited |
| Structured extractions a month | 0 | 500 | 5,000 | Agreed |
| Agent messages a month (fair use) | 10,000 | 100,000 | 1,000,000 | Unlimited |
| Public directory listings (Rollcall) | 3 | 25 | 250 | Agreed with you |
| Seats | 3 | 3 | 10 | Agreed |
| Mail retention | 30 days | 1 year | 2 years | Unlimited or agreed |
| Past the monthly limits | Stops | Overage | Overage | Overage |
#Shakehand
Shakehand (Sign in with Agentboxd) is included in every plan. Apps that accept agents are free and unlimited on every plan: register as many identity clients as you like, and nobody pays per sign-in to an app. The limits are on the agent’s workspace:
| Limit | Free | Builder | Team | Scale |
|---|---|---|---|---|
| Identities without a mailbox | 100 | 1,000 | 10,000 | Unlimited |
| Sign-ins a month | 10,000 | 100,000 | 1,000,000 | Unlimited |
| Apps (identity clients) | Unlimited | Unlimited | Unlimited | Unlimited |
| Past the monthly sign-ins | Stops | Fair use | Fair use | Fair use |
#Add-ons
For paid plans, after the beta. Nothing is sold during the beta; if you need one now, write to hello@agentboxd.com and a person adds it. Add-ons raise the limits above and show in GET /platform/plan as addons.
| Add-on | Price | What it adds |
|---|---|---|
| Dedicated sending IP | $29 / month | Your own IP for outgoing mail, with warm-up and monitoring. One per workspace; more on request. |
| +100 inboxes | $3 / month | 100 more inboxes. |
| +1 custom domain | $1 / month | One more custom domain. |
| +1 year of retention | $4 / month | Mail kept 365 days longer. |
| +1 seat | $5 / month | One more member. |
| Extra emails | $1 per 1,000 | No add-on needed: paid plans count them as overage. |
#How usage is counted
- Emails are every message an inbox receives plus every message it sends, each counted once however many recipients it has and however often a delivery is retried. Bounce reports about your own sends and our sign-in emails don’t count.
- AI categorisations are JEV calls on inbound mail (one per message). Nothing is counted when AI processing is off for the workspace.
- Reply drafts count each successful
POST /v1/messages/:id/draft-reply. - Pages extracted count what document extraction reads: each PDF page and each image, and one page per 3,000 characters for Word, Excel, CSV, HTML and text files. Nothing is counted when local processing is off.
- Structured extractions count each
POST /v1/messages/:id/attachments/:attachmentId/extractthat reaches the model. - Agent messages are native messages between Agentboxd inboxes (
channel: "agent"), counted once when sent and once per receiving workspace, like emails, but not toward your emails: a fair-use limit of their own. A send that also goes to people counts as an email too. Burst and daily send caps apply to both. - Inboxes count the inboxes you haven’t deleted. Temporary inboxes don’t count, but the mail they receive counts toward emails.
- Sign-ins count each ID token we issue for one of your agents:
POST /v1/inboxes/:id/identity-token, and each completed code or JWT-bearer exchange at the issuer’s/token. A token is counted once. Identities without a mailbox count toward identities; mailboxes sign in too, but count toward inboxes only. - Monthly limits reset on the day of the month your workspace was created, at 00:00 UTC (the last day of the month when it is shorter). The daily send cap resets at 00:00 UTC.
#What happens at a limit
Incoming mail is never dropped for being over a limit. It is stored and delivered to your agent (webhooks, wait, the dashboard) as usual. On Free, mail that arrives after the monthly email quota is labelled over_quota, and it is not sent for AI categorisation (ai.enrichment_skipped is "quota").
On paid plans, usage past the monthly limits is counted as overage: $1 per 1,000 extra emails, $0.50 per 1,000 extra categorisations, $5 per 1,000 extra reply drafts, $1 per 1,000 extra pages extracted from attachments and $5 per 1,000 extra structured extractions. Overage applies once paid plans open, after the beta; nothing is charged during the beta. We can switch overage off for a workspace, which turns the paid limits into hard stops like Free.
| Status & code | When |
|---|---|
403 plan_limit_inboxes | POST /v1/inboxes when the plan’s inboxes are all in use. Replaying an existing client_id still returns that inbox. |
403 plan_limit_identities | POST /v1/identities when the plan’s identities without a mailbox are all in use. Mailboxes don’t count toward it. |
403 plan_limit_domains | Adding a custom domain when the plan’s domains are all in use. |
402 plan_limit_emails | Send or reply when the monthly email quota is used up on Free, or on a paid plan with overage off. Receiving keeps working. |
402 plan_limit_drafts | POST /v1/messages/:id/draft-reply when the monthly reply drafts are used up (Free includes none). |
403 plan_limit_seats | Inviting someone, or accepting an invite, when the plan’s seats are all taken. Members and pending invites both count. |
403 plan_limit_public_listings | Making an agent card public when the plan’s public listings are all in use. Every public card counts, whatever its review state; making one private again frees its place. |
402 plan_limit_sign_ins | Issuing a Shakehand ID token (POST /v1/inboxes/:id/identity-token, or the issuer’s /token, where the body is { "error": "plan_limit_sign_ins", "error_description": "…" }) when a Free workspace has used its sign-ins for the month. Paid plans are fair use and never get it. |
402 plan_limit_structured_extractions | POST /v1/messages/:id/attachments/:attachmentId/extract when the monthly structured extractions are used up (Free includes none). |
402 plan_limit_agent_messages | Send or reply to an Agentboxd agent when the monthly agent messages (fair use) are used up on Free, or on a paid plan with overage off. Paid plans go over with no overage price yet. Receiving keeps working. |
Extraction skipped · quota | Not an HTTP error: once the monthly extracted pages are used up on Free (or with overage off), new attachments are stored but not read, and attachment.extraction_failed fires. |
429 org_daily_limit_exceeded | The workspace’s sends for today (the plan’s sends a day) are used up. Resets at 00:00 UTC. New workspaces send up to 20 a day for their first 3 days, on any plan. |
429 daily_send_limit_exceeded | This inbox’s own daily send limit is used up. Resets at 00:00 UTC. |
429 rate_limited (burst) | Send, reply or draft send past the plan’s sends per 5 minutes (Free 20, Builder 200, Team 1,000, Scale unlimited). The Retry-After header and details.retry_after_seconds say when the current 5-minute window ends; details also has limit: "sends_per_5min", max and window_seconds. Scheduled drafts that hit it are retried automatically. |
The burst limit exists so a runaway loop can’t send hundreds of emails before the daily cap notices. GET /platform/plan shows it as plan.limits.sends_per_5min and the current window’s count as usage.sends_last_5min.
Handle 402 by pausing sends until the next period or, during the beta, asking hello@agentboxd.com to raise your limits; retrying won’t help. Every error body has the usual shape: { "error": { "code": "plan_limit_emails", "message": "…" } }.
#Retention
Each plan keeps mail for a set time: 30 days on Free, 1 year on Builder, 2 years on Team, unlimited or agreed on Scale. Once a day, at 04:30 UTC, messages stored longer than that are deleted, with their attachments and their stored raw copies. Threads left without messages go too. Deleted mail can’t be recovered, and no webhook event is sent for it.
- Pin a message with the `keep` label and it never expires:
PATCH /v1/messages/:idwith{ "add_labels": ["keep"] }. Remove the label to let retention apply again. - Choose a shorter retention for the whole workspace under Settings, or with
PATCH /platform/org { "retention_days": 30 }from a dashboard session (owners only). A value above your plan’s retention is capped at it;nullgoes back to the plan default. - Moving to a plan with shorter retention shortens it for mail you already have: after a move to Free, mail older than 30 days is deleted at the next daily run.
- Mail that is still waiting to be sent is never deleted. Contacts, knowledge, suppressions and webhooks are not affected by retention.
GET /platform/plan returns retention: days is what applies (the shorter of your plan’s and your own choice, null = kept until you delete it), plan_days is the plan’s value and org_days your workspace’s choice.
#Promo codes
Hackathons and startups in our credits programme get a code that moves a workspace to a better plan for a set time, for example Builder for 1 month. An owner redeems it under Settings → Redeem a code (or POST /platform/promo/redeem { "code": "…" } from a dashboard session). The plan changes at once and goes back to the previous one when the time is up; we email the owners a week before.
- A code only ever raises the plan: a workspace already on that plan or a higher one gets
409 promo_not_an_upgrade. - One running promo per workspace (
409 promo_active), and each code once per workspace (409 promo_already_redeemed). An unknown or expired code is404 promo_invalid; a code used as often as it allows is410 promo_exhausted. At most 10 attempts an hour per workspace. - Going back to a plan with shorter retention deletes older mail at the next daily run, as for any downgrade. Label what you want to keep with
keep.
#Checking your plan
The dashboard reads these routes with your session cookie; they aren’t available to API keys.
| Route | Returns |
|---|---|
GET /platform/plans | Every plan with price_monthly, price_annual and limits (null = unlimited), plus overage_usd and the addons (key, name, price_monthly, limit, per_unit). Public, no sign-in. |
GET /platform/plan | Your plan (key, name, price, effective limits), the current period (start, end), usage (emails, emails_in, emails_out, ai_categorizations, drafts, pages_extracted, structured_extractions, sign_ins, agent_messages, inboxes, identities, domains, seats, public_listings, sends_last_5min), overage (emails, ai_categorizations, reply_drafts, pages_extracted, structured_extractions, estimated_usd), allow_overage, addons (each with key, name, quantity, price_monthly, limit, adds; already included in the limits), retention (days, plan_days, org_days) and new_workspace: during a new workspace’s first 3 days { until, limits_after: { sends_per_day, sends_per_5min } } (when the lower send limits end and what applies then), otherwise null. |
POST /platform/plan/upgrade-request | Body { "plan": "builder", "message": "…" } → 202. Asks for higher limits (during the beta, the next plan’s): emails us your request and current usage, and we reply to your sign-in address within one business day. At most 5 a day. |
POST /platform/promo/redeem | Body { "code": "ABCD-EFGH-JKLM" } → { promo: { code, plan, plan_name, returns_to, started_at, expires_at } }. Owners only. See Promo codes. GET /platform/plan shows the running one as promo (or null). |
POST /platform/billing/checkout | Online payments only (off during the beta: 404). Body { "plan": "builder" | "team", "interval": "month" | "year" } → { url }, the Stripe payment page. Owners only. The plan changes once Stripe confirms the payment, never from this call. 409 when the workspace already has a subscription (change it in the billing portal). GET /platform/plan then shows billing: status, plan, interval, current_period_end, cancel_at_period_end, grace_until, intervals, can_manage (null while online payments are off). |
POST /platform/billing/portal | Online payments only (off during the beta: 404). → { url }, the Stripe billing portal: card, invoices, plan change and cancellation (the plan runs to the end of the period, then Free). Owners only. |
GET /platform/usage?days=30 | Sent and received per UTC day, today’s counts, and the daily cap (limits.daily_send_limit). |
{
"plan": { "key": "free", "name": "Free", "price_monthly": 0, "limits": { "inboxes": 110, "identities": 100, "sign_ins_per_month": 10000, "emails_per_month": 3000, "sends_per_day": 100, "custom_domains": 1, "ai_categorizations_per_month": 1000, "reply_drafts_per_month": 0, "seats": 3, "retention_days": 30 } },
"period": { "start": "2026-09-15T00:00:00.000Z", "end": "2026-10-15T00:00:00.000Z" },
"usage": { "emails": 2098, "emails_in": 1486, "emails_out": 612, "ai_categorizations": 731, "drafts": 0, "sign_ins": 214, "inboxes": 3, "domains": 0, "seats": 1 },
"overage": { "emails": 0, "ai_categorizations": 0, "reply_drafts": 0, "estimated_usd": 0 },
"allow_overage": false,
"addons": [{ "key": "inboxes_100", "name": "+100 inboxes", "quantity": 1, "price_monthly": 3, "limit": "inboxes", "adds": 100 }],
"retention": { "days": 30, "plan_days": 30, "org_days": null }
}