Stacks API

The stack surface lives under /api/v3 beside the rest of the REST API. Reads are public on a public repository; writes require an oa_pat_ bearer token with forge:write scope and an Idempotency-Key header.

Routes

GET  /api/v3/repos/:owner/:repo/pulls
GET  /api/v3/repos/:owner/:repo/pulls/:pull_number
POST /api/v3/repos/:owner/:repo/pulls
PATCH /api/v3/repos/:owner/:repo/pulls/:pull_number

GET  /api/v3/repos/:owner/:repo/stacks
GET  /api/v3/repos/:owner/:repo/stacks/:stack_number
POST /api/v3/repos/:owner/:repo/stacks
POST /api/v3/repos/:owner/:repo/stacks/:stack_number/append
POST /api/v3/repos/:owner/:repo/stacks/:stack_number/rebase
POST /api/v3/repos/:owner/:repo/stacks/:stack_number/merge
POST /api/v3/repos/:owner/:repo/stacks/:stack_number/unstack
POST /api/v3/repos/:owner/:repo/stacks/:stack_number/dissolve

GET  /api/v3/repos/:owner/:repo/stacks/:stack_number/operations/:operation_id
POST /api/v3/repos/:owner/:repo/stacks/:stack_number/operations/:operation_id/continue
POST /api/v3/repos/:owner/:repo/stacks/:stack_number/operations/:operation_id/abort

PUT  /api/v3/repos/:owner/:repo/pulls/:pull_number/merge-async
GET  /api/v3/repos/:owner/:repo/pulls/:pull_number/merge-async/:operation_id

The stack payload

A stack read returns the stack's number, trunk_ref, state, health, version, and its active entries in position order. Each entry carries its position and the pull request's number, head (ref and sha), and base (ref and sha), so one read gives you the complete chain and the exact commits the stack has observed.

curl https://openagents.com/api/v3/repos/acme/api/stacks/1

Idempotency

Every write takes an Idempotency-Key. Repeating a request with the same key and the same body returns the original result instead of acting twice; the same key with a different body is refused with 409 Conflict. Use a fresh UUID per intended action and retry with the same one on network failure.

Optimistic concurrency

A stack's version increments on every structural change. Writes accept an optional expected_stack_version, and the mutating operations verify branch heads against what the stack last observed before moving anything. When either check fails you get a conflict rather than a mutation built on a stack you have not seen — read again, re-decide, and resubmit.

Operations

Rebase and merge run as durable operations. The submitting request returns 202 Accepted with the operation, and GET .../operations/:operation_id reports its state: pending, running, waiting_for_conflict_resolution, succeeded, partially_succeeded, failed, or cancelled. One operation runs per stack at a time; submitting while one is active returns 409 Conflict carrying the active operation_id. A paused rebase resumes through continue or abort.

Worked example

Create three chained pull requests, stack them, and merge the bottom two:

repo=https://openagents.com/api/v3/repos/acme/api
auth="Authorization: Bearer $OPENAGENTS_TOKEN"

# One pull request per layer: base of each is the head of the one below.
curl -X POST -H "$auth" -H "Content-Type: application/json" \
  -d '{"title": "Layer 1", "head": "layer-1",
       "head_repository": "acme/api", "base": "main"}' $repo/pulls
curl -X POST -H "$auth" -H "Content-Type: application/json" \
  -d '{"title": "Layer 2", "head": "layer-2",
       "head_repository": "acme/api", "base": "layer-1"}' $repo/pulls
curl -X POST -H "$auth" -H "Content-Type: application/json" \
  -d '{"title": "Layer 3", "head": "layer-3",
       "head_repository": "acme/api", "base": "layer-2"}' $repo/pulls

# Stack them bottom-first.
curl -X POST -H "$auth" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"trunk_ref": "main", "pull_requests": [1, 2, 3]}' $repo/stacks

# Land layers 1 and 2; layer 3 restacks automatically.
curl -X POST -H "$auth" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"pull_request_number": 2, "merge_method": "merge"}' $repo/stacks/1/merge

Next steps