---
title: Pricing and limits
description: Free and claimed tiers, size caps, rate limits, and token lifetimes for Charming apps.
sidebar:
  label: Pricing and limits
  order: 10
---

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](https://charm.ing/.well-known/openapi.json) 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](/docs/technical-reference/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](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](https://charm.ing/docs/build-http.md) 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](https://charm.ing/docs/technical-reference/authentication.md) 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](https://charm.ing/.well-known/openapi.json) for the live values.

Found a bug or need a feature? [Tell us](/docs/capabilities/feedback) with `submit_feedback` or `POST /app/{id}/feedback`. Your feedback shapes what we build next.
