Skip to main content

Runs

Status: Current (Scale plan). A run is one unit of automation work — a warm_up or a post — that your own connected iPhone picks up and executes. The API enqueues and reads; the airlock keeps the API session off the device itself. All paths are relative to <baseUrl>/v1 and require Authorization: Bearer. Reads need runs:read; writes need runs:write.

Endpoints

Enqueue a run — POST /v1/runs

Returns 202 with { "run": Run }.

Idempotency

Send an Idempotency-Key header (recommended for a post): a duplicate key returns the original run (200) instead of creating a second post. The key must be a UUID — any other format is rejected up front with 422 invalid_request.

Target app resolution

The platform a run executes against is derived from your org’s account roster at dispatch — the runner looks up account_username and resolves the target app from that account’s platform. TikTok and Instagram accounts always resolve; X accounts additionally need the operator to set the target app in Warmr’s Settings on the Mac before runs execute. The target app is deliberately not settable over /v1 — the platform comes from the roster, never from the API caller.

Bulk-enqueue — POST /v1/runs/batch

Stages N explicit clips as post runs for a single account, spreading them over time per cadence (each scheduled_at is folded in at enqueue — one clip becomes one run). Each item’s upload_id is its per-row idempotency key, so a replayed batch is a no-op; enqueued may be less than requested when some uploads were already staged. The per-account/day cap returns 413. The airlock is unchanged — this only enqueues. Returns 202 (200 on an idempotent replay) with { "batch": BatchResult, "runs": Run[] }. BatchResult = { account_username, type, requested, enqueued, first_at, last_at }.

List runs — GET /v1/runs

Query: status (public status), account (exact account_username), limit (1–200, default 50). Returns { "runs": Run[] }, newest first, org-scoped by RLS.

Get a run — GET /v1/runs/{id}

Returns { "run": Run }, or 404 not_found.

Cancel a run — POST /v1/runs/{id}/cancel

Only a queued run can be canceled via the API. A claimed / running / terminal run returns 409 conflict — a device owns it, and the airlock blocks releasing it. Returns { "run_id", "status": "canceled", "result" }.

Retry a run — POST /v1/runs/{id}/retry

Requeue a failed or canceled run. Body: { "reschedule_now": boolean } (optional). Returns { "run_id", "status": "queued", "result" }. If the post’s clip already went out, returns 409 source_consumed; if the run is not in a requeueable state, 409 conflict.

The Run object

Status mapping: internally done is surfaced as succeeded; every other state (queued / claimed / running / failed / canceled) passes through unchanged.

Next

  • Schedules — standing recurring cadences instead of one-off runs.
  • Webhooks — get notified on run.completed / run.failed.
  • SDK quickstart — the posting flow that produces an upload_id.