HTTP API
The CLI and the MCP tools are clients of this API. Anything they do, you can do with plain HTTP.
Basics
- Base URL:
https://api.heyquilt.com. Bodies and answers are JSON. - Send
Authorization: Bearer <token>. The token is a person's sign-in (the oneseam loginkeeps in~/.seam/account.json) or an agent'sqa_access key. Hosted agents sendAuthorization: QuiltPass <pass>. <q>is a quilt's slug or id.<s>is a square's id or its short handle, such assq:77aa01bc.<rev>is any revision.- A write that names
expect_headis refused withhead_movedif the square moved. Without it, the server uses the square's current head. - Commits, merges, rebases, cherry-picks, resolves, folds and splits can be retried safely: the same call on the same square, with the same
expect_headand body, within 24 hours gets the first answer again.
$ curl -s "https://api.heyquilt.com/v1/quilts/my-app/log?limit=1" \
-H "Authorization: Bearer $SEAM_TOKEN"Routes
Quilts
| Method | Path | What it does |
|---|---|---|
| GET | /v1/quilts | The quilts you are a member of: id, slug, name, owner and your level. |
| POST | /v1/quilts | Makes a quilt: { slug, name? }. Answers 201 with the quilt. main starts at an empty root commit. |
| GET | /v1/quilts/<q> | One quilt: its lines (name, tip, patch_count, frozen), its members, and you. |
| POST | /v1/quilts/<q>/members | Adds a member or changes their level: { member_id, level: read | write | admin }. Leave level out to remove them. Needs admin. |
Squares
| Method | Path | What it does |
|---|---|---|
| GET | /v1/quilts/<q>/squares | Squares, with ?state=open | merged | abandoned | folded, ?mine=1 and ?task=<id>. |
| POST | /v1/quilts/<q>/squares | Opens a square: { title, line?, base?, task_id? }. base is any revision; the default is the line's tip. 201. |
| GET | /v1/quilts/<q>/squares/<s> | One square, with merged_under_you and would_conflict. |
| POST | /v1/quilts/<q>/squares/<s>/commit | Commits: { expect_head, message, changes }. 201 with the commit and the square. |
| POST | /v1/quilts/<q>/squares/<s>/merge | Merges another square or revision into this square as a two-parent commit: { from, expect_head?, message? }. |
| POST | /v1/quilts/<q>/squares/<s>/rebase | Moves the square onto its line's tip, or { onto } any revision. |
| POST | /v1/quilts/<q>/squares/<s>/cherry-pick | Applies one commit's change to the square: { from, expect_head? }. |
| POST | /v1/quilts/<q>/squares/<s>/resolve | Settles conflicts: { entries: [{ path, choose }], expect_head? }. |
| POST | /v1/quilts/<q>/squares/<s>/handoff | Gives the square to another member: { to, note?, expect_head? }. |
| POST | /v1/quilts/<q>/squares/<s>/abandon | Abandons the square: { expect_head? }. |
| POST | /v1/quilts/<q>/squares/<s>/reopen | Reopens an abandoned square; the caller becomes its holder. |
| POST | /v1/quilts/<q>/squares/<s>/fold | Folds this square into another one: { into, message?, expect_head? }. |
| POST | /v1/quilts/<q>/squares/<s>/split | Splits it by paths: { by: "paths", groups: [[paths], …], titles?, expect_head? }. 201. |
Lines and patches
| Method | Path | What it does |
|---|---|---|
| POST | /v1/quilts/<q>/lines/<line>/merge | Merges a square into the line: { square, expect_head?, message?, on_conflict?, dry_run?, expect_tip? }. 201 merged, 200 dry run, 422 refused. |
| GET | /v1/quilts/<q>/lines/<line>/patches | The line's patches, newest first, with author, first line, file count and square times. ?limit= (default 100, at most 1000) and ?before=<patch_no>. |
| POST | /v1/quilts/<q>/patches/<n>/revert | Opens a square that undoes patch n: { line?, message? }. 201. |
| POST | /v1/quilts/<q>/revert | Opens a square that undoes any revision: { rev, line?, message? }. 201. |
| POST | /v1/quilts/<q>/lines/<line>/checks | Reports a check on the line: { check, status: red | green | flaky }. Answers with the line's merge window. |
| GET | /v1/quilts/<q>/lines/<line>/window | The merge window: window, window_max, used in the last hour, and the red checks. |
| POST | /v1/quilts/<q>/lines/<line>/window | Sets the line's largest window: { merge_window_max } (5 or more). Needs admin. |
History and files
| Method | Path | What it does |
|---|---|---|
| GET | /v1/quilts/<q>/log | Commits, newest first: ?rev= (default main, or a range a..b), ?paths=a,b, ?limit= (default 20, at most 1000). |
| GET | /v1/quilts/<q>/show/<rev> | One commit with its square and its diff: ?format=unified | stat | names | none, ?paths=. |
| GET | /v1/quilts/<q>/diff | The difference between two revisions: ?from=, ?to=, ?paths=, ?format=. Without from: against the parent, or for a square against its base. |
| GET | /v1/quilts/<q>/tree/<rev> | The files at a revision: path, mode, blob and size. ?paths= limits it to folders. |
| GET | /v1/quilts/<q>/blob/<rev>/<path> | One file, base64 in content. A conflicted file comes with its markers: ?format=diff | snapshot | git. |
| GET | /v1/quilts/<q>/blame/<rev>/<path> | Each line with the commit, patch, author and time that last changed it. |
Events
| Method | Path | What it does |
|---|---|---|
| GET | /v1/quilts/<q>/events | Your events, oldest first: ?after=<cursor>, ?kinds=a,b, ?limit= (default 100). The answer's after is the next cursor. |
| POST | /v1/quilts/<q>/webhook | POSTs your events to a URL: { url, secret?, events?, bearer? }. { url: null } removes it. |
Transfer
| Method | Path | What it does |
|---|---|---|
| POST | /v1/quilts/<q>/have | Which of these object ids the server is missing: { ids } → { missing }. |
| POST | /v1/quilts/<q>/packs | Uploads a pack of objects (raw bytes, up to 65 MiB). 201. |
| POST | /v1/quilts/<q>/blobs | File contents by id: { ids } → { blobs: { id: base64 } }. Up to 2,000 ids, and about 32 MiB, a call. |
| POST | /v1/quilts/<q>/import/commits | The git importer's commits, in order, at most 500 a call. Only for a quilt made with { import: true }. Needs admin. |
| GET | /v1/quilts/<q>/export/git | A line's patches with everything a git commit is made from: ?line=, ?after=<patch_no>, ?limit= (default 200). |
Request bodies
Commit
Each change puts a file or deletes one. Put the bytes inline as base64 content, or name a blob you uploaded first with /have and /packs. mode is file (the default), exec or symlink. Paths under .quilt/ are refused.
POST /v1/quilts/my-app/squares/sq:77aa01bc/commit
{
"expect_head": "0c1d2e3f4a5b…",
"message": "Rate limit the sign-in route",
"changes": [
{ "path": "src/limits.js", "op": "put", "content": "ZXhwb3J0IGNvbnN0IExJTUlUID0gMTAK" },
{ "path": "src/old-limits.js", "op": "delete" }
]
}Putting a conflicted file without its conflict tag line settles that conflict.
Merge into a line
POST /v1/quilts/my-app/lines/main/merge
{ "square": "sq:77aa01bc", "message": "Rate limit sign-in", "dry_run": false }A merge answers { ok: true, merged: { line, patch_no, commit, seq }, retries, combined, fast, warnings, square }. A refused one answers 422 with refused, the square's new head and terms: each conflicted path, its kind and its sides. on_conflict: "leave" keeps the square as it was.
Resolve
choose is ours, theirs, base, suggestion, delete, a side number, or { "blob": "<id>" } for content you uploaded.
Answers
Every write answers with the server's record. A square, as GET /squares/<s> returns it:
{
"id": "019a2b7e-5c40-7d21-8e3f-0d3e77aa01bc",
"short": "sq:77aa01bc",
"title": "Rate limit sign-in",
"state": "open",
"line": "main",
"base": "4d5bb0f2aacd…",
"head": "0c1d2e3f4a5b…",
"holder": "agent:5c1e…",
"opened_by": "agent:5c1e…",
"writers": [],
"behind_by": 1,
"conflicted_paths": 0,
"opened_at": 1761398000000,
"updated_at": 1761399500000,
"merged_under_you": [
{ "patch_no": 784, "commit": "5f4e3d2c1b0a…", "author": "person:7f3e…", "message": "Fix sign-in", "paths": ["src/auth.js"] }
],
"would_conflict": [
{ "path": "src/auth.js", "kind": "content", "patch_no": 784, "author": "person:7f3e…" }
]
}Times are milliseconds since 1970. behind_by counts the patches on the line since the square's base. A merged square adds merged; a folded one adds folded_into.
A commit, as POST /squares/<s>/commit returns it:
{
"ok": true,
"seq": 5121,
"square": { "id": "019a2b7e-…", "short": "sq:77aa01bc", "state": "open", "head": "1e2f3a4b5c6d…", "…": "…" },
"result": {
"id": "1e2f3a4b5c6d…",
"short": "1e2f3a4b5c6d",
"seq": 5121,
"square_id": "019a2b7e-…",
"square": "sq:77aa01bc",
"parents": ["0c1d2e3f4a5b…"],
"author": { "member_id": "agent:5c1e…", "kind": "agent", "name": "builder", "tool": "claude" },
"on_behalf_of": "person:7f3e…",
"message": "Rate limit the sign-in route",
"authored_at": 1761399600000,
"at": 1761399600000,
"conflicted": 0,
"stack": {},
"changes": [
{ "path": "src/limits.js", "op": "put", "blob": "8a7b6c5d…", "mode": "file", "size": 412 },
{ "path": "src/old-limits.js", "op": "delete" }
]
}
}A commit on a line also has patch: { line, patch_no }. In /log each commit has files (a count) in place of changes.
Errors
A refusal is { ok: false, error, code } with an HTTP status, plus fields that help, such as head or retry_after. The codes you will meet most:
| Status | code | Means |
|---|---|---|
| 400 | bad_request | The request is not right; error says why. |
| 403 | forbidden, not_holder | You need a higher level, or the square is held by someone else. |
| 404 | not_found | No such quilt (or you are not a member), square, revision or file. |
| 409 | head_moved | The square moved on since you looked. The answer has its head; look again and retry. |
| 409 | tip_moved | The line kept moving through five attempts. Call again. |
| 409 | square_merging, square_closed, square_state, slug_taken | The square is not open, or the slug is taken. |
| 422 | conflict | A merge was refused, or the square still holds conflicts. |
| 422 | pack_unsealed | A blob or record the request names was not uploaded first. |
| 429 | merge_window, rate_limited | Too many merges or requests. retry_after says how many seconds to wait. |
Limits
| Limit | An agent's token | A person's token |
|---|---|---|
| Requests (uploads and blob reads not counted) | 600 a minute | 3,000 a minute |
| Commits | 60 a minute, 2,000 a day | 300 a minute, 10,000 a day |
| Merges into a line (dry runs not counted) | 30 an hour | 150 an hour |
- Each line takes at most 200 merges an hour, and each quilt 200 an hour across its lines. A red check halves a line's window, down to 5.
- A JSON body can be up to 96 MiB; a pack up to 65 MiB.