Amba

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; beyond those quotas, usage either accrues overage or is throttled at a 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.

curl 'https://api.amba.dev/v1/admin/projects/$PROJECT_ID/billing/status' \
  -H 'Authorization: Bearer $AMBA_PAT'
{
  "data": {
    "tier": "pro",
    "effective_tier": "pro",
    "comped": false,
    "headroom": {
      "mau": { "used": 4200, "included": 25000, "pct": 0.168 },
      "engagement_events": { "used": 120000, "included": 250000, "pct": 0.48 },
      "telemetry_events": { "used": 0, "included": null, "pct": null },
      "push": { "used": 3000, "included": 50000, "pct": 0.06 },
      "db_storage_mb": { "used": 210, "included": 1024, "pct": 0.205 }
    },
    "projected_overage_usd_this_month": 0,
    "recorded_overage_usd_this_period": 0,
    "billed_overage_usd_this_period": 0,
    "metered_billing_live": false,
    "ceiling_usd": null,
    "mode": "throttle",
    "paused_at": null,
    "subscription_status": "active",
    "current_period_end": "2026-06-01T00:00:00.000Z",
    "human_action_required": "none",
    "period": { "start": "2026-05-01", "resets_at": "2026-06-01T00:00:00.000Z" },
    "usage_cost_by_meter": {
      "mau": { "used": 4200, "included_quota": 25000, "overage_units": 0, "cost_usd": 0 },
      "engagement_events": {
        "used": 120000,
        "included_quota": 250000,
        "overage_units": 0,
        "cost_usd": 0
      },
      "telemetry_events": { "used": 0, "included_quota": null, "overage_units": 0, "cost_usd": 0 },
      "push": { "used": 3000, "included_quota": 50000, "overage_units": 0, "cost_usd": 0 },
      "db_storage_mb": { "used": 210, "included_quota": 1024, "overage_units": 0, "cost_usd": 0 },
      "media_storage_mb": { "used": 0, "included_quota": 1024, "overage_units": 0, "cost_usd": 0 },
      "app_mcp_tool_calls": {
        "used": 1400,
        "included_quota": null,
        "overage_units": 1400,
        "cost_usd": 0.07
      }
    },
    "ceiling_pct_consumed": null,
    "projected_end_of_period_overage_usd": 0.07,
    "enforcement": { "read_only": false, "reason": null, "over_quota_meters": [] },
    "database": {
      "limits_enforced": true,
      "source": "tier",
      "compute": { "max_cu": 1, "used_cu_hours": 61.2, "limit_cu_hours": null, "pct": null },
      "storage": { "used_mb": 214.5, "limit_mb": null, "pct": null },
      "period": null,
      "limit_reached": [],
      "observed_at": "2026-05-14T09:17:00.000Z",
      "upgrade": null
    }
  }
}

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:

FieldMeaning
projected_overage_usd_this_monthLive estimate of overage from current usage vs. your included quotas.
recorded_overage_usd_this_periodOverage tallied into your usage ledger for the current billing period.
billed_overage_usd_this_periodOverage actually added to your Stripe invoice so far this period.
metered_billing_liveWhether metered overage is being charged yet (see How overage is collected).

human_action_required is one of:

ValueMeaning
noneSafe to proceed unattended.
verify_emailThe project owner hasn't verified their email; nothing unlocks first.
approve_checkoutFree tier is near a quota or database cap; a human should approve an upgrade.
set_ceilingPaid 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 (linear overage, no limit).

{
  "error": {
    "code": "SPEND_CEILING_REACHED",
    "message": "This project reached its $250 spend ceiling for the current billing period…",
    "details": {
      "read_only": true,
      "reason": "ceiling",
      "meter": "engagement_events",
      "current_usage_usd": 250.4,
      "ceiling_usd": 250,
      "ceiling_pct_consumed": 1.0016,
      "resets_at": "2026-07-01T00:00:00.000Z",
      "billing_url": "https://app.amba.dev/billing"
    }
  }
}

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.*):

EventFires
billing.ceiling_warningAt 80% of the ceiling, once per billing period.
billing.ceiling_reachedAt 100% — the project is now read-only, once per period.
curl -X PUT 'https://api.amba.dev/v1/admin/projects/$PROJECT_ID/billing/ceiling' \
  -H 'Authorization: Bearer $AMBA_PAT' \
  -H 'Content-Type: application/json' \
  -d '{ "ceiling_usd": 250 }'
{ "data": { "ceiling_usd": 250 } }

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:

LimitFreePro, Scale
Compute size0.25 compute unitsUp to 1 unit
Compute per billing month10 CU-hours (about 40 hours awake at 0.25 units)No cap
Database size512 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:

{
  "error": {
    "code": "FREE_TIER_LIMIT_REACHED",
    "message": "This project used its free-tier database compute for the current billing period (10 CU-hours), so its database is paused until 2026-11-01…",
    "details": {
      "limit": "database_compute",
      "tier": "free",
      "used": 10.02,
      "cap": 10,
      "unit": "cu_hours",
      "resets_at": "2026-11-01T00:00:00.000Z",
      "billing_url": "https://app.amba.dev/projects/PROJECT_ID/billing",
      "upgrade": {
        "mcp_tool": "amba_billing_upgrade",
        "mcp_args": { "project_id": "PROJECT_ID", "tier": "pro" },
        "http": {
          "method": "POST",
          "path": "/v1/admin/projects/PROJECT_ID/billing/checkout",
          "body": { "tier": "pro", "interval": "month" }
        },
        "cli": "amba billing upgrade --tier pro"
      }
    }
  }
}

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.

CapabilityFreeProScaleEnterprise
Price (monthly)$0$20$200Custom
Price (annual, per month)$0$16$160Custom
Monthly active users (MAU)1,00025,000250,000Custom
Engagement events / mo10,000250,0002,500,000Custom
Push notifications / mo1,00050,000500,000Custom
Database storage100 MB1 GB5 GBCustom
Media storage250 MB1 GB25 GBCustom
Database compute / mo10 CU-hoursNo capNo capCustom
Database size (hard cap)512 MBNo capNo capCustom
All feature categoriesYesYesYesYes
Projects per account2UnlimitedUnlimitedCustom
Sleeps after inactivity14 daysNeverNeverNever

Beyond the included quotas, usage on a paid tier accrues linear overage (unless you set a spend ceiling):

Metered axisOverage 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.

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

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.

SurfaceFree tierLimit id
Hosted site on *.app.amba.hostServes with a "Built with Amba · preview" bar and noindex(no refusal)
Custom domains (sites and functions)Paid planscustom_domains
Domain purchasePaid plansdomain_purchase
Sign-in email (magic link, email OTP)25 per dayauth_emails_per_day
Sign-in SMS codes10 per day across all numberssms_otp_per_day
Email to any recipient, custom templatesPaid 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 unlimitedpush_campaign_sends_per_day
Direct pushes (test pushes, per-user schedules)100 per daypush_direct_sends_per_day
Function invocations10,000 per day (schedules, queues, and fan-out included), 50 ms CPU eachfunction_invocations_per_day
Function schedules2 schedules, at most hourly (5-field cron, minute field a single number)function_schedules, function_schedule_interval
Queue messages1,000 per dayqueue_messages_per_day
Tracked links50 new links per daytracked_links_per_day
App builderPaid plansapp_builder
Media storage250 MB kept; every upload registration needs size_bytes and the upload URL is capped at it; deleting media frees spacemedia_storage
Hosted site files50 MB across every site and kept deployment, at most 50 MB and 1,000 files per deploy; older deployments are dropped for roomsite_storage, site_files
Media and files on *.cdn.amba.hostImages, video, and audio serve inline; HTML, SVG, XML, and scripts download as attachments; every response carries a sandbox CSP(no refusal)
Function responses on *.fn.amba.hostJSON 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:

{
  "error": {
    "code": "FREE_TIER_LIMIT_REACHED",
    "message": "This project sent its 25 free sign-in emails ... Upgrade in one call: POST /v1/admin/projects/<id>/billing/checkout {\"tier\":\"pro\"} ...",
    "details": {
      "limit": "auth_emails_per_day",
      "tier": "free",
      "used": 25,
      "cap": 25,
      "unit": "emails",
      "resets_at": "2026-10-03T00:00:00.000Z",
      "billing_url": "https://app.amba.dev/projects/<id>/billing",
      "upgrade": {
        "mcp_tool": "amba_billing_upgrade",
        "mcp_args": { "project_id": "<id>", "tier": "pro" },
        "http": {
          "method": "POST",
          "path": "/v1/admin/projects/<id>/billing/checkout",
          "body": { "tier": "pro", "interval": "month" }
        },
        "cli": "amba billing upgrade --tier pro"
      }
    }
  }
}

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_claim sends a one-click 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 clockWhat happens
0–6 daysNormal.
7–13 daysEvery 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 daysThe 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 holdThe project is archived. Every request returns 403 SANDBOX_CLAIM_REQUIRED; billing, invites, and ownership transfer stay open.

Archiving deletes nothing. The project's database is kept exactly as it was, and no later step deletes it. 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:

# MCP
amba_developer_claim { "email": "owner@example.com" }
# HTTP, with the account's PAT
POST /v1/auth/developer/claim  { "email": "owner@example.com" }
# CLI
amba claim owner@example.com

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.

When the day's capacity for unclaimed accounts is spent, a new project returns 503 PLATFORM_CAPACITY with details.pool: "unproven" and the claim call; signup still creates the account and returns project_unavailable in place of project. 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:

MethodPathReturns
GET/admin/projects/:projectId/billing/statusLive billing state (above).
PUT/admin/projects/:projectId/billing/ceilingSet / 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. POST .../portal opens the billing portal so the owner can update their card, change plan, or download invoices.

MCP tools

ToolDoes
amba_billing_statusStored/effective tier, headroom, overage, and required action.
amba_billing_tiersThe full tier catalog (offline, no API call).
amba_billing_set_ceilingSet or remove the monthly spend ceiling.
amba_billing_upgradeStart a paid-plan checkout; lifts the free database caps.

On this page