Macaron API
Start agent threads, follow them and run them on a schedule from your own scripts.
How it works
A thread is one piece of work for a coding agent: a prompt, an optional GitHub repository and the agent to use. The API queues the thread. A runner signed in to your account claims it and runs the agent. Today the runner is the Macaron desktop app with Run threads on this Mac turned on, so the app must be open on a computer that has the repository checked out. A thread stays queued until a runner with a matching repository claims it.
- Threads from the API, schedules, Slack, Linear and GitHub share one list and the same fields.
- Each runner claims threads for the GitHub repositories it has locally, matched by owner/name.
- The agent works in a separate worktree, so your own checkout is not changed.
Authentication
Send requests to https://macaron.sh/api/cli/v1 with an API key in the Authorization header. API keys start with mak_ and reach threads and schedules only. Create a key with a signed-in Macaron session, not with another API key. The key is shown once; store it in your secret manager. An account has at most 20 keys. Deleting a key revokes it, and requests with it then answer 401.
# Create a key (needs a session token from a signed-in Macaron app or CLI)
curl -X POST https://macaron.sh/api/cli/v1/api-keys \
-H "Authorization: Bearer $MACARON_SESSION" \
-H "Content-Type: application/json" \
-d '{"name": "CI"}'
# -> {"key": "mak_...", "id": "key_...", "name": "CI", "prefix": "mak_...", "created_at": "..."}
# List keys (never the key itself): {"keys": [...]}. Revoke one by id
curl https://macaron.sh/api/cli/v1/api-keys -H "Authorization: Bearer $MACARON_SESSION"
curl -X DELETE https://macaron.sh/api/cli/v1/api-keys/key_0123456789abcdef \
-H "Authorization: Bearer $MACARON_SESSION"Create a thread
POST /threads with a prompt. repo is owner/name on GitHub; without it, only a runner that accepts threads without a repository claims the thread. kind is task (the default), review (read-only) or fix (pushes to an existing pull request, so it needs repo and pr). agent is claude-code (the default) or codex. open_pr makes a task push a branch and open a draft pull request; it is true by default for a task with a repo, so send false to keep the change on a branch. title defaults to the first line of the prompt. The response is 201 with the thread.
curl -X POST https://macaron.sh/api/cli/v1/threads \
-H "Authorization: Bearer $MACARON_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "The cart total rounds wrong for three items at $0.10. Fix it and add a test.",
"repo": "acme/shop",
"agent": "claude-code"
}'
# -> 201 {"thread": {"id": "thr_...", "status": "queued", ...}}
# Fix an existing pull request
curl -X POST https://macaron.sh/api/cli/v1/threads \
-H "Authorization: Bearer $MACARON_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "Address the review comments", "repo": "acme/shop", "kind": "fix", "pr": 42}'Thread fields
A thread has these fields. Lists leave out messages, pending and artifacts.
- id: thr_ followed by 16 hexadecimal characters.
- source: api, schedule, slack, linear, sentry, github or imessage.
- kind, title, prompt, agent and open_pr: as created.
- repo: { full_name, branch, pr }, each null when not set.
- status: queued, running, done, failed or cancelled.
- runner: the app running the thread ({ id, kind, name, claimed_at, heartbeat_at }), or null.
- interrupt: true while a stop request waits for the runner.
- result: { summary, changed_files, branch, pr_url, proof } once the agent finished, else null. changed_files lists { path, add, del }; proof lists artifact ids.
- error: why the thread failed, or null.
- messages: the conversation as { id, at, role, text }, role being user, agent or status. The latest 200 are kept.
- pending: follow-ups the runner has not delivered yet.
- artifacts: screenshots and recordings as { id, url, content_type, size, created_at, expires_at }. Anyone with a URL can open it; URLs expire after 30 days.
- archived, created_at and updated_at (ISO 8601, UTC).
Statuses
A thread moves from queued to running to done, failed or cancelled. done and failed do not change again.
- queued: waiting for a runner with a matching repository.
- running: a runner claimed the thread. If the runner stops checking in for 3 minutes, the thread returns to queued for the next runner.
- done: the agent finished. Read result.
- failed: the run did not finish. Read error.
- cancelled: you stopped the thread, or archived it before a runner claimed it. While the runner that stopped it still has the thread open (runner is set), a follow-up resumes it and the status returns to running.
Read threads
List threads newest first. status filters by status, limit is 1 to 100 (default 50) and archived=true lists archived threads instead. Read one thread with its messages and artifacts, or only its messages.
curl "https://macaron.sh/api/cli/v1/threads?status=running&limit=20" \
-H "Authorization: Bearer $MACARON_API_KEY"
# -> {"threads": [...]}
curl https://macaron.sh/api/cli/v1/threads/thr_0123456789abcdef \
-H "Authorization: Bearer $MACARON_API_KEY"
# -> {"thread": {..., "messages": [...], "pending": [...], "artifacts": [...]}}
curl https://macaron.sh/api/cli/v1/threads/thr_0123456789abcdef/messages \
-H "Authorization: Bearer $MACARON_API_KEY"
# -> {"messages": [{"id": "msg_...", "at": "...", "role": "agent", "text": "..."}]}Follow-ups, stop and archive
Send a follow-up while a thread is queued or running, or cancelled with its runner still set. The runner delivers it to the agent as the next turn. Up to 20 follow-ups can wait at once, each up to 8 KB. Other threads answer 409; start a new thread instead. interrupt stops the agent: a queued thread is cancelled at once, and a running one stops when the runner next checks in, usually within 10 seconds. archive hides the thread from the default list and stops it the same way.
curl -X POST https://macaron.sh/api/cli/v1/threads/thr_0123456789abcdef/message \
-H "Authorization: Bearer $MACARON_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "Also update the changelog"}'
# -> 201 {"pending": {"id": "fup_...", "at": "...", "text": "..."}, "thread": {...}}
curl -X POST https://macaron.sh/api/cli/v1/threads/thr_0123456789abcdef/interrupt \
-H "Authorization: Bearer $MACARON_API_KEY"
curl -X POST https://macaron.sh/api/cli/v1/threads/thr_0123456789abcdef/archive \
-H "Authorization: Bearer $MACARON_API_KEY"Schedules
A schedule creates a thread on a cron schedule, with source schedule and the schedule name as its title. cron has five fields (minute, hour, day of month, month, day of week) in UTC. Fields accept *, numbers, ranges such as 1-5, steps such as */15, and lists such as 1,15, plus three-letter month and day names. 0 and 7 are both Sunday. When both day fields are set, a day matching either one runs. repo, agent and open_pr work as they do for threads (open_pr is true by default when repo is set); enabled defaults to true. The response includes next_run_at, last_run_at, last_thread_id and last_error. Change any field with PATCH; send enabled false to pause a schedule.
# Weekdays at 09:00 UTC
curl -X POST https://macaron.sh/api/cli/v1/schedules \
-H "Authorization: Bearer $MACARON_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Dependency check", "cron": "0 9 * * 1-5", "prompt": "Update outdated dependencies and run the tests", "repo": "acme/shop"}'
# -> 201 {"schedule": {"id": "sch_...", "next_run_at": "...", ...}}
curl https://macaron.sh/api/cli/v1/schedules -H "Authorization: Bearer $MACARON_API_KEY"
curl https://macaron.sh/api/cli/v1/schedules/sch_0123456789abcdef -H "Authorization: Bearer $MACARON_API_KEY"
curl -X PATCH https://macaron.sh/api/cli/v1/schedules/sch_0123456789abcdef \
-H "Authorization: Bearer $MACARON_API_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'
curl -X DELETE https://macaron.sh/api/cli/v1/schedules/sch_0123456789abcdef \
-H "Authorization: Bearer $MACARON_API_KEY"Schedule limits
An account has at most 25 schedules and at most 50 scheduled runs in any 24 hours. A run over that limit is skipped and the reason is recorded in last_error. Macaron checks schedules every minute, so a run can start up to a minute after its time. If Macaron misses several runs, the schedule runs once and moves to its next time.
Errors and rate limits
Errors are JSON with an HTTP status: {"error": "message"}. The message names the field or limit involved.
- 400: a field is missing or invalid. Unknown fields are refused.
- 401: the key is missing or revoked, or the route needs a signed-in session instead of an API key.
- 404: the thread or schedule is not in your account.
- 409: the request conflicts with the thread status or a limit, such as a follow-up on a finished thread.
- 429: too many requests. Wait the number of seconds in the Retry-After header.
- Rate limits per account: 60 requests a minute across the API. Creating threads is limited to 10 a minute, with bursts of 20.
- Sizes: prompt up to 32,000 characters, title 200 characters, follow-up 8 KB. An account keeps up to 500 threads; finished threads are deleted 90 days after their last change.