Raw HTTP
Languages without an SDK, embedded systems, one-offs.

When there's no SDK and no third-party integration, you have the raw HTTP API. The core endpoints: `/v1/activity/start` opens a Live Activity, `/v1/activity/update` ticks state, `/v1/activity/end` closes it, `/v1/notify` fires a one-shot push without a persistent activity. Auth is bearer token via the `Authorization` header.
Routing identity is `(schema_id, instance_id)`. You usually don't pass `instance_id` - the server smart-routes by id-key match (repo, session_id, task_id, etc.) so retries of the same flow merge into the same card and parallel flows land in distinct cards. Pass `instance_id` explicitly when you want guaranteed routing across two parallel runs of the same template.
Use this when you're integrating from a language without a Chirp SDK (Elixir, Crystal, Zig, etc.), from an embedded device, or from a one-off bash script where pulling in the SDK isn't worth it. Every other Chirp integration is a thin wrapper over these endpoints.
Prerequisites
- A Chirp API key set in the calling environment (`$CHIRP_API_KEY`).
- Anything that can speak HTTPS + JSON.
Setup
- 1
Set the API key in your environment
Get the key from chirpapp.dev/dashboard. Export it in whatever runtime your code lives in - shell profile, CI secret, Docker env-file, K8s secret. The Chirp API expects it on the
Authorizationheader.shellexport CHIRP_API_KEY=chirp_sk_... - 2
POST /v1/activity/start
Opens a Live Activity.
schema_idpicks the layout (@agent,@deploy, etc.);datais the schema-specific payload. Response includesinstanceIds(one per device the user has registered) and the resolvedinstance_idthe server picked for routing. Identify subsequent /update and /end calls with the same(schema_id, instance_id)tuple - not the per-device instance UUIDs.curlcurl -X POST https://api.chirpapp.dev/v1/activity/start \ -H "Authorization: Bearer $CHIRP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "schema_id": "@agent", "data": { "agentName": "embedding-pipeline", "statusMessage": "Building chunk index", "state": "working" } }' # Response: {"success":true,"instanceIds":["..."],"schema_id":"@agent","instance_id":null} - 3
POST /v1/activity/update
Updates the activity in place. Pass the same
schema_idyou started with. Fields you don't include indatakeep their previous value (partial update). When multiple instances of the same schema are running, the server smart-routes to whichever one your payload best matches via id-key fields (repo, session_id, etc.) - passinstance_idif you want explicit targeting.curlcurl -X POST https://api.chirpapp.dev/v1/activity/update \ -H "Authorization: Bearer $CHIRP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "schema_id": "@agent", "data": {"statusMessage": "Uploading", "progress": 0.85} }' - 4
POST /v1/activity/end
Closes the activity. Pass
schema_id(and optionalinstance_idto end just one of several parallel cards). Optionaldismissal_policycontrols how long the final state lingers on the lock screen. Withoutinstance_id, ALL active instances of that schema_id are ended.curlcurl -X POST https://api.chirpapp.dev/v1/activity/end \ -H "Authorization: Bearer $CHIRP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "schema_id": "@agent", "dismissal_policy": {"afterSeconds": 60} }' - 5
(Parallel cards) Pass instance_id for two of the same template at once
Same template, different work - e.g. two
@deployflows on different repos. Pass a stableinstance_idderived from the work itself (the repo slug, a session id) so each call routes deterministically.curl# First parallel card curl -X POST https://api.chirpapp.dev/v1/activity/start \ -H "Authorization: Bearer $CHIRP_API_KEY" -H "Content-Type: application/json" \ -d '{"schema_id":"@deploy","instance_id":"chirp-app","data":{"repo":"chirp-app","stage":"build","progress":0.1}}' # Second parallel card - different instance_id, same schema curl -X POST https://api.chirpapp.dev/v1/activity/start \ -H "Authorization: Bearer $CHIRP_API_KEY" -H "Content-Type: application/json" \ -d '{"schema_id":"@deploy","instance_id":"other-app","data":{"repo":"other-app","stage":"build","progress":0.1}}' # Update only one curl -X POST https://api.chirpapp.dev/v1/activity/update \ -H "Authorization: Bearer $CHIRP_API_KEY" -H "Content-Type: application/json" \ -d '{"schema_id":"@deploy","instance_id":"chirp-app","data":{"stage":"deploy","progress":0.7}}' - 6
(Alternative) POST /v1/notify for one-shot pushes
When you don't need a persistent activity - just a single notification. Same auth header.
curlcurl -X POST https://api.chirpapp.dev/v1/notify \ -H "Authorization: Bearer $CHIRP_API_KEY" \ -d '{"title":"Build done","body":"main passed in 4m 12s"}'
What you’ll see
Whatever the schema renders - `@agent` shows agent name + status + state pill; `@deploy` shows repo + stage + progress bar. Multiple parallel instances of the same template show as separate cards on the lock screen, distinguished by their data. The schema docs at chirpapp.dev/templates list every field per schema. State directives (`*Append`, `*LatestOutcome`) work over HTTP exactly like they do via the SDKs.
Troubleshooting
- 401 unauthorized.
- Either the key is wrong, or you're sending it as
Authorization: <key>instead ofAuthorization: Bearer <key>. TheBearerprefix is required. - 422 unprocessable entity.
- Schema validation failed. The error response includes the failing field path (e.g.
data.progress: must be a number between 0 and 1). Check your data shape against the schema docs at chirpapp.dev/templates. - Update went to the wrong card when I have multiple parallels.
- Pass
instance_idexplicitly to disambiguate. Use a stable string derived from the work (repo slug, session id). Withoutinstance_id, the server picks the best id-key match in your payload - usually right but not guaranteed for ambiguous payloads. - I dismissed a card and want it back.
- Open the Chirp app → Activities tab → your card is in 'Running' or 'Recently ended' → tap Re-push. Or
chirp activity listthenchirp activity retrigger <instanceId>from the CLI.