The daemon serves JSON HTTP routes under /v1.
Accepted HTTP connections use 30-second read/write timeouts; slow requests
receive 408 Request Timeout without stopping the listener. Total
requests larger than 16 MiB, including headers and body, are rejected before
routing.
Trail accepts origin-form HTTP/1.0 or HTTP/1.1 request lines and
fixed-length requests only: malformed request lines, malformed or unterminated
header lines, bare CR/LF request-head line endings, header names with
surrounding whitespace, missing header terminators, duplicate or non-decimal
Content-Length, bodies without Content-Length, body length mismatches, and
Transfer-Encoding requests are rejected before routing.
Origin-form request targets must start with / and cannot contain fragments,
control characters, or backslash separators.
Requests with an Origin header are accepted only for well-formed loopback
origins such as localhost, 127.0.0.1, and ::1; invalid ports and opaque
origins such as null are rejected.
Routed requests must also include a Host header with a loopback host such as
localhost, 127.0.0.1, or [::1]; missing, malformed, non-loopback, and
invalid-port hosts are rejected before route handling.
Daemon listener connections are rate-limited per peer. The default limit is
600 requests per 60 seconds; excess requests receive 429 Too Many Requests.
Mutation request bodies are strict JSON objects. Unknown fields are rejected
with 400 Bad Request; they are not silently ignored. Top-level request
schemas in /v1/openapi.json set additionalProperties: false to match that
runtime behavior; patch edit/file variants are also emitted as strict nested
schemas.
For retry-safe mutation requests, send:
Idempotency-Key: <stable unique key>
Trail replays the first stored response when the same key is reused with the
same method, path, and body. Reusing the key for different request content
returns 400 Bad Request. Keys must be 1-200 non-control characters.
Unauthorized and forbidden responses are not cached.
Replayed mutation responses still produce audit rows, marked with
idempotency_replay: true in the audit summary.
Non-GET mutation attempts, including unauthorized and forbidden attempts, are
recorded in the local external_mutation_audit table with actor label,
method/path, status, inferred lane/ref when available, and a small redacted
result summary. Turn-scoped mutation failures can still be attributed through
the turn id in the request path. Actor labels identify the HTTP auth path, such
as http:bearer, http:x-trail-token, or http:no-auth; raw request bodies
and tokens are not stored.
GET /v1/health is unauthenticated. Other routes require a token unless the daemon starts with --no-auth.
Headers:
Authorization: Bearer <token>
x-trail-token: <token>
| Method | Path | Purpose |
|---|---|---|
| GET | /v1/health |
Service liveness. |
| GET | /v1/openapi.json |
OpenAPI document. |
| GET | /v1/doctor |
Workspace diagnostics. |
| GET | /v1/status |
Current status. |
| POST | /v1/record |
Record workspace changes. |
| GET | /v1/diff |
Diff range, roots, or dirty worktree. |
| GET | /v1/timeline |
Recent operations. |
| GET | /v1/why |
Line provenance. |
| GET | /v1/history |
File or line history. |
| GET | /v1/code-from |
Trace code from selector. |
| GET | /v1/config |
List config. |
| POST | /v1/config |
Set config. |
| GET | /v1/config/{key} |
Get config. |
| GET | /v1/ignore |
List ignore rules. |
| POST | /v1/ignore/patterns |
Add ignore rule. |
| DELETE | /v1/ignore/patterns |
Remove ignore rule. |
| POST | /v1/ignore/check |
Check ignored path. |
| POST | /v1/guardrails/check |
Preflight guardrails. |
| Method | Path | Purpose |
|---|---|---|
| GET | /v1/lanes |
List lanes. |
| POST | /v1/lanes |
Spawn lane. |
| GET | /v1/lanes/{lane_or_id} |
Show lane. |
| DELETE | /v1/lanes/{lane_or_id} |
Remove lane. |
| GET | /v1/lanes/{lane_or_id}/status |
Lane status. |
| GET | /v1/lanes/{lane_or_id}/review |
Review packet. |
| GET | /v1/lanes/{lane_or_id}/contribution |
Review bundle. |
| GET | /v1/lanes/{lane_or_id}/gates |
Gate history. |
| GET | /v1/lanes/{lane_or_id}/readiness |
Merge readiness. |
| GET | /v1/lanes/{lane_or_id}/refresh-preview?target=main |
Preview refreshing lane onto target branch. |
| GET | /v1/lanes/{lane_or_id}/handoff |
Handoff packet. |
| GET | /v1/lanes/{lane_or_id}/workdir |
Workdir path. |
| GET | /v1/lanes/{lane_or_id}/diff |
Lane branch diff. |
| POST | /v1/lanes/{lane_or_id}/read-file |
Read lane file. |
| POST | /v1/lanes/{lane_or_id}/hydrate |
Hydrate sparse workdir paths. |
| POST | /v1/lanes/{lane_or_id}/sync-workdir |
Sync workdir. |
| POST | /v1/lanes/{lane_or_id}/record |
Record lane workdir. |
| POST | /v1/lanes/{lane_or_id}/rewind |
Rewind lane branch. |
| POST | /v1/lanes/{lane}/merge |
Dry-run or explicitly direct-merge this lane into body field into. |
| POST | /v1/lanes/{lane_or_id}/tests |
Run test gate. |
| POST | /v1/lanes/{lane_or_id}/evals |
Run eval gate. |
| POST | /v1/lanes/{lane_or_id}/patches |
Apply lane patch. |
Patch requests accept either native edits or compatibility files; provide
one non-empty array, not both.
GET /v1/lanes/{lane_or_id}/status includes base_status when Trail can
resolve the workspace default branch. It reports the target branch/ref, target
change, lane base change, operations_behind, and whether the lane base is
stale.
POST /v1/lanes accepts workdir_mode values auto, virtual, sparse,
native-cow, portable-copy, fuse-cow, nfs-cow, and dokan-cow.
native-cow is strict, portable-copy permits per-file byte-copy fallback,
and auto tries the former before restarting as the latter. fuse-cow creates an empty workdir mountpoint and
records the fuse backend; a runtime such as trail agent start --workdir-mode fuse-cow mounts the FUSE view and keeps it alive while the
agent runs. The response includes requested_workdir_mode, resolved
workdir_mode, workdir_backend, optional materialization, sparse_paths,
and transparent_cow_available.
On macOS, nfs-cow reports workdir_backend: "nfs" and requires no
macFUSE installation. On Windows, dokan-cow reports workdir_backend: "dokan".
POST /v1/lanes/{lane_or_id}/hydrate accepts the same body as path-scoped
sync-workdir, but requires at least one paths entry.
POST /v1/lanes/{lane_or_id}/sync-workdir returns rescue_workdir when
force=true overwrites dirty materialized workdir files or replaces a
non-directory file at the lane workdir path. The rescue directory contains
copied recoverable regular files plus manifest.json.
POST /v1/lanes/{lane_or_id}/record accepts {"preview": true} to return a
non-committing record preview with changed paths, ignored paths, risky workdir
entries, oversized changed files, and policy allow/block details.
GET /v1/lanes/{lane_or_id}/refresh-preview?target=<branch> returns a
non-committing refresh/rebase preview with operations-behind, conflicts, changed
paths, and next steps.
POST /v1/lanes/{lane_or_id}/merge requires a JSON into target branch and
allows dry_run=true preflight. A non-dry-run merge into the workspace default
branch requires direct=true; otherwise enqueue with
POST /v1/lanes/merges/queue
and run the queue. The former branch-scoped merge-lane endpoint was removed.
| Method | Path | Purpose |
|---|---|---|
| GET/POST | /v1/sessions |
List or start sessions. |
| GET | /v1/sessions/current |
Current sessions. |
| GET | /v1/sessions/{session_id} |
Show session. |
| GET | /v1/sessions/{session_id}/context |
Session context. |
| POST | /v1/sessions/{session_id}/end |
End session. |
| GET/POST | /v1/approvals |
List or request approvals. |
| GET | /v1/approvals/{approval_id} |
Show approval. |
| POST | /v1/approvals/{approval_id}/decision |
Decide approval. |
| GET/POST | /v1/leases |
List or acquire leases. |
| DELETE | /v1/leases/{lease_id} |
Release lease. |
| POST | /v1/lanes/{lane_or_id}/claims |
Claim path. |
| GET/POST | /v1/anchors |
List or create anchors. |
| GET/DELETE | /v1/anchors/{anchor_id} |
Resolve or delete anchor. |
| GET/POST | /v1/lanes/merges/queue |
List or queue a lane merge. |
| POST | /v1/lanes/merges/queue/run |
Run queued lane merges. |
| GET | /v1/lanes/merges/queue/{selector}/explain |
Explain a queue item by queue id, lane id, or lane name. |
| DELETE | /v1/lanes/merges/queue/{selector} |
Remove a lane queue item. |
| GET | /v1/conflicts |
List conflicts. |
| GET | /v1/conflicts/{conflict_set_id}?limit=50 |
Show conflict with explanation evidence. |
| POST | /v1/conflicts/{conflict_set_id}/resolve |
Resolve conflict. |
Conflict explanations include the stored base_root, target_root, and
source_root snapshots used to reproduce the conflict, plus per-path
conflict_class values such as modify/modify, delete/modify,
rename/modify, binary, mode, and same_insertion_gap.
They also include known_resolutions for paths whose path/content conflict
signature matches a previously resolved conflict.
POST /v1/conflicts/{conflict_set_id}/resolve requires exactly one of take
or manual. Manual file values can be plain strings or objects with only
content, delete, and executable; unknown keys are rejected.
Bodyless mutation routes reject non-empty request bodies. This applies to path-only deletes such as lane removal, lease release, anchor deletion, and lane merge-queue removal.
| Method | Path | Purpose |
|---|---|---|
| POST | /v1/lane/turns |
Begin turn. |
| GET | /v1/lane/events |
List events. |
| GET | /v1/lane/spans |
List spans. |
| GET | /v1/lane/spans/summary |
Span summary. |
| GET/POST | /v1/lane/runs |
List or pause runs. |
| GET | /v1/lane/runs/{run_id} |
Show run. |
| POST | /v1/lane/runs/{run_id}/resume |
Resume run. |
| GET | /v1/lane/spans/{span_id} |
Show span. |
| POST | /v1/lane/spans/{span_id}/end |
End span. |
| GET | /v1/lane/turns/{turn_id} |
Show turn. |
| POST | /v1/lane/turns/{turn_id}/messages |
Add message. |
| POST | /v1/lane/turns/{turn_id}/events |
Add event. |
| POST | /v1/lane/turns/{turn_id}/spans |
Start span. |
| POST | /v1/lane/turns/{turn_id}/patches |
Apply turn patch. |
| POST | /v1/lane/turns/{turn_id}/end |
End turn. |
- OpenAPI paths:
trail/src/server/openapi/paths - Route handlers:
trail/src/server/route - Request types:
trail/src/server/request_types