Merge preview response
The merge preview lists what a branch would change on main if you merged it
now. This page documents every field of the REST response:
GET /v1/branches/{id}/merge-preview
The CLI prints its own JSON form of the preview:
tiden branch merge-preview --product-id <product-id> --name <branch-name> --format json
That output is not the REST response. See CLI JSON output for how it differs.
The endpoint uses the usual API authentication. For what the preview means to a reviewer, see Merge preview.
Schema version
The response carries schemaVersion, an integer. The current version is 3.
schemaVersionincreases by one whenever a field is added to, or changed in, the response or one of its preview item types: the modification items, the duplicate items,stats,intentSession,blockingSessions, andautoMerge.- Existing fields are never renamed, removed, or given a different type within the public API.
- Each change is listed in Schema history on this page and in the Tiden changelog.
- The spec, test, and component objects inside the response are shared with
the rest of the API. They can gain fields without a change to
schemaVersion.
If your client rejects unknown keys, read the REST endpoint, check
schemaVersion first, and update the client when the number is higher than the
one it was written for.
JSON encoding
The REST API is generated from the Tiden gRPC API, so the JSON follows the protobuf JSON mapping:
- Field names are camelCase, such as
testAdditionsandhasConflict. - Fields with an empty or zero value are still sent: an empty list is
[], an empty string is"", a zero number is0, andfalseisfalse. - An object field with no value is sent as
null, for exampleintentSessionon a branch with no intent session. - Fields marked optional on this page are left out of the JSON when they have no value.
- Timestamps are RFC 3339 strings in UTC, such as
"2026-10-27T09:00:00Z".
CLI JSON output
tiden branch merge-preview --format json decodes the REST response and
encodes it again. Its output can differ from the REST response:
- It leaves out fields the CLI does not know. The current CLI leaves out the
autoMergefieldsgateRed,conflictsResolved,closingFallback, andabandonedAt. An older CLI can leave out more, such asschemaVersion. - It leaves out these fields when they are empty or zero:
schemaVersion,previewDigest,linkProposals,testDuplicates,stats.testDuplicates,autoMerge, and every field ofautoMergeexceptstate. - Spec, test, component, and session objects pass through as the server sent them.
For a check that rejects unknown or missing keys, read the REST endpoint.
Top-level fields
In the API, a spec is the requirement resource, so spec changes use the plain
names additions, modifications, and deletions.
| Field | Type | Meaning |
|---|---|---|
schemaVersion | integer | Version of this response shape. See Schema version. |
previewDigest | string | Fingerprint of what the branch would put into main: 64 lowercase hex characters (SHA-256). See Merge only the reviewed preview. |
additions | list of spec | Specs the branch created. |
modifications | list of modification of spec | Specs the branch changed. |
deletions | list of spec | Specs the branch deleted, as they are on main. |
testAdditions | list of test | Suites and cases the branch created, without the duplicates. |
testModifications | list of modification of test | Suites and cases the branch changed. |
testDeletions | list of test | Suites and cases the branch deleted. |
testDuplicates | list of duplicate | Suites and cases the branch created that already exist on main. The merge still adds them. |
componentAdditions | list of component | Components the branch created. |
componentModifications | list of modification of component | Components the branch changed. |
componentDeletions | list of component | Components the branch deleted. |
stats | stats | Counts of the lists above and of conflicts. |
intentSession | session or null | The branch's intent session state and its verdict. See Intent sessions for which session each field describes. null when the branch has no intent session. |
blockingSessions | list of session | Sessions on the branch whose specs were not written down yet. These are the sessions that can stop the merge. |
hasRequirementChanges | boolean | true when the branch holds spec changes, counted the way the merge check counts them. |
linkProposals | list of link proposal | Test-to-spec link proposals that the merge makes into links on main: those with the status proposed or accepted. Rejected proposals are not listed. |
autoMerge | auto-merge | Optional. Where the automatic merge of an intent branch stands. Left out for a branch that Tiden does not track for automatic merge. |
Lists that hold no items are []. A branch with no test or component changes
has empty test and component lists.
Item shapes by kind of change
The items in the lists do not share one shape. Each kind of change has its own:
| Kind of change | Lists | Item shape |
|---|---|---|
| Addition | additions, testAdditions, componentAdditions | The full entity object: a spec, test, or component. |
| Deletion | deletions, testDeletions, componentDeletions | The full entity object, as above. |
| Modification | modifications, testModifications, componentModifications | branchVersion, mainVersion, hasConflict, conflictingFields. |
| Duplicate | testDuplicates | branchVersion, mainVersion, match. |
Modification items
A modification item holds both versions of one spec, test, or component.
| Field | Type | Meaning |
|---|---|---|
branchVersion | entity object | The item as the branch has it. This is the version the merge writes. |
mainVersion | entity object | The item as main has it now. |
hasConflict | boolean | true when main changed this item after the branch copied it. See Conflict resolution. |
conflictingFields | list of strings | Names of the fields whose values differ between the two versions, in snake_case, such as title, parent_id, or component_id. A spec whose decisions conflict also lists decisions:<spec-id>. |
Duplicate items
A duplicate item is a suite or case the branch created that already exists on
main.
| Field | Type | Meaning |
|---|---|---|
branchVersion | test | The suite or case the branch created. |
mainVersion | test | The suite or case on main that it matches. |
match | string | How it matched. suite_path: a suite with the same title path from the root. signature: a case with the same signature. |
Link proposals
A link proposal is a test-to-spec link that the branch proposes. The merge
makes it a link on main.
| Field | Type | Meaning |
|---|---|---|
id | string | ID of the proposal. |
testId | string | ID of the test. |
requirementId | string | ID of the spec. |
testSeqNum | integer | Sequence number of the test. |
requirementSeqNum | integer | Sequence number of the spec. |
status | string | proposed or accepted. |
Spec
A spec object, the requirement resource of the API. It can gain fields
without a change to schemaVersion.
| Field | Type | Meaning |
|---|---|---|
id | string | Spec ID. |
productId | string | Product ID. |
parentId | string | Optional. Parent spec in the tree. Left out for a top-level spec. |
componentId | string | Optional. Component the spec belongs to. |
title | string | Title. |
content | string | Body, in Markdown. |
position | integer | Order among its siblings. |
createdBy | string | ID of the user who created it. |
childrenCount | integer | Number of direct child specs. |
createdAt | timestamp | When it was created. |
updatedAt | timestamp | When it last changed. |
seqNum | integer | Product-wide number used in references such as PRJ-12. |
status | string | Spec status. |
priority | string | Spec priority. |
assigneeId | string | Optional. Assigned user. |
branchId | string | Branch that holds this copy. |
sourceId | string | Optional. The main spec this branch copy was made from. Left out for a spec the branch created. |
branchStatus | string | "", added, modified, or unchanged. |
type | string | Spec type. |
sourceCount | integer | Number of sources. |
sources | list of objects | Where the spec came from: repository file anchors, documentation URLs, and similar records. Can be empty; sourceCount has the count. |
derivedState | string | "" or machine_unpromoted: Tiden wrote the spec from merged code and nobody has directed work on it yet. Cleared when a linked test runs or an intent session settles against the spec. |
decisions | list of objects | The spec's decision records. Can be empty; decisionCount has the count. |
decisionCount | integer | Number of decision records. |
Test
A test object is a suite or a case; kind tells which. Case-only fields are
zero or empty on a suite. It can gain fields without a change to
schemaVersion.
| Field | Type | Meaning |
|---|---|---|
id | string | Test ID. |
productId | string | Product ID. |
branchId | string | Branch that holds this copy. |
parentId | string | Optional. Parent suite. Left out at the root. |
kind | string | suite or case. |
title | string | Title. |
description | string | Description. |
position | integer | Order among its siblings. |
seqNum | integer | Optional. Case number. Left out for a suite. |
status | string | Case status. |
priority | string | Case priority. |
type | string | Case type. |
layer | string | Test layer. |
muted | boolean | true when the case is muted. |
componentId | string | Optional. Component the case belongs to. |
assigneeId | string | Optional. Assigned user. |
tags | list of strings | Tags. |
customFields | object | Custom field values, as a JSON object. |
steps | list of objects | The case's steps. A step can hold child steps. |
origin | string | manual or imported. |
framework | string | Test framework of an imported test. |
externalId | string | ID of the test in the source it was imported from. |
externalPath | string | Path of the test in that source. |
filePath | string | Repository file of the test. |
lastSyncedAt | timestamp or null | When the import last updated it. null for a test that was never imported. |
sourceId | string | Optional. The main test this branch copy was made from. |
branchStatus | string | "", added, modified, or unchanged. |
childrenCount | integer | Number of direct children, suites and cases. |
directCaseCount | integer | Suite only. Number of direct child cases. |
directSuiteCount | integer | Suite only. Number of direct child suites. |
descendantCaseCount | integer | Suite only. Number of cases at any depth below it. |
linkedRequirementCount | integer | Case only. Number of specs linked to the case. |
createdBy | string | ID of the user who created it. |
createdAt | timestamp | When it was created. |
updatedAt | timestamp | When it last changed. |
isAutomated | boolean | true when the case is automated. |
signature | string | Case identity from a reporter. See Signature. |
testopsId | integer | Optional. TestOps case ID linked to the case. Left out when none is set. |
authorType | string | user, agent, reporter, import, system, or unknown. |
authorId | string | ID of the author. |
authorName | string | Name of the author. |
parameterGroups | list of objects | Parameter sets the case runs with. |
latestExecution | object or null | The latest reported result: status, duration, and timing. |
attachments | list of strings | Attachments. |
relations | list of objects | Relations to other tests. |
sourceSha | string | Git blob SHA of the source the test was last imported from. Empty for tests not imported from a repository snapshot. |
latestExecutionStale | boolean | true when latestExecution ran against an earlier sourceSha than the current one. |
repository | string | Repository of the case. Empty when unknown. |
mergedIntoId | string | Optional. Set while this case is being merged, or has been merged, into another case. |
redirectedFrom | string | Set only by a single-test read that was redirected to a merged case. Empty here. |
keys | list of objects | Identity keys of the case. Filled only by a single-test read. Empty here. |
Component
A component object. It can gain fields without a change to schemaVersion.
| Field | Type | Meaning |
|---|---|---|
id | string | Component ID. |
productId | string | Product ID. |
name | string | Name. |
description | string | Description. |
createdAt | timestamp | When it was created. |
updatedAt | timestamp | When it last changed. |
branchId | string | Branch that holds this copy. |
sourceId | string | Optional. The main component this branch copy was made from. |
branchStatus | string | "", added, modified, or unchanged. |
repository | string | Optional. Canonical repository ID, such as github.com/acme/backend. Left out for a component with no repository. |
componentPaths | list of strings | Path prefixes inside the repository. Empty means the whole repository. |
repositoryAliases | list of strings | Local checkout paths that map to the repository. |
runtimePaths | list of strings | Path prefixes that error stack frames show at runtime. |
Stats
stats counts the lists above.
| Field | Type | Meaning |
|---|---|---|
additions | integer | Spec additions. |
modifications | integer | Spec modifications. |
deletions | integer | Spec deletions. |
testAdditions | integer | Test additions, duplicates included, because the merge adds them. It equals the length of testAdditions plus the length of testDuplicates. |
testModifications | integer | Test modifications. |
testDeletions | integer | Test deletions. |
testDuplicates | integer | How many of the test additions are duplicates. |
componentAdditions | integer | Component additions. |
componentModifications | integer | Component modifications. |
componentDeletions | integer | Component deletions. |
conflicts | integer | Modification items with hasConflict set, over specs, tests, and components together. |
Intent sessions
intentSession and each row of blockingSessions have the same shape. An
intent session is the record of one coding agent session on the branch.
Each blockingSessions row describes one session. The fields of
intentSession can come from two sessions:
sessionId,status, andsettleddescribe the latest session on the branch.agent,objective,gitRepo, andverdictdescribe the latest session that is not a Tiden system round. A system round is a session that Tiden runs itself, such as the closing pass or a checkpoint merge round. Itsagentistidenand it never carries a verdict.verdictcomes from the latest such session that has been graded.
| Field | Type | Meaning |
|---|---|---|
sessionId | string | Session ID. |
status | string | provisional, materialized, or closed. |
settled | boolean | true when a settlement is recorded for the session. A settlement says which specs the session wrote, or that it had nothing to write down. A closed session can still be unsettled. |
agent | string | The coding agent that ran the session. |
objective | string | The objective the session recorded when it started. |
gitRepo | string | The git repository the session worked in. |
verdict | string | Filled on intentSession only. The key is also present on every blockingSessions row, always ""; read intentSession.verdict. How the files the session changed compare with the files anchored to the specs the session retrieved when it started. A session is graded when the agent reconciles it during the session (tiden intent reconcile) and when it closes, so an open session can carry a verdict. The latest grading wins. Empty until the first grading. |
Verdict values:
| Value | Meaning |
|---|---|
confirm | Every changed file is among the files anchored to the specs the session retrieved at start, or the session changed no files. |
amend | Some changed files are outside those anchored files, or the start retrieved no anchored files. |
flag-divergence | No changed file is among those anchored files. |
"" | The session has not been graded yet. |
When the verdict is amend or flag-divergence, tiden branch merge-preview
prints a warning on stderr, in text and in JSON mode, whatever the session
status:
intent session verdict is "amend": the session changed files outside the files anchored to the specs it retrieved at start; review the changes before you merge
The JSON on stdout does not change. Review the changes outside those files before you merge.
blockingSessions lists the sessions that the merge check looks at: open
sessions, and sessions closed as abandoned, that recorded no settlement.
settled is false on every row. The merge refuses with
INTENT_UNDISTILLED when hasRequirementChanges is true and
blockingSessions is not empty. Use these two fields to predict that result.
Do not compute it from the lengths of additions, modifications, and
deletions: those lists leave out copies that do not differ from main, so
they can disagree with the check.
Auto-merge
autoMerge describes the automatic merge of an intent branch. See
Automatic merge of intent branches.
| Field | Type | Meaning |
|---|---|---|
state | string | Where the branch is in the automatic merge. Values are below. |
reason | string | Code for why the branch waits. Empty when nothing stops it. Values are below. |
reasonDetail | string | Text that goes with reason. |
nextCheckAt | timestamp or null | When Tiden looks at the intent next: the time the merge box shows as "merges automatically after". |
holdUntil | timestamp or null | End of the hold. It stays set after the hold ends, so a hold is active only while this time is in the future. |
holdReason | string | Reason given for the hold. |
heldByName | string | Who set the hold. |
holdSource | string | agent or person. |
openPromises | list of strings | Texts of the promises still open, for a partly delivered intent. |
partialUntil | timestamp or null | When a partly delivered intent merges its shipped part as a checkpoint. |
successorBranchId | string | The intent branch that carries the open promises after a checkpoint merge. |
successorBranchName | string | Name of that branch. |
gateRed | boolean | The branch Quality Gate was Blocked, or could not be computed, when Tiden merged. |
conflictsResolved | integer | Number of conflicts the automatic merge resolved. |
closingFallback | boolean | The branch merged without the closing pass. |
abandonedAt | timestamp or null | When Tiden closed the intent as abandoned. Set while state is abandoned. |
state values:
| Value | Meaning |
|---|---|
in_progress | Work on the intent is still going on. |
countdown | The conditions are met and Tiden merges after nextCheckAt. |
closing | The closing pass is running. |
partial | Promises are still open. See openPromises and partialUntil. |
merged | Tiden merged the branch. |
checkpoint_merged | Tiden merged the shipped part and moved the open promises to the successor branch. |
abandoned | Tiden closed the intent as abandoned. |
off | Tiden does not merge this branch by itself. |
reason codes:
| Value | Meaning |
|---|---|
product_setting_off | Automatic intent merge is off for the product. |
branch_not_open | The branch is not open. |
not_intent_branch | The branch is not an intent branch. |
on_hold | A hold is active. |
pr_open | A linked pull request is still open. |
pr_state_unknown | Tiden cannot read the state of a linked pull request. |
round_active | An agent session is still working on the intent. |
no_merged_pr | No linked pull request has merged. |
quiet_period | The 24 hours without activity have not passed. |
closing_pass | The closing pass is running. |
promise_open | A promise of the intent is still open. |
no_acting_user | No member with access can be the actor of the merge. |
merge_failed | The last merge attempt failed. |
closing_conflicts | The closing pass found conflicts it has to settle. |
changed_during_merge | The intent or main changed while Tiden was merging it, for example a spec conflict that the preview did not show. The merge did not go ahead; Tiden checks the intent again at once. |
New codes can appear. Show reasonDetail for a code your client does not know.
Merge only the reviewed preview
previewDigest covers the branch side of the merge: its specs and their
decisions, tests, components, deletions, and link proposals. It changes when
any of these change on the branch. It does not change when only main moves,
and it holds no timestamps or session state.
To merge only what you reviewed, send the digest back with the merge:
tiden branch merge \
--product-id <product-id> \
--name <branch-name> \
--expect-preview <digest>
Over REST, send it as expectPreviewDigest in the body of
POST /v1/branches/{id}/merge. When it differs from the branch's current
digest, Tiden refuses the merge with 409 Conflict and a message that starts
with MERGE_PREVIEW_STALE:. Nothing is merged. Run the merge preview again,
review it, and merge with its new digest.
A merge without the digest does not do this check. With --auto-resolve, the
CLI reads a fresh preview first and stops with exit code 3 when its digest
differs from --expect-preview.
Example
A branch that adds one spec and has a closed intent session with the verdict
amend. The spec object is shortened here; the API sends every field listed
in Spec.
{
"schemaVersion": 3,
"previewDigest": "50d858e0985ecc7f60418aaf0cc5ab587f42c2570a884095a9e8ccacd0f6545c",
"additions": [
{
"id": "7c1e2a4b-0d3f-4b8e-9a51-2f6c8d0e1b37",
"title": "Export works offline",
"branchStatus": "added"
}
],
"modifications": [],
"deletions": [],
"stats": {
"additions": 1,
"modifications": 0,
"deletions": 0,
"conflicts": 0,
"testAdditions": 0,
"testModifications": 0,
"testDeletions": 0,
"componentAdditions": 0,
"componentModifications": 0,
"componentDeletions": 0,
"testDuplicates": 0
},
"testAdditions": [],
"testModifications": [],
"testDeletions": [],
"componentAdditions": [],
"componentModifications": [],
"componentDeletions": [],
"intentSession": {
"sessionId": "5b0d9e62-3c71-4f2a-8e19-a7d4c6b2f803",
"status": "closed",
"settled": true,
"agent": "claude-code",
"gitRepo": "github.com/acme/backend",
"objective": "Add offline export",
"verdict": "amend"
},
"blockingSessions": [],
"hasRequirementChanges": true,
"testDuplicates": [],
"linkProposals": []
}
autoMerge is left out because this branch has no automatic merge record.
Schema history
| Version | Date | Change |
|---|---|---|
| 1 | 2026-10-27 | First published version. Adds schemaVersion, intentSession.verdict, and blockingSessions[].verdict (always ""; read intentSession.verdict). intentSession now fills agent, objective, and gitRepo. |
| 2 | 2026-10-29 | Adds linkProposals. |
| 3 | 2026-10-30 | Adds previewDigest. Send it as expectPreviewDigest to merge only the reviewed preview. |