Skip to content
Esc
↑↓navigate↵open⌘Jpreview

Subscribe to the live-state SSE stream for an app

Server-Sent Events stream of state-changed notifications. Each event id is <server-epoch>:<rev>; the browser EventSource replays via Last-Event-ID on reconnect, and the server emits a synthetic state-changed event with source: "reconnect-resync" and result: null whenever the client’s id belongs to an older epoch or claims an unseen revision. The overcapacity event is emitted (and the stream closed) when the per-app subscriber cap is reached. Heartbeats are SSE comment lines (: ping) every 15s.

Auth posture is broader than the owner endpoints: appToken, userToken, renderToken (bearer or ?t= query), or the path-scoped buildy_sse_<id> HMAC cookie minted by the /app/{id} render-bridge when a render-token rendered the page. The {} (no-auth) option in security covers the cookie path — OpenAPI cannot model a per-app cookie name as a fixed scheme.

GET/app/{id}/events
Authorization

Optional — this operation also accepts unauthenticated requests.

AuthorizationBearer token (chrm_app_*) · header

Per-app token returned once by POST /app for anonymous creates. Authorises mutations and /api/* calls on that one app only.

or
AuthorizationBearer token (chrm_user_*) · header

User-scoped personal access token. Mint via POST /api/token after signing in, or via the device-pairing flow at POST /api/pair/start. Accepted by every operation that lists userToken in its security; operations that list only sessionCookie refuse it with 401 unauthorized.

or
AuthorizationBearer token (chrm_render_*) · header

Short-lived per-app token used by null-origin sandboxed iframes (Claude Desktop, ChatGPT Apps) to call back into the app without cookies. Minted server-side at render time and embedded in the runtime closure; not user-mintable. Bound to one app id.

or
tAPI key · query

URL-signed render token, equivalent to renderToken but carried in the query string for GET /app/{id} loads where iframes cannot attach Authorization headers (the MCP viewer fetch+srcdoc path). Bound to one app id; expires after ~24 hours.

Path parameters
idstringrequired
Query parameters
tstring

URL-signed render token, equivalent to the renderToken bearer but carried in the query string for SSE connections from null-origin iframes that cannot attach Authorization headers.

Header parameters
Last-Event-IDstring

Last event id the client saw, in the <epoch>:<rev> form the server emits. Triggers a synthetic reconnect-resync event when the epoch differs or the rev is forward-jumped.

Responses
200

Open SSE stream. Frames arrive as event: state-changed (or event: overcapacity) with a JSON data: payload. The stream stays open until the client disconnects, the per-app capacity cap is hit, or the server shuts down.

One of:
AppEventStateChanged
kindstringrequired
Allowed:state-changed
appIdstringrequired
opstring | nullrequired

Operation name that produced the state change, or null on synthetic reconnect-resync events.

sourcestringrequired

agent for writes routed through the runtime; reconnect-resync for the server-generated catch-up signal.

Allowed:agentreconnect-resync
tsintegerrequired

Unix milliseconds at the time the frame was written.

resultanyrequired

Operation result body when JSON-shaped, null on reconnect-resync events and on operations whose response was non-JSON or exceeded the 32 KiB cap.

AppEventOvercapacity
messagestringrequired
Allowed:too_many_subscribers
400

Invalid app id. Error kind: invalid_id.

okbooleanrequired
Allowed:false
errorobjectrequired
Show properties
kindstringrequired

Stable enum-shaped error key. Branch on this for recovery flows.

Allowed:not_foundunknown_operationoperation_not_foundmethod_not_allowedoperation_failedapp_busyvisitor_rate_limitedservice_busyunauthorizedsign_in_requiredtoken_expiredforbiddenaccount_not_joinedforbidden_writestored_value_too_largestorage_fullinvalid_moduleinvalid_uiunpinned_cdn_scriptinvalid_route_schemainvalid_routes_shapecontract_migration_requiredinvalid_manifest_schemainvalid_manifest_exportsinvalid_requestinvalid_inputinvalid_outputunexpected_keyscapability_deniedcapability_errorexecutor_errorinvalid_call_shapealready_claimedmanifest_not_foundpayload_too_largemodule_too_largeui_too_largedescription_too_longmissing_descriptioninvalid_descriptionrate_limitedimage_origin_deniedtoo_many_requestsexpiredalready_approvedanon_not_remixabletemplate_listing_incompletetemplate_media_invalidforbidden_template_editinvalid_if_matchprecondition_requiredexpected_revision_requiredidempotency_key_requiredidempotency_conflictcontract_not_enabledapp_build_capacityapp_build_failedapp_build_expiredapp_build_authority_lostapp_build_unavailableapp_build_unsupportedversion_history_unavailableversion_not_restorablerevision_mismatchinvalid_revisioncandidate_effects_unsupportedold_string_not_foundold_string_not_uniqueedits_too_largemanifest_id_conflictmanifest_id_immutabletext_too_largestructured_data_too_largeapp_not_foundfeedback_not_foundfeedback_has_no_reply_recipientasset_too_largeasset_count_exceededasset_quota_exceededasset_invalid_keyasset_invalid_content_typeasset_errorinvalid_idsse_cookie_expiredinvalid_secret_namesecret_too_largesecret_already_existssecret_not_foundinvalid_iconteam_slug_takenalready_membergrantee_not_foundinvite_cap_reachedinvite_sender_blockedread_onlyhandle_takeninvalid_handlename_takeninvalid_domaindomain_takendomain_existsdomain_provider_unavailableapp_unclaimedlink_cap_reachedgrantee_is_ownergrantee_is_selfgrantee_already_has_accessalready_invitedroutine_limit_exceededinvalid_intervalop_requires_inputduplicate_routinewebhook_limit_exceededduplicate_webhookwebhook_disabledversion_limit_exceededplan_requiredstorage_limit_exceededcopy_data_forbiddencopy_data_too_large
messagestring
reasonstring

Set on 401s to distinguish token failure modes when finer detail is needed. token_revoked_post_claim is the post-#1284 surface for a chrm_app_* that was invalidated by a claim — the recovery field on the same error object carries the self-contained pairing-recovery hint the agent should follow instead of creating a new app.

Allowed:token_malformedtoken_unknowntoken_revokedtoken_revoked_post_claim
planstring

Present on every plan refusal: the tier the caller was refused under. Its presence is what marks an error as a plan refusal rather than a validation or permission failure.

Allowed:freeprobusinessenterprise
limitstring | integer

Which ceiling the call met, keyed by kind. A plan refusal (*_limit_exceeded) names the countable that ran out — version_history, routines_per_app, routines_per_owner, webhooks_per_app, webhooks_per_owner — and pairs it with max. An app_busy refusal names the bound it met and pairs it with observed and allowed; no plan lifts any of them. Two are per-app run bounds, concurrent_runs and runs_per_window; the third, render_token_refresh, is the page render asking for a new render-token secret, held by 10 per app per client IP and 60 per app across every address in a 60 second window, and observed/allowed name whichever of those two refused. An edits_too_large rejection carries the numeric cap that was exceeded instead of a name.

rungstring

On an app_busy refusal, how hard the app's own refusal ladder is refusing: throttled is the plain ceiling breach above; parked means a caller kept calling inside a wait it was given, so the server mints its own Retry-After (doubling from 60s to a 300s cap) and refuses until a quiet window passes even once the ceiling clears; closed is the same, plus the response closes the connection. Absent on every other kind.

Allowed:throttledparkedclosed
maxinteger

On a *_limit_exceeded refusal, that limit's number on the caller's plan.

min 0
featurestring

On a plan_required refusal, the capability the plan does not include. Never paired with limit / max.

Allowed:custom_domainwatermark_removalteams
upgradeUrlstring<uri>

Absolute link to the owner's own billing page, where the refusal is lifted — the team's page for a team-owned app. Omitted only when the owner has no handle and therefore no settings page of their own, so treat it as optional and fall back to your own copy. MCP appends it as an Upgrade: <url> last line; the CLI prints it under the message.

recoveryRecoveryPair | RecoveryRetry | RecoveryShrink | RecoveryRefetch | RecoveryFixRouteLabel | RecoveryRename | RecoveryMigrateContract

Self-contained recovery hint, branching on kind. Attached to error envelopes the agent can self-correct on: pair for token_revoked_post_claim 401s (#1284), retry for 429s, shrink for too-large / quota-exceeded 413s, refetch for 412 revision_mismatch / invalid_if_match / 428 precondition_required, fix_route_label for 403 forbidden_write, rename for 409 manifest_id_immutable / manifest_id_conflict, and migrate_contract for 422 contract_migration_required. Every instructions string is a single-paragraph, action-first walk-through. Inlined per ADR 2026-05-06-agent-facing-error-surfaces-self-contain-the-fix.

Show properties
One of:
RecoveryPair
kindstringrequired
Allowed:pair
startstring<uri>required
verification_urlstring<uri>required
instructionsstringrequired
RecoveryRetry
kindstringrequired
Allowed:retry
retry_after_secintegerrequired

Seconds to wait before retrying. Matches the RateLimit-Reset and Retry-After headers on the same response.

min 0
retry_after_urlstring<uri>required

Absolute URL to retry verbatim once the wait elapses.

instructionsstringrequired
RecoveryShrink
kindstringrequired
Allowed:shrink
fieldstringrequired

Which input tripped the cap.

Allowed:moduleuistylesdescriptiontextstructured_dataasseteditssecretpayload
size_bytesintegerrequired
min 0
cap_bytesintegerrequired
min 0
instructionsstringrequired
RecoveryRefetch
kindstringrequired
Allowed:refetch
refetch_urlstring<uri>required

Absolute URL to GET for the fresh ETag before retrying with a corrected If-Match.

instructionsstringrequired
RecoveryFixRouteLabel
kindstringrequired
Allowed:fix_route_label
instructionsstringrequired

App-code recovery: the fix is in the route declaration, NOT in the request. Retrying the same request will fail again.

RecoveryRename
kindstringrequired
Allowed:rename
instructionsstringrequired
RecoveryMigrateContract
kindstringrequired
Allowed:migrate_contract
instructionsstringrequired
401

Anonymous caller's buildy_sse_<id> cookie was once valid but its 1-hour TTL elapsed. Error kind: sse_cookie_expired — reload /app/{id} so the render-token bridge re-issues a fresh cookie.

okbooleanrequired
Allowed:false
errorobjectrequired
Show properties
kindstringrequired

Stable enum-shaped error key. Branch on this for recovery flows.

Allowed:not_foundunknown_operationoperation_not_foundmethod_not_allowedoperation_failedapp_busyvisitor_rate_limitedservice_busyunauthorizedsign_in_requiredtoken_expiredforbiddenaccount_not_joinedforbidden_writestored_value_too_largestorage_fullinvalid_moduleinvalid_uiunpinned_cdn_scriptinvalid_route_schemainvalid_routes_shapecontract_migration_requiredinvalid_manifest_schemainvalid_manifest_exportsinvalid_requestinvalid_inputinvalid_outputunexpected_keyscapability_deniedcapability_errorexecutor_errorinvalid_call_shapealready_claimedmanifest_not_foundpayload_too_largemodule_too_largeui_too_largedescription_too_longmissing_descriptioninvalid_descriptionrate_limitedimage_origin_deniedtoo_many_requestsexpiredalready_approvedanon_not_remixabletemplate_listing_incompletetemplate_media_invalidforbidden_template_editinvalid_if_matchprecondition_requiredexpected_revision_requiredidempotency_key_requiredidempotency_conflictcontract_not_enabledapp_build_capacityapp_build_failedapp_build_expiredapp_build_authority_lostapp_build_unavailableapp_build_unsupportedversion_history_unavailableversion_not_restorablerevision_mismatchinvalid_revisioncandidate_effects_unsupportedold_string_not_foundold_string_not_uniqueedits_too_largemanifest_id_conflictmanifest_id_immutabletext_too_largestructured_data_too_largeapp_not_foundfeedback_not_foundfeedback_has_no_reply_recipientasset_too_largeasset_count_exceededasset_quota_exceededasset_invalid_keyasset_invalid_content_typeasset_errorinvalid_idsse_cookie_expiredinvalid_secret_namesecret_too_largesecret_already_existssecret_not_foundinvalid_iconteam_slug_takenalready_membergrantee_not_foundinvite_cap_reachedinvite_sender_blockedread_onlyhandle_takeninvalid_handlename_takeninvalid_domaindomain_takendomain_existsdomain_provider_unavailableapp_unclaimedlink_cap_reachedgrantee_is_ownergrantee_is_selfgrantee_already_has_accessalready_invitedroutine_limit_exceededinvalid_intervalop_requires_inputduplicate_routinewebhook_limit_exceededduplicate_webhookwebhook_disabledversion_limit_exceededplan_requiredstorage_limit_exceededcopy_data_forbiddencopy_data_too_large
messagestring
reasonstring

Set on 401s to distinguish token failure modes when finer detail is needed. token_revoked_post_claim is the post-#1284 surface for a chrm_app_* that was invalidated by a claim — the recovery field on the same error object carries the self-contained pairing-recovery hint the agent should follow instead of creating a new app.

Allowed:token_malformedtoken_unknowntoken_revokedtoken_revoked_post_claim
planstring

Present on every plan refusal: the tier the caller was refused under. Its presence is what marks an error as a plan refusal rather than a validation or permission failure.

Allowed:freeprobusinessenterprise
limitstring | integer

Which ceiling the call met, keyed by kind. A plan refusal (*_limit_exceeded) names the countable that ran out — version_history, routines_per_app, routines_per_owner, webhooks_per_app, webhooks_per_owner — and pairs it with max. An app_busy refusal names the bound it met and pairs it with observed and allowed; no plan lifts any of them. Two are per-app run bounds, concurrent_runs and runs_per_window; the third, render_token_refresh, is the page render asking for a new render-token secret, held by 10 per app per client IP and 60 per app across every address in a 60 second window, and observed/allowed name whichever of those two refused. An edits_too_large rejection carries the numeric cap that was exceeded instead of a name.

rungstring

On an app_busy refusal, how hard the app's own refusal ladder is refusing: throttled is the plain ceiling breach above; parked means a caller kept calling inside a wait it was given, so the server mints its own Retry-After (doubling from 60s to a 300s cap) and refuses until a quiet window passes even once the ceiling clears; closed is the same, plus the response closes the connection. Absent on every other kind.

Allowed:throttledparkedclosed
maxinteger

On a *_limit_exceeded refusal, that limit's number on the caller's plan.

min 0
featurestring

On a plan_required refusal, the capability the plan does not include. Never paired with limit / max.

Allowed:custom_domainwatermark_removalteams
upgradeUrlstring<uri>

Absolute link to the owner's own billing page, where the refusal is lifted — the team's page for a team-owned app. Omitted only when the owner has no handle and therefore no settings page of their own, so treat it as optional and fall back to your own copy. MCP appends it as an Upgrade: <url> last line; the CLI prints it under the message.

recoveryRecoveryPair | RecoveryRetry | RecoveryShrink | RecoveryRefetch | RecoveryFixRouteLabel | RecoveryRename | RecoveryMigrateContract

Self-contained recovery hint, branching on kind. Attached to error envelopes the agent can self-correct on: pair for token_revoked_post_claim 401s (#1284), retry for 429s, shrink for too-large / quota-exceeded 413s, refetch for 412 revision_mismatch / invalid_if_match / 428 precondition_required, fix_route_label for 403 forbidden_write, rename for 409 manifest_id_immutable / manifest_id_conflict, and migrate_contract for 422 contract_migration_required. Every instructions string is a single-paragraph, action-first walk-through. Inlined per ADR 2026-05-06-agent-facing-error-surfaces-self-contain-the-fix.

Show properties
One of:
RecoveryPair
kindstringrequired
Allowed:pair
startstring<uri>required
verification_urlstring<uri>required
instructionsstringrequired
RecoveryRetry
kindstringrequired
Allowed:retry
retry_after_secintegerrequired

Seconds to wait before retrying. Matches the RateLimit-Reset and Retry-After headers on the same response.

min 0
retry_after_urlstring<uri>required

Absolute URL to retry verbatim once the wait elapses.

instructionsstringrequired
RecoveryShrink
kindstringrequired
Allowed:shrink
fieldstringrequired

Which input tripped the cap.

Allowed:moduleuistylesdescriptiontextstructured_dataasseteditssecretpayload
size_bytesintegerrequired
min 0
cap_bytesintegerrequired
min 0
instructionsstringrequired
RecoveryRefetch
kindstringrequired
Allowed:refetch
refetch_urlstring<uri>required

Absolute URL to GET for the fresh ETag before retrying with a corrected If-Match.

instructionsstringrequired
RecoveryFixRouteLabel
kindstringrequired
Allowed:fix_route_label
instructionsstringrequired

App-code recovery: the fix is in the route declaration, NOT in the request. Retrying the same request will fail again.

RecoveryRename
kindstringrequired
Allowed:rename
instructionsstringrequired
RecoveryMigrateContract
kindstringrequired
Allowed:migrate_contract
instructionsstringrequired
403

Caller does not have access to this app.

okbooleanrequired
Allowed:false
errorobjectrequired
Show properties
kindstringrequired

Stable enum-shaped error key. Branch on this for recovery flows.

Allowed:not_foundunknown_operationoperation_not_foundmethod_not_allowedoperation_failedapp_busyvisitor_rate_limitedservice_busyunauthorizedsign_in_requiredtoken_expiredforbiddenaccount_not_joinedforbidden_writestored_value_too_largestorage_fullinvalid_moduleinvalid_uiunpinned_cdn_scriptinvalid_route_schemainvalid_routes_shapecontract_migration_requiredinvalid_manifest_schemainvalid_manifest_exportsinvalid_requestinvalid_inputinvalid_outputunexpected_keyscapability_deniedcapability_errorexecutor_errorinvalid_call_shapealready_claimedmanifest_not_foundpayload_too_largemodule_too_largeui_too_largedescription_too_longmissing_descriptioninvalid_descriptionrate_limitedimage_origin_deniedtoo_many_requestsexpiredalready_approvedanon_not_remixabletemplate_listing_incompletetemplate_media_invalidforbidden_template_editinvalid_if_matchprecondition_requiredexpected_revision_requiredidempotency_key_requiredidempotency_conflictcontract_not_enabledapp_build_capacityapp_build_failedapp_build_expiredapp_build_authority_lostapp_build_unavailableapp_build_unsupportedversion_history_unavailableversion_not_restorablerevision_mismatchinvalid_revisioncandidate_effects_unsupportedold_string_not_foundold_string_not_uniqueedits_too_largemanifest_id_conflictmanifest_id_immutabletext_too_largestructured_data_too_largeapp_not_foundfeedback_not_foundfeedback_has_no_reply_recipientasset_too_largeasset_count_exceededasset_quota_exceededasset_invalid_keyasset_invalid_content_typeasset_errorinvalid_idsse_cookie_expiredinvalid_secret_namesecret_too_largesecret_already_existssecret_not_foundinvalid_iconteam_slug_takenalready_membergrantee_not_foundinvite_cap_reachedinvite_sender_blockedread_onlyhandle_takeninvalid_handlename_takeninvalid_domaindomain_takendomain_existsdomain_provider_unavailableapp_unclaimedlink_cap_reachedgrantee_is_ownergrantee_is_selfgrantee_already_has_accessalready_invitedroutine_limit_exceededinvalid_intervalop_requires_inputduplicate_routinewebhook_limit_exceededduplicate_webhookwebhook_disabledversion_limit_exceededplan_requiredstorage_limit_exceededcopy_data_forbiddencopy_data_too_large
messagestring
reasonstring

Set on 401s to distinguish token failure modes when finer detail is needed. token_revoked_post_claim is the post-#1284 surface for a chrm_app_* that was invalidated by a claim — the recovery field on the same error object carries the self-contained pairing-recovery hint the agent should follow instead of creating a new app.

Allowed:token_malformedtoken_unknowntoken_revokedtoken_revoked_post_claim
planstring

Present on every plan refusal: the tier the caller was refused under. Its presence is what marks an error as a plan refusal rather than a validation or permission failure.

Allowed:freeprobusinessenterprise
limitstring | integer

Which ceiling the call met, keyed by kind. A plan refusal (*_limit_exceeded) names the countable that ran out — version_history, routines_per_app, routines_per_owner, webhooks_per_app, webhooks_per_owner — and pairs it with max. An app_busy refusal names the bound it met and pairs it with observed and allowed; no plan lifts any of them. Two are per-app run bounds, concurrent_runs and runs_per_window; the third, render_token_refresh, is the page render asking for a new render-token secret, held by 10 per app per client IP and 60 per app across every address in a 60 second window, and observed/allowed name whichever of those two refused. An edits_too_large rejection carries the numeric cap that was exceeded instead of a name.

rungstring

On an app_busy refusal, how hard the app's own refusal ladder is refusing: throttled is the plain ceiling breach above; parked means a caller kept calling inside a wait it was given, so the server mints its own Retry-After (doubling from 60s to a 300s cap) and refuses until a quiet window passes even once the ceiling clears; closed is the same, plus the response closes the connection. Absent on every other kind.

Allowed:throttledparkedclosed
maxinteger

On a *_limit_exceeded refusal, that limit's number on the caller's plan.

min 0
featurestring

On a plan_required refusal, the capability the plan does not include. Never paired with limit / max.

Allowed:custom_domainwatermark_removalteams
upgradeUrlstring<uri>

Absolute link to the owner's own billing page, where the refusal is lifted — the team's page for a team-owned app. Omitted only when the owner has no handle and therefore no settings page of their own, so treat it as optional and fall back to your own copy. MCP appends it as an Upgrade: <url> last line; the CLI prints it under the message.

recoveryRecoveryPair | RecoveryRetry | RecoveryShrink | RecoveryRefetch | RecoveryFixRouteLabel | RecoveryRename | RecoveryMigrateContract

Self-contained recovery hint, branching on kind. Attached to error envelopes the agent can self-correct on: pair for token_revoked_post_claim 401s (#1284), retry for 429s, shrink for too-large / quota-exceeded 413s, refetch for 412 revision_mismatch / invalid_if_match / 428 precondition_required, fix_route_label for 403 forbidden_write, rename for 409 manifest_id_immutable / manifest_id_conflict, and migrate_contract for 422 contract_migration_required. Every instructions string is a single-paragraph, action-first walk-through. Inlined per ADR 2026-05-06-agent-facing-error-surfaces-self-contain-the-fix.

Show properties
One of:
RecoveryPair
kindstringrequired
Allowed:pair
startstring<uri>required
verification_urlstring<uri>required
instructionsstringrequired
RecoveryRetry
kindstringrequired
Allowed:retry
retry_after_secintegerrequired

Seconds to wait before retrying. Matches the RateLimit-Reset and Retry-After headers on the same response.

min 0
retry_after_urlstring<uri>required

Absolute URL to retry verbatim once the wait elapses.

instructionsstringrequired
RecoveryShrink
kindstringrequired
Allowed:shrink
fieldstringrequired

Which input tripped the cap.

Allowed:moduleuistylesdescriptiontextstructured_dataasseteditssecretpayload
size_bytesintegerrequired
min 0
cap_bytesintegerrequired
min 0
instructionsstringrequired
RecoveryRefetch
kindstringrequired
Allowed:refetch
refetch_urlstring<uri>required

Absolute URL to GET for the fresh ETag before retrying with a corrected If-Match.

instructionsstringrequired
RecoveryFixRouteLabel
kindstringrequired
Allowed:fix_route_label
instructionsstringrequired

App-code recovery: the fix is in the route declaration, NOT in the request. Retrying the same request will fail again.

RecoveryRename
kindstringrequired
Allowed:rename
instructionsstringrequired
RecoveryMigrateContract
kindstringrequired
Allowed:migrate_contract
instructionsstringrequired
404

App not found.

okbooleanrequired
Allowed:false
errorobjectrequired
Show properties
kindstringrequired

Stable enum-shaped error key. Branch on this for recovery flows.

Allowed:not_foundunknown_operationoperation_not_foundmethod_not_allowedoperation_failedapp_busyvisitor_rate_limitedservice_busyunauthorizedsign_in_requiredtoken_expiredforbiddenaccount_not_joinedforbidden_writestored_value_too_largestorage_fullinvalid_moduleinvalid_uiunpinned_cdn_scriptinvalid_route_schemainvalid_routes_shapecontract_migration_requiredinvalid_manifest_schemainvalid_manifest_exportsinvalid_requestinvalid_inputinvalid_outputunexpected_keyscapability_deniedcapability_errorexecutor_errorinvalid_call_shapealready_claimedmanifest_not_foundpayload_too_largemodule_too_largeui_too_largedescription_too_longmissing_descriptioninvalid_descriptionrate_limitedimage_origin_deniedtoo_many_requestsexpiredalready_approvedanon_not_remixabletemplate_listing_incompletetemplate_media_invalidforbidden_template_editinvalid_if_matchprecondition_requiredexpected_revision_requiredidempotency_key_requiredidempotency_conflictcontract_not_enabledapp_build_capacityapp_build_failedapp_build_expiredapp_build_authority_lostapp_build_unavailableapp_build_unsupportedversion_history_unavailableversion_not_restorablerevision_mismatchinvalid_revisioncandidate_effects_unsupportedold_string_not_foundold_string_not_uniqueedits_too_largemanifest_id_conflictmanifest_id_immutabletext_too_largestructured_data_too_largeapp_not_foundfeedback_not_foundfeedback_has_no_reply_recipientasset_too_largeasset_count_exceededasset_quota_exceededasset_invalid_keyasset_invalid_content_typeasset_errorinvalid_idsse_cookie_expiredinvalid_secret_namesecret_too_largesecret_already_existssecret_not_foundinvalid_iconteam_slug_takenalready_membergrantee_not_foundinvite_cap_reachedinvite_sender_blockedread_onlyhandle_takeninvalid_handlename_takeninvalid_domaindomain_takendomain_existsdomain_provider_unavailableapp_unclaimedlink_cap_reachedgrantee_is_ownergrantee_is_selfgrantee_already_has_accessalready_invitedroutine_limit_exceededinvalid_intervalop_requires_inputduplicate_routinewebhook_limit_exceededduplicate_webhookwebhook_disabledversion_limit_exceededplan_requiredstorage_limit_exceededcopy_data_forbiddencopy_data_too_large
messagestring
reasonstring

Set on 401s to distinguish token failure modes when finer detail is needed. token_revoked_post_claim is the post-#1284 surface for a chrm_app_* that was invalidated by a claim — the recovery field on the same error object carries the self-contained pairing-recovery hint the agent should follow instead of creating a new app.

Allowed:token_malformedtoken_unknowntoken_revokedtoken_revoked_post_claim
planstring

Present on every plan refusal: the tier the caller was refused under. Its presence is what marks an error as a plan refusal rather than a validation or permission failure.

Allowed:freeprobusinessenterprise
limitstring | integer

Which ceiling the call met, keyed by kind. A plan refusal (*_limit_exceeded) names the countable that ran out — version_history, routines_per_app, routines_per_owner, webhooks_per_app, webhooks_per_owner — and pairs it with max. An app_busy refusal names the bound it met and pairs it with observed and allowed; no plan lifts any of them. Two are per-app run bounds, concurrent_runs and runs_per_window; the third, render_token_refresh, is the page render asking for a new render-token secret, held by 10 per app per client IP and 60 per app across every address in a 60 second window, and observed/allowed name whichever of those two refused. An edits_too_large rejection carries the numeric cap that was exceeded instead of a name.

rungstring

On an app_busy refusal, how hard the app's own refusal ladder is refusing: throttled is the plain ceiling breach above; parked means a caller kept calling inside a wait it was given, so the server mints its own Retry-After (doubling from 60s to a 300s cap) and refuses until a quiet window passes even once the ceiling clears; closed is the same, plus the response closes the connection. Absent on every other kind.

Allowed:throttledparkedclosed
maxinteger

On a *_limit_exceeded refusal, that limit's number on the caller's plan.

min 0
featurestring

On a plan_required refusal, the capability the plan does not include. Never paired with limit / max.

Allowed:custom_domainwatermark_removalteams
upgradeUrlstring<uri>

Absolute link to the owner's own billing page, where the refusal is lifted — the team's page for a team-owned app. Omitted only when the owner has no handle and therefore no settings page of their own, so treat it as optional and fall back to your own copy. MCP appends it as an Upgrade: <url> last line; the CLI prints it under the message.

recoveryRecoveryPair | RecoveryRetry | RecoveryShrink | RecoveryRefetch | RecoveryFixRouteLabel | RecoveryRename | RecoveryMigrateContract

Self-contained recovery hint, branching on kind. Attached to error envelopes the agent can self-correct on: pair for token_revoked_post_claim 401s (#1284), retry for 429s, shrink for too-large / quota-exceeded 413s, refetch for 412 revision_mismatch / invalid_if_match / 428 precondition_required, fix_route_label for 403 forbidden_write, rename for 409 manifest_id_immutable / manifest_id_conflict, and migrate_contract for 422 contract_migration_required. Every instructions string is a single-paragraph, action-first walk-through. Inlined per ADR 2026-05-06-agent-facing-error-surfaces-self-contain-the-fix.

Show properties
One of:
RecoveryPair
kindstringrequired
Allowed:pair
startstring<uri>required
verification_urlstring<uri>required
instructionsstringrequired
RecoveryRetry
kindstringrequired
Allowed:retry
retry_after_secintegerrequired

Seconds to wait before retrying. Matches the RateLimit-Reset and Retry-After headers on the same response.

min 0
retry_after_urlstring<uri>required

Absolute URL to retry verbatim once the wait elapses.

instructionsstringrequired
RecoveryShrink
kindstringrequired
Allowed:shrink
fieldstringrequired

Which input tripped the cap.

Allowed:moduleuistylesdescriptiontextstructured_dataasseteditssecretpayload
size_bytesintegerrequired
min 0
cap_bytesintegerrequired
min 0
instructionsstringrequired
RecoveryRefetch
kindstringrequired
Allowed:refetch
refetch_urlstring<uri>required

Absolute URL to GET for the fresh ETag before retrying with a corrected If-Match.

instructionsstringrequired
RecoveryFixRouteLabel
kindstringrequired
Allowed:fix_route_label
instructionsstringrequired

App-code recovery: the fix is in the route declaration, NOT in the request. Retrying the same request will fail again.

RecoveryRename
kindstringrequired
Allowed:rename
instructionsstringrequired
RecoveryMigrateContract
kindstringrequired
Allowed:migrate_contract
instructionsstringrequired
Try it
Server
Authorization (optional)
Parameters
Request
curl -X GET 'https://charm.ing/app/string/events' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Accept: application/json'
Response
{
  "kind": "state-changed",
  "appId": "string",
  "op": "string",
  "source": "agent",
  "ts": 0,
  "result": null
}