Skip to main content

Reviews

For the Operator view, see erun review.

A review is the unit of work-to-be-merged. It binds a source branch to a target branch and tracks the state of that pairing through to merge.

Resource shape

{
"reviewId": "rev_01H...",
"tenantId": "tnt_01H...",
"authorUserId": "usr_01H...", // the caller that created the review; server-derived, never client-set
"name": "Refactor pricing engine",
"targetBranch": "main",
"sourceBranch": "feature-a",
"status": "OPEN", // OPEN | CLOSED | FAILED | READY | MERGE | MERGED
"lastFailedBuildId": "bld_...",
"lastReadyBuildId": "bld_...",
"lastMergedBuildId": "bld_...",
"createdAt": "2026-05-24T10:42:00Z",
"updatedAt": "2026-05-24T11:13:00Z"
}

authorUserId is always the authenticated caller that created the review. A POST /v1/reviews body that includes authorUserId has it ignored — a client cannot assert authorship for someone else.

Endpoints

MethodPathDescription
GET/v1/reviewsList reviews. Optional filters, all composable: ?targetBranch=<name>, ?sourceBranch=<name>, ?status=<OPEN|CLOSED|FAILED|READY|MERGE|MERGED>, ?authorUserId=<id>, ?reviewerUserId=<id>.
POST/v1/reviewsCreate a review. Body: name, sourceBranch, targetBranch. Refused with 409 Conflict if another non-MERGED/CLOSED review already proposes the same sourceBranch onto the same targetBranch.
GET/v1/reviews/{reviewId}Fetch one review.
PATCH/v1/reviews/{reviewId}/statusUpdate review status. Body: status, buildId.
GET/v1/reviews/merge-queueList reviews waiting to merge for a target branch (status READY, not yet promoted). Optional ?targetBranch=<name>.
POST/v1/reviews/merge-queue/advancePromote the next waiting review to MERGE and dispatch its merge-queue gate. Body: targetBranch. Does not itself produce MERGED — see Merge queue.
GET/v1/reviews/{reviewId}/reviewersList the review's reviewers.
POST/v1/reviews/{reviewId}/reviewersAdd a reviewer. Body: userId.
DELETE/v1/reviews/{reviewId}/reviewers/{userId}Remove a reviewer. 204 No Content on success.

Author, reviewers, and discovery

Every review has exactly one author and any number of reviewers:

  • Author. Set once, at creation, to the authenticated caller. It never changes and cannot be reassigned.
  • Reviewers. Zero or more users explicitly assigned to a review. Assigning a reviewer does not gate any status transition today — PATCH .../status still works the same regardless of who (if anyone) is assigned. A reviewer's tenant must match the review's tenant; a cross-tenant userId is refused.

The reviewer resource:

{
"tenantId": "tnt_01H...",
"reviewId": "rev_01H...",
"userId": "usr_01H...",
"createdAt": "2026-05-24T10:42:00Z",
"updatedAt": "2026-05-24T11:13:00Z"
}

The authorUserId and reviewerUserId list filters make two questions answerable directly, without client-side filtering: "my reviews" is GET /v1/reviews?authorUserId=<me>, and "reviews waiting on me" is GET /v1/reviews?reviewerUserId=<me>. Both compose with status, targetBranch, and sourceBranch.

One live review per branch pair

At most one non-MERGED, non-CLOSED review may propose a given sourceBranch onto a given targetBranch at a time. POST /v1/reviews for a branch pair that already has a live review fails with 409 Conflict. Once that review reaches MERGED or CLOSED, the same branch pair can be proposed again — branch history is unbounded, only live duplicates are refused. This prevents two reviews from independently reaching the merge queue for the same change, where the second would merge a branch the target already contains.

Status lifecycle

The status transitions are enforced server-side: an Agent cannot directly mark a review MERGE or MERGEDPATCH .../status refuses both. The only path to MERGED is the merge queue's own gate: once merge-queue/advance promotes a review to MERGE, the queue builds the prospective merge of the review's source onto its current target branch, gates that build with a real build, and pushes only on green. MERGED means that gate actually ran and actually passed, not that any caller asserted it.

Status meanings

StatusWhat it means
OPENReview exists, no successful build yet, no decision either way.
FAILEDThe latest build for this review failed. The corresponding build id is stored in lastFailedBuildId.
READYThe latest build succeeded; the review is mergeable.
MERGEPromoted to the head of its target branch's queue; the merge queue is building the prospective merge and gating it with a build right now.
MERGEDThe gate build passed and the merge was pushed to the target branch. Terminal. lastMergedBuildId records the gate build (kind GATE; it publishes nothing, so it carries no version — see Builds).
CLOSEDClosed without merge (abandoned). Terminal.

Merge queue

The merge queue is shared per target branch. Every READY review for a given targetBranch waits in a single FIFO — GET /v1/reviews/merge-queue?targetBranch=main lists that waiting line. The review currently being gated (status MERGE) has already left the waiting line; only one review per target branch may be MERGE at a time. POST /v1/reviews/merge-queue/advance promotes the head of the waiting line:

POST /v1/reviews/merge-queue/advance
Content-Type: application/json

{ "targetBranch": "main" }

The server picks the head of the waiting line for that target branch, transitions it to MERGE, and dispatches the merge queue's gate for it: fetch the review's target and source branches, build the prospective merge of the source onto the current target, run a real build against that prospective merge, and push it only if the build passes. The response to advance is the promoted (now MERGE) review, not the merged one — poll GET /v1/reviews/{reviewId} for the terminal MERGED or FAILED outcome. If the waiting line is empty, or another review is already MERGE for that target branch, the API returns an error.

This is what catches two reviews that are each green on their own but broken together: the second review promoted onto a target branch is always gated against whatever the first one just landed, not against a stale snapshot, so their combination is tested before the target branch ever sees it.

If a MERGE review's gate never reaches a terminal state (an operator-diagnosed stuck run), PATCH /v1/reviews/{reviewId}/status with {"status": "READY"} and no buildId requeues it: it moves back to READY and rejoins the waiting line at the tail rather than being promoted again immediately.

Errors

All endpoints return JSON error bodies:

{
"code": "INVALID_TRANSITION",
"message": "Cannot transition review from OPEN directly to MERGED",
"details": { "from": "OPEN", "to": "MERGED", "validTargets": ["FAILED", "READY", "CLOSED"] }
}
StatusWhenExample
400 Bad RequestMalformed JSON, missing required fields, type mismatches; a caller asserting MERGE or MERGED directly.POST /v1/reviews without sourceBranch; PATCH .../status with {"status": "MERGED"} — that status is written only by the merge queue's own gate result.
401 UnauthorizedNo Authorization header, or token validation failed.Bearer token expired.
403 ForbiddenToken valid; caller not allowed in this tenant.Agent of tenant A calling on tenant B.
404 Not FoundThe review or build id doesn't exist or isn't visible to the caller.GET /v1/reviews/rev_unknown.
409 ConflictInvalid state transition or queue-state mismatch; a second live review for a branch pair already proposed by a live review; a reviewer already assigned to the review.POST /v1/reviews/merge-queue/advance while another review is already MERGE for that target branch; POST /v1/reviews proposing feature-a onto main while another live review already does.
422 Unprocessable EntityStructurally valid but semantically invalid.PATCH status to READY without a successful build.
429 Too Many RequestsRate limit exceeded.Burst of POST /comments.
500 Internal Server ErrorServer error. Retry.Database unavailable.

Machine error codes

codeWhenHTTP status
INVALID_TRANSITIONPATCH /status with a transition not allowed by the Status lifecycle. details.validTargets lists the allowed next statuses.409
EMPTY_QUEUEPOST /merge-queue/advance against a target branch whose queue has no MERGE-status reviews.409
UNKNOWN_COMMITPATCH /status (or POST /builds) referencing a buildId whose commitId doesn't exist on the review's sourceBranch.422
INVALID_BODYRequest body missing required field or fails type validation. details.field names the offender.400
INVALID_TARGET_BRANCHtargetBranch is not a valid branch name.400
EXPIRED_PAGE_TOKENpageToken is stale or malformed.400

Pagination + rate limits

List endpoints page at 100 items max; see API protocol · Pagination. Rate-limit buckets per token: read 600 req/min, write 60 req/min, merge-queue-advance 10 req/min. Full table: API protocol · Rate limits.