Skip to content
Charming Docs
Esc
navigateopen⌘Jpreview
On this page

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 /app or PUT /app/{id})
  • App reads (GET /app/{id}, GET /app/{id}/source, GET /app/{id}/describe, GET /app/{id}/agent.json)
  • MCP query_app / mutate_app calls. 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.

Was this page helpful?