Pricing and limits
Free and claimed tiers, size caps, rate limits, and token lifetimes for Charming apps.
The first anonymous app needs no signup and lasts 7 days. Claim it to keep it. There is no enforced app cap per account today. The OpenAPI specification is the machine-readable source for current limits.
Tiers at a glance
| Tier | Signup | App persistence |
|---|---|---|
| Anonymous | Not required | 7 days from creation |
| Claimed | Required | Indefinite |
See Authentication for token scope, claim, recovery, and revocation.
Per-account limits
| Resource | Limit |
|---|---|
| Apps per user | No enforced cap today (fair use) |
list_apps default page size |
50 |
list_apps max page size |
100 |
| Routines per user | 25 (routine_limit_exceeded, 429) |
Listing only returns the caller’s apps (owned + collaborator); paging uses an opaque cursor.
Per-app limits
| Resource | Cap | Error kind on breach |
|---|---|---|
module source |
256 KiB | module_too_large (413) |
ui source |
256 KiB | ui_too_large (413) |
description |
500 JS chars | description_too_long (400) |
starter_prompt |
2000 JS chars | invalid_input (400) |
Feedback text body |
16 KiB chars | text_too_large (413) |
Feedback structuredData (serialized) |
64 KiB | structured_data_too_large (413) |
| Feedback wire body | 96 KiB | payload_too_large (413) |
Edits batch (PATCH /app/{id}/source) |
per-string + per-batch cap | edits_too_large (413) |
Diag report body (POST /app/{id}/diag/report) |
16 KiB | payload_too_large (413) |
| Load-error report body | 2 KiB | payload_too_large (413) |
| Per-app asset size | 10 MiB | asset_too_large (413) |
| Per-app asset count | 50 | asset_count_exceeded (413) |
| Per-app asset total | 100 MiB | asset_quota_exceeded (413) |
env.storage, one value |
10 MiB | stored_value_too_large (413) |
env.storage, all values |
100 MiB | storage_full (413) |
| Per-app secret value | 8 KiB | secret_too_large (400) |
| Per-app secret name uniqueness | one row per (scope, manifestId, name) |
secret_already_exists (409) |
| Per-app diag ring buffer | 50 entries (oldest rolls off) | None |
| Durable activity events | 90-day retention, kind-capped | None |
| Routines per app | 3 | routine_limit_exceeded (429) |
| Routine interval floor | hourly | invalid_interval (400) |
| Routine auto-disable | 5 consecutive tick failures | None (see Routines) |
Both env.storage caps apply. Delete or shrink a key to make room. Everyone using an app shares one store, the owner’s; anonymous visitors on a custom domain get their own.
Network access
Backend requests must use HTTPS and declare each exact origin. Private, loopback, link-local, and cloud-metadata addresses are blocked, including after redirects. Sealed env.fetch responses are capped at 5 MiB. See the build reference for capabilities, permissions, and secret handling.
Rate limits
Token-bucket limiters, in-memory, per process. All windows are 60 seconds.
| Route family | Per (app, IP) |
Per app | Per IP | Per user |
|---|---|---|---|---|
POST /app/{id}/diag/report |
30/min | 200/min | None | None |
POST /app/{id}/load-error/report |
30/min | 200/min | None | None |
POST /app/{id}/feedback |
30/min | 200/min | None | None |
POST /app/{id}/assets / DELETE /app/{id}/assets/{key} |
throttled per app | None | None | None |
POST /api/pair/start |
None | None | 30/min | None |
POST /api/pair/poll |
None | None | 60/min | None |
POST /api/pair/approve |
None | None | None | 20/min |
A breach returns 429 with error.kind: "rate_limited" (or "too_many_requests" for asset writes).
Rate-limit response headers
Every rate-limited route emits the draft-IETF RateLimit-* headers (plus their legacy X-RateLimit-* aliases for older agent tooling) on both success and 429 responses so a client can project headroom without busy-polling:
| Header | Value | When |
|---|---|---|
RateLimit-Limit |
Capacity of the most-restrictive bucket gating this request (e.g. 30). |
every response |
RateLimit-Remaining |
Floored whole tokens left in that bucket. 0 on the response that exhausts it. |
every response |
RateLimit-Reset |
Integer seconds until the next whole token. 0 while remaining > 0. |
every response |
X-RateLimit-Limit / X-RateLimit-Remaining / X-RateLimit-Reset |
Legacy aliases with the same values. | every response |
Retry-After |
Integer seconds the client should wait before retrying. Equals RateLimit-Reset on every 429. |
429 only |
The numbers reflect the most-restrictive bucket among all that gate the request (e.g. (app, IP) vs (app) for diag/feedback/load-error). The Retry-After value lets generic HTTP clients back off without parsing the body.
Token TTLs
| Token | Default TTL | Notes |
|---|---|---|
chrm_app_* (anonymous create) |
Until claimed or app reaped (7 days unclaimed) | Authorises one app; revoked on claim |
chrm_user_* (POST /api/token) |
7 days | Personal access token; rotate by minting a new one |
chrm_user_* (paired via POST /api/pair/start) |
30 days | Issued when a user approves a device-pairing grant |
chrm_render_* (server-minted at render) |
7 days (rotates on re-render; previous hash kept live for a ~15-minute grace window) | Bound to one app id; not user-mintable |
Device-pairing grant (device_code + user_code) |
10 minutes (hard cap 60 minutes with active polling) | RFC 8628-shaped; user_code displayed to human |
chrm_user_* token revocation is immediate (DELETE /api/token/{id}). The Connections page in Account settings is the user-facing inventory; programmatic listing is GET /api/token. See the authentication reference for the full revocation surface.
What is NOT metered today
- App create / update calls (no per-minute write limit on
POST /apporPUT /app/{id}) - App reads (
GET /app/{id},GET /app/{id}/source,GET /app/{id}/describe,GET /app/{id}/agent.json) - MCP
query_app/mutate_appcalls. These run the app’s own handler.
The route limits cover paths an anonymous caller can hit: diagnostics, feedback, asset writes, and pairing. Authenticated PUT and PATCH writes use the bearer token instead of a per-minute counter.
When numbers change
Fetch the OpenAPI specification for the live values.