Billing & Usage
Check a project's tier and remaining headroom, cap monthly spend, read the tier catalog, and open checkout or the billing portal — pricing as an API your agent can reason about.
Billing is part of the API. A project sits on a tier with included quotas.
By default, writes on a meter stop at its included quota (throttle mode); a
project in overage_bill mode keeps going and accrues overage, up to a spend
ceiling you set. You can read live usage and headroom, set or remove the
spend ceiling, and start an upgrade — all programmatically, so an agent can
self-throttle or escalate to a human before running a data-heavy workload.
Billing routes mount under /v1/admin/projects/:projectId/billing/* and
require developer credentials (a PAT or console session). Server keys
(amb_*_sk_*) are rejected with 403 BILLING_REQUIRES_DEVELOPER_AUTH —
billing is an owner action, not a runtime one.
Status
GET .../billing/status returns the stored and effective tiers, per-meter usage and cost,
the billing period and when it resets, projected overage, the spend ceiling
and how much of it is consumed, the live enforcement state, the
database limits block, and —
importantly for agents — human_action_required, a single field telling
you whether it's safe to proceed unattended.
Use effective_tier for automation that makes quota or feature decisions.
tier remains the persisted subscription/accounting value. The fields normally
match; for operator-comped projects effective_tier is enterprise because
runtime quota and spend-ceiling refusals are waived, while tier preserves the
stored subscription class. Check comped to distinguish that grant from a paid
Enterprise subscription.
enforcement is the live verdict the API itself acts on: when read_only
is true, metered write operations return 402 until the period
resets or the ceiling changes (see Spend ceiling);
over_quota_meters lists meters whose included quota is already consumed —
writes on those meters are refused on throttle mode (402 TIER_QUOTA_EXCEEDED, except mau, where new-user signups keep their
long-standing 429 MAU_CAP_REACHED shape).
Three overage figures, deliberately distinct so the status never overstates what you owe:
| Field | Meaning |
|---|---|
projected_overage_usd_this_month | Live estimate of overage from current usage vs. your included quotas. |
recorded_overage_usd_this_period | Overage tallied into your usage ledger for the current billing period. |
billed_overage_usd_this_period | Overage actually added to your Stripe invoice so far this period. |
metered_billing_live | Whether metered overage is being charged yet (see How overage is collected). |
human_action_required is one of:
| Value | Meaning |
|---|---|
none | Safe to proceed unattended. |
verify_email | The project owner hasn't verified their email; nothing unlocks first. |
approve_checkout | Free tier is near a quota or database cap; a human should approve an upgrade. |
set_ceiling | Paid tier is projecting significant overage with no ceiling set. |
An agent should call this before provisioning new features or pushing large workloads.
Spend ceiling
Cap the monthly bill with PUT .../billing/ceiling. When the current
period's overage cost reaches the ceiling, the project degrades to
read-only mode: metered write operations — tracking events, inserting
or updating collection rows, media uploads, push sends, new user signups —
return 402 SPEND_CEILING_REACHED with a machine-readable payload, while
reads (and deletes, which shrink usage) keep working. Pass
ceiling_usd: null to remove the cap; an overage_bill project then accrues
overage with no limit.
The same contract applies to tier quotas on throttle mode (the default):
once a meter's included quota is consumed, writes on that meter return
402 TIER_QUOTA_EXCEEDED with the identical payload shape (reason: "quota"). New-user signups keep their long-standing 429 MAU_CAP_REACHED
shape at the MAU quota. Enforcement takes effect within seconds of a
ceiling change and resets automatically when the billing period rolls.
So you're never surprised by either the bill or the read-only flip, two
control-plane webhook events fire as the ceiling fills — subscribe with
amba_control_webhooks_create (or billing.*):
| Event | Fires |
|---|---|
billing.ceiling_warning | At 80% of the ceiling, once per billing period. |
billing.ceiling_reached | At 100% — the project is now read-only, once per period. |
ceiling_usd must be a finite number between 0 and 100000, or null.
Database limits (free tier)
Every project runs on its own database. Free projects get a small one with two hard caps, so a prototype can never run up a bill:
| Limit | Free | Pro, Scale |
|---|---|---|
| Compute size | 0.25 compute units | Up to 1 unit |
| Compute per billing month | 10 CU-hours (about 40 hours awake at 0.25 units) | No cap |
| Database size | 512 MB (the whole database, including system tables) | No hard cap |
A database sleeps after a minute without queries and wakes on the next request, so an app used in bursts spends its compute hours on real traffic. The compute allowance resets on the 1st of each month (UTC).
At the compute cap the database pauses for the rest of the billing
period. Every call that touches the database returns
402 FREE_TIER_LIMIT_REACHED until the period resets or the project
upgrades; an upgrade resumes the database within seconds.
At the storage cap writes that add data return
402 FREE_TIER_LIMIT_REACHED. Reads and deletes keep working, so deleting
data brings the project back under the limit.
The 402 body names the limit, the reset date, and the one-call upgrade:
limit is database_compute or database_storage; resets_at is null
for storage, which is a size limit. Usage is measured hourly and shows in the
database block of billing status (compute.used_cu_hours,
compute.limit_cu_hours, storage.used_mb, period.resets_at,
limit_reached), in amba billing status, and on the console billing page.
At 80% of the compute allowance the project owner gets one email per billing
period, and human_action_required becomes approve_checkout.
Upgrade in one call with the amba_billing_upgrade MCP tool,
amba billing upgrade --tier pro, or POST .../billing/checkout (see
Upgrade & manage). An app with real users belongs on a
paid plan, where the database has no compute cap.
Tiers
The tier catalog (names, prices, included quotas, overage rates) is
available offline through the amba_billing_tiers MCP tool, so an agent
can reason about upgrades without a network call. It mirrors the pricing
page at amba.dev. The shipped tiers are
free, pro, scale, and enterprise.
Every tier includes every capability you build an app with. Collections, functions, push, gamification, economy, social, AI, sites, and sign-in are available on every tier, including free. Tiers differ by the quotas in the table below. Free projects also carry the free-tier limits on the surfaces that publish on Amba's domains, send messages through Amba's sending reputation, or spend Amba's money.
| Capability | Free | Pro | Scale | Enterprise |
|---|---|---|---|---|
| Price (monthly) | $0 | $20 | $200 | Custom |
| Price (annual, per month) | $0 | $16 | $160 | Custom |
| Monthly active users (MAU) | 1,000 | 25,000 | 250,000 | Custom |
| Engagement events / mo | 10,000 | 250,000 | 2,500,000 | Custom |
| Push notifications / mo | 1,000 | 50,000 | 500,000 | Custom |
| Database storage | 100 MB | 1 GB | 5 GB | Custom |
| Media storage | 250 MB | 1 GB | 25 GB | Custom |
| Database compute / mo | 10 CU-hours | No cap | No cap | Custom |
| Database size (hard cap) | 512 MB | No cap | No cap | Custom |
| All feature categories | Yes | Yes | Yes | Yes |
| Projects per account | 2 | Unlimited | Unlimited | Custom |
| Sleeps after inactivity | 14 days | Never | Never | Never |
Projects run in throttle mode by default (mode in GET .../billing/status):
once a meter's included quota is used, metered writes on it return
402 TIER_QUOTA_EXCEEDED (new-user signups return 429 MAU_CAP_REACHED) until
the billing period resets or the project upgrades. A paid project in
overage_bill mode continues past its included quotas and accrues overage at
the rates below, up to its spend ceiling when one is set.
Amba sets the mode per project; no API or MCP tool changes it.
| Metered axis | Overage rate |
|---|---|
| MAU | $0.50 per 1,000 MAU |
| Engagement events | $0.50 per 10,000 events |
| Push | $0.50 per 10,000 push |
| Database storage | $1.50 per GB-month |
| Media storage | $0.10 per GB-month |
| Telemetry events | $0.10 per 1,000,000 |
| Agent tool calls | $0.50 per 10,000 calls |
Telemetry events — emitted via Amba.track(name, props, { telemetry: true }) —
are billed separately and far cheaper than engagement events because they
don't fan out to segments, workflows, or push. Use them for high-volume
analytics signals you don't need to drive engagement off.
Telemetry events are stored and readable. List or count them with
source=telemetry on the events endpoints,
with source: "telemetry" on the amba_events_list and amba_events_count
MCP tools, or with amba analytics export --type=events --source=telemetry.
source=all reads engagement and telemetry events together.
Agent tool calls — tools/call requests against your project's own
app MCP — are priced like engagement events. Like
telemetry, they have no included quota; every successful call to a collection
or function tool meters. The whoami and list_collections meta tools are
free, and each client key has a per-minute and per-day tool-call budget (see
Metering).
Free-tier limits
A free project gets everything it needs to build and test an app. The surfaces below run on Amba's own domains, sending reputation, phone number, registrar account, or compute, so on the free tier they are either paid features or capped per day. Upgrading the project lifts every one of them.
| Surface | Free tier | Limit id |
|---|---|---|
Hosted site on *.app.amba.host | Serves with a "Built with Amba · preview" bar and noindex | (no refusal) |
| Custom domains (sites and functions) | Paid plans | custom_domains |
| Domain purchase | Paid plans | domain_purchase |
| Sign-in email (magic link, email OTP) | 25 per day | auth_emails_per_day |
| Sign-in SMS codes | 10 per day across all numbers | sms_otp_per_day |
| Email to any recipient, custom templates | Paid plans (includes ctx.email.send in functions and /v1/client/email/send) | email_send, email_templates |
| Push campaigns (immediate, scheduled) | 3 per day; drafting is unlimited | push_campaign_sends_per_day |
| Direct pushes (test pushes, per-user schedules, transactional pushes counted per user) | 100 per day | push_direct_sends_per_day |
| Function invocations | 10,000 per day (schedules, queues, and fan-out included), 50 ms CPU each | function_invocations_per_day |
| Function schedules | 2 schedules, at most hourly (5-field cron, minute field a single number) | function_schedules, function_schedule_interval |
| Queue messages | 1,000 per day | queue_messages_per_day |
| Tracked links | 50 new links per day | tracked_links_per_day |
| App builder | Paid plans | app_builder |
| Media storage | 250 MB kept; every upload registration needs size_bytes and the upload URL is capped at it; deleting media frees space | media_storage |
| Hosted site files | 50 MB across every site and kept deployment, at most 50 MB and 1,000 files per deploy; older deployments are dropped for room | site_storage, site_files |
Media and files on *.cdn.amba.host | On every plan: images, video and audio serve inline; HTML, SVG, XML, scripts and PDFs download; every response has a sandbox CSP | (no refusal) |
Function responses on *.fn.amba.host | JSON serves as-is to apps; every response carries a sandbox CSP, so HTML never runs as a page | (no refusal) |
AI requests always use your project's own provider keys, on every tier.
A refused call returns 402 with a machine-readable body. The message says
which limit was hit, why it exists, and the exact upgrade call:
Daily caps reset at 00:00 UTC. Projects created before these limits launched keep their previous behaviour.
When Amba cannot confirm a free project's limits for a moment (its daily counter
or plan lookup is briefly unreachable), a call that would send, charge, or start
work answers 503 LIMITS_UNAVAILABLE with a Retry-After header, and nothing
is sent or charged. Retry after the given seconds. Paid and comped projects never see this, and reads keep working.
The two-free-projects limit counts the projects you own and the free projects
you joined, on every path that adds one (create, POST /v1/admin/provision,
accepting an invite). It lifts once a project has an active paid subscription
(or the account is comped); starting a checkout and leaving it unpaid changes
nothing. App-factory children provisioned under a paying parent org do not
count against it.
Unclaimed accounts
An account is unclaimed until its owner proves they control its inbox. An
agent can create an account with amba_developer_signup before any human is
involved; it gets a sandbox-…@layers.com address with no inbox at all. A
password signup carries a real address that nobody has confirmed yet, and the
verify_token that signup returns does not count, because the caller already
holds it. Nobody can be reached about an unclaimed account's apps, so its free
projects follow a fixed timeline, and it draws new projects from a small daily
slice of platform capacity.
Three things prove the inbox, and each one ends the timeline at once:
- the claim link:
amba_developer_claimsends a confirmation link to the owner's address (an account that already uses that address claims the same one); - signing in with an emailed code (
POST /v1/auth/developer/otp/request, then/v1/auth/developer/otp/verify); - signing in with GitHub when the GitHub account's verified primary email is the account address.
Each project runs its own clock. The clock starts at the latest of these moments: the account was created, the project was created, or the project later became subject to the timeline (free-tier limits turned on for it, a move to the Free plan, or a transfer into the account). A project created in an account that is already months old gets the whole timeline from day 0.
| Project clock | What happens |
|---|---|
| 0–6 days | Normal. |
| 7–13 days | Every admin API response carries an X-Amba-Notice header and every MCP tool result carries a notice with the one-call claim instruction. The notice names the project and its dates. |
| 14 days | The project goes on hold: reads keep working (including collection queries and session refresh), writes return 403 SANDBOX_CLAIM_REQUIRED, functions stop, and hosted sites stop serving. |
| 90 days, after 76 days on hold | The project is archived. Every request returns 403 SANDBOX_CLAIM_REQUIRED; billing, invites, and ownership transfer stay open. |
Archiving deletes nothing. The project's database and its stored files (media,
files its functions wrote, and hosted site deployments) are kept exactly as
they were, and no later step deletes them. While a project is archived its data cannot be
read or exported. A claim, an upgrade, or a transfer to a claimed account
brings it back in the same request, and every read and export (collection
rows, amba_users_export) works again at once. A project only reaches
the archive after a full 76-day hold, during which every write to it is
refused.
Claiming the account ends the timeline at any point and lifts a hold or an archive in the same request:
The owner clicks the emailed link (valid for 15 minutes) and the account takes their address. Upgrading a held project to a paid plan also lifts its hold.
Accounts whose owner has not proven the inbox draw new projects from a small
daily capacity slice, and each network and each company or custom email domain
gets a few of them a day. While the slice has room, signup returns the project
at once (see Getting access). When it
is spent, signup still creates the account and returns project_unavailable
in place of project, with details.reason: "inbox_proof_required" and the
claim call; the account gets its first project once the owner proves the
inbox. A new project on an existing unclaimed account returns
503 PLATFORM_CAPACITY with details.pool: "unproven" and the claim call.
Claim, then create the project with amba_projects_create.
Account emails (claim links, sign-in codes, invites) run on durable daily
budgets per account, per address, and platform-wide; a refusal is
429 EMAIL_BUDGET_EXCEEDED with details.scope and a Retry-After header.
One claim email goes to an address per 10 minutes, and asking again sooner
returns 429 CLAIM_EMAIL_RECENTLY_SENT while the first link keeps working.
How overage is collected
When a project's spend mode is overage_bill (rather than throttle),
requests past your included quotas are allowed through and the overage is
tallied into a per-period usage ledger. A daily reconciliation totals each
metered axis for the current billing period and adds the billable amount to
your upcoming invoice — so overage rolls into your next invoice rather
than being charged per-request.
Metered overage billing is rolling out behind a flag. While it's being enabled, usage is still
tracked and shown in GET .../billing/status (recorded_overage_usd_this_period), but nothing is
charged — metered_billing_live is false and billed_overage_usd_this_period stays 0. Once
enabled, the recorded overage is added to your invoice and metered_billing_live flips to true.
Set a spend ceiling if you want hard read-only enforcement (402) instead of
accruing charges.
These numbers come straight from the live tier catalog and are reproduced here for reference. If
they ever appear to differ from the amba_billing_tiers MCP tool or
amba.dev/#pricing, treat those as authoritative — they read the same
source.
Upgrade & manage
Two routes return hosted URLs you redirect the human to — these flows need a browser, so they're surfaced to a person rather than driven by an agent:
| Method | Path | Returns |
|---|---|---|
| GET | /admin/projects/:projectId/billing/status | Live billing state (above). |
| PUT | /admin/projects/:projectId/billing/ceiling | Set / clear the spend ceiling. |
| POST | /admin/projects/:projectId/billing/checkout | { url } — checkout for an upgrade. |
| POST | /admin/projects/:projectId/billing/portal | { url } — manage card, plan, invoices. |
POST .../checkout takes { tier: "pro" | "scale", interval: "month" | "year" }
and returns a checkout url. The amba_billing_upgrade MCP tool and
amba billing upgrade --tier pro make the same call and hand back the link
for the owner to open. If the project already has an active
subscription, it returns 409 SUBSCRIPTION_EXISTS — use the portal to
change plan instead. A project has one open checkout link at a time: a new
checkout closes the link handed out before, and a checkout started while
another for the same project is running returns 409 CHECKOUT_IN_PROGRESS;
retry in a moment. POST .../portal opens the billing portal. On a project
with an active subscription, any project owner or admin gets a session for
that subscription (change or cancel the plan). The full portal for the
payment account (cards, every invoice, every subscription the organization
pays for) is open to the owner of the organization that pays for the project;
anyone else gets 403 BILLING_AUTHORITY_REQUIRED.
The paid tier follows the subscription's status. An active or trialing
subscription holds the tier. When a renewal payment fails the subscription
goes past_due and keeps the tier for a 14-day grace window while the
payment is retried; a successful retry keeps it, and once the window has
passed the project drops to the free tier until the payment succeeds. A
canceled, unpaid, incomplete_expired or paused subscription leaves
the project on the free tier, and a first payment that is still incomplete
grants nothing until it succeeds. A canceled subscription stays canceled:
billing updates are applied in the order they happened, whatever order they
arrive in, so a late update from before the cancellation never restores the
paid tier. A new subscription (a fresh checkout) does.
Deleting a project ends its subscription: the subscription is set to cancel
at the end of its current period, so the period already paid for is the last
one billed, and no overage is added after the delete. The delete answers
subscription.ends_at with the date billing stops. See
Delete a project.
MCP tools
| Tool | Does |
|---|---|
amba_billing_status | Stored/effective tier, headroom, overage, and required action. |
amba_billing_tiers | The full tier catalog (offline, no API call). |
amba_billing_set_ceiling | Set or remove the monthly spend ceiling. |
amba_billing_upgrade | Start a paid-plan checkout; lifts the free database caps. |