openapi: 3.0.3 info: title: byre version: 0.0.0 paths: /api/me: get: operationId: getMe responses: "200": description: The calling user. content: application/json: schema: { $ref: "#/components/schemas/User" } put: operationId: updateMe requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/UserUpdate" } responses: "200": description: The updated user. content: application/json: schema: { $ref: "#/components/schemas/User" } "400": { $ref: "#/components/responses/Error" } /api/me/notifications: put: operationId: updateNotificationPrefs description: Sets which inbox items the calling user is sent push notifications for. requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/NotificationPrefs" } responses: "200": description: The updated user. content: application/json: schema: { $ref: "#/components/schemas/User" } "400": { $ref: "#/components/responses/Error" } /api/me/push-subscriptions: post: operationId: addPushSubscription description: Registers a browser's push subscription for the calling user. Registering an endpoint again replaces it. requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/PushSubscription" } responses: "204": description: Registered. "400": { $ref: "#/components/responses/Error" } delete: operationId: removePushSubscription parameters: - { name: endpoint, in: query, required: true, schema: { type: string } } responses: "204": description: Removed, or was not registered. /api/push/key: get: operationId: getPushKey responses: "200": description: The server's VAPID public key, the applicationServerKey for PushManager.subscribe. content: application/json: schema: { $ref: "#/components/schemas/PushKey" } /api/projects: get: operationId: listProjects responses: "200": description: All projects. content: application/json: schema: type: array items: { $ref: "#/components/schemas/Project" } post: operationId: createProject requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/ProjectCreate" } responses: "201": description: The new project. content: application/json: schema: { $ref: "#/components/schemas/Project" } "400": { $ref: "#/components/responses/Error" } /api/projects/{projectId}: parameters: - { name: projectId, in: path, required: true, schema: { type: string } } get: operationId: getProject responses: "200": description: The project. content: application/json: schema: { $ref: "#/components/schemas/Project" } "404": { $ref: "#/components/responses/Error" } /api/projects/{projectId}/tasks: parameters: - { name: projectId, in: path, required: true, schema: { type: string } } get: operationId: listTasks responses: "200": description: The project's tasks, newest first. content: application/json: schema: type: array items: { $ref: "#/components/schemas/Task" } "404": { $ref: "#/components/responses/Error" } post: operationId: createTask description: Creates the task's branch and worktree from the project's default branch. requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/TaskCreate" } responses: "201": description: The new task. content: application/json: schema: { $ref: "#/components/schemas/Task" } "400": { $ref: "#/components/responses/Error" } "404": { $ref: "#/components/responses/Error" } /api/tasks/{taskId}: parameters: - { name: taskId, in: path, required: true, schema: { type: string } } get: operationId: getTask responses: "200": description: The task. content: application/json: schema: { $ref: "#/components/schemas/Task" } "404": { $ref: "#/components/responses/Error" } /api/tasks/{taskId}/unmerged: parameters: - { name: taskId, in: path, required: true, schema: { type: string } } get: operationId: getUnmerged description: What the task's worktree holds that its project's default branch doesn't. responses: "200": description: The unmerged changes. content: application/json: schema: { $ref: "#/components/schemas/Unmerged" } "404": { $ref: "#/components/responses/Error" } "409": { $ref: "#/components/responses/Error" } /api/tasks/{taskId}/merge: parameters: - { name: taskId, in: path, required: true, schema: { type: string } } post: operationId: mergeTask description: > Starts the same agent turn as merge-in. When that turn ends normally, byre squashes the task's worktree into one commit titled with the task's merge_title on the project's default branch, fast-forwards the project's checkout to it, then removes the worktree and branch and marks the task merged. Nothing is pushed. Refused while a turn is running or queued. responses: "202": description: The merge turn was started or queued. content: application/json: schema: { $ref: "#/components/schemas/AgentSession" } "400": { $ref: "#/components/responses/Error" } "404": { $ref: "#/components/responses/Error" } "409": { $ref: "#/components/responses/Error" } /api/tasks/{taskId}/merge-in: parameters: - { name: taskId, in: path, required: true, schema: { type: string } } post: operationId: mergeIn description: > Asks the task's agent to merge the project's default branch into the task branch, resolving any conflicts and regenerating generated files. Sent to the task's idle session, or starts a session when it has none. Refused while a turn is running or queued. responses: "202": description: The merge turn was started or queued. content: application/json: schema: { $ref: "#/components/schemas/AgentSession" } "400": { $ref: "#/components/responses/Error" } "404": { $ref: "#/components/responses/Error" } "409": { $ref: "#/components/responses/Error" } /api/tasks/{taskId}/discard: parameters: - { name: taskId, in: path, required: true, schema: { type: string } } post: operationId: discardTask description: Removes the task's worktree and branch without merging. The task's record and events remain. responses: "200": description: The discarded task. content: application/json: schema: { $ref: "#/components/schemas/Task" } "404": { $ref: "#/components/responses/Error" } "409": { $ref: "#/components/responses/Error" } /api/tasks/{taskId}/events: parameters: - { name: taskId, in: path, required: true, schema: { type: string } } get: operationId: listTaskEvents description: The task's events after a seq, oldest first. Page by passing the last seq back as after. parameters: - { name: after, in: query, required: false, schema: { type: integer, format: int64 } } - { name: limit, in: query, required: false, schema: { type: integer, minimum: 1, maximum: 1000 } } responses: "200": description: Up to limit events (default 500). content: application/json: schema: type: array items: { $ref: "#/components/schemas/Event" } "404": { $ref: "#/components/responses/Error" } /api/tasks/{taskId}/changes: parameters: - { name: taskId, in: path, required: true, schema: { type: string } } get: operationId: getTaskChanges description: > Everything in the task's worktree (committed, staged, unstaged and untracked, respecting .gitignore) as a tree, compared with the merge-base of the task branch and the project's default branch, or with since when given. The worktree's own index is not touched. parameters: - { name: since, in: query, required: false, schema: { type: string }, description: "A commit to compare with instead of the merge-base, such as a user.message event's head." } responses: "200": description: Per-file stats. content: application/json: schema: { $ref: "#/components/schemas/TaskChanges" } "400": { $ref: "#/components/responses/Error" } "404": { $ref: "#/components/responses/Error" } "409": { $ref: "#/components/responses/Error" } /api/tasks/{taskId}/diff: parameters: - { name: taskId, in: path, required: true, schema: { type: string } } get: operationId: getTaskDiff description: The unified diff of one file between a base and tree from getTaskChanges. parameters: - { name: base, in: query, required: true, schema: { type: string } } - { name: tree, in: query, required: true, schema: { type: string } } - { name: path, in: query, required: true, schema: { type: string } } - { name: old_path, in: query, required: false, schema: { type: string } } responses: "200": description: The patch. content: application/json: schema: { $ref: "#/components/schemas/FileDiff" } "400": { $ref: "#/components/responses/Error" } "404": { $ref: "#/components/responses/Error" } "409": { $ref: "#/components/responses/Error" } /api/tasks/{taskId}/snapshots: parameters: - { name: taskId, in: path, required: true, schema: { type: string } } get: operationId: listSnapshots description: > The task's snapshots, oldest first. Each is a commit of the worktree as if everything were staged, kept at refs/byre/tasks/{taskId}/snap/{n} until the task is merged or discarded; the rows remain after that. responses: "200": description: The snapshots. content: application/json: schema: type: array items: { $ref: "#/components/schemas/Snapshot" } "404": { $ref: "#/components/responses/Error" } /api/tasks/{taskId}/sessions: parameters: - { name: taskId, in: path, required: true, schema: { type: string } } get: operationId: listSessions responses: "200": description: The task's agent sessions, oldest first. content: application/json: schema: type: array items: { $ref: "#/components/schemas/AgentSession" } "404": { $ref: "#/components/responses/Error" } post: operationId: createSession description: Starts an agent session in the task's worktree and sends it the prompt as its first turn. requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/SessionCreate" } responses: "201": description: The new session. content: application/json: schema: { $ref: "#/components/schemas/AgentSession" } "400": { $ref: "#/components/responses/Error" } "404": { $ref: "#/components/responses/Error" } "409": { $ref: "#/components/responses/Error" } /api/sessions/{sessionId}: parameters: - { name: sessionId, in: path, required: true, schema: { type: string } } get: operationId: getSession responses: "200": description: The session. content: application/json: schema: { $ref: "#/components/schemas/AgentSession" } "404": { $ref: "#/components/responses/Error" } /api/sessions/{sessionId}/messages: parameters: - { name: sessionId, in: path, required: true, schema: { type: string } } post: operationId: sendMessage description: Starts a new turn with this message. The session must be idle. requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/MessageCreate" } responses: "202": description: The turn has started. content: application/json: schema: { $ref: "#/components/schemas/AgentSession" } "400": { $ref: "#/components/responses/Error" } "404": { $ref: "#/components/responses/Error" } "409": { $ref: "#/components/responses/Error" } /api/sessions/{sessionId}/compact: parameters: - { name: sessionId, in: path, required: true, schema: { type: string } } post: operationId: compactSession description: > Asks the idle session's agent for a handoff (goal, decisions, state, dead ends, open questions, files). When that turn ends, the session is completed and a new session on the same task and model starts with the handoff as its first message. responses: "202": description: The handoff turn was started or queued. content: application/json: schema: { $ref: "#/components/schemas/AgentSession" } "404": { $ref: "#/components/responses/Error" } "409": { $ref: "#/components/responses/Error" } /api/sessions/{sessionId}/interrupt: parameters: - { name: sessionId, in: path, required: true, schema: { type: string } } post: operationId: interruptSession description: Ends the current turn. responses: "202": description: The interrupt was delivered. content: application/json: schema: { $ref: "#/components/schemas/AgentSession" } "404": { $ref: "#/components/responses/Error" } "409": { $ref: "#/components/responses/Error" } /api/blobs: post: operationId: uploadBlob description: > Stores an image for use in a message. The body is the raw image; its type is detected from the bytes and must be PNG, JPEG, GIF or WebP. At most 5 MiB. requestBody: required: true content: image/png: { schema: { type: string, format: binary } } image/jpeg: { schema: { type: string, format: binary } } image/gif: { schema: { type: string, format: binary } } image/webp: { schema: { type: string, format: binary } } responses: "201": description: The stored blob. content: application/json: schema: { $ref: "#/components/schemas/Blob" } "400": { $ref: "#/components/responses/Error" } "413": { $ref: "#/components/responses/Error" } /api/blobs/{blobId}: parameters: - { name: blobId, in: path, required: true, schema: { type: string } } get: operationId: getBlob responses: "200": description: The blob's bytes, with its content type. content: image/png: { schema: { type: string, format: binary } } image/jpeg: { schema: { type: string, format: binary } } image/gif: { schema: { type: string, format: binary } } image/webp: { schema: { type: string, format: binary } } "404": { $ref: "#/components/responses/Error" } /api/inbox: get: operationId: listInbox description: > Inbox items, highest priority first (blocked, gate, review, fyi), then oldest first. Filters narrow the one query to a project's or a task's view. parameters: - { name: project_id, in: query, required: false, schema: { type: string } } - { name: task_id, in: query, required: false, schema: { type: string } } - { name: state, in: query, required: false, schema: { $ref: "#/components/schemas/InboxState" } } responses: "200": description: Matching items with their responses. Defaults to open items. content: application/json: schema: type: array items: { $ref: "#/components/schemas/InboxItem" } /api/inbox/{itemId}: parameters: - { name: itemId, in: path, required: true, schema: { type: string } } get: operationId: getInboxItem responses: "200": description: The item with its responses. content: application/json: schema: { $ref: "#/components/schemas/InboxItem" } "404": { $ref: "#/components/responses/Error" } /api/inbox/responses: post: operationId: respondInbox description: > Answers one or more items in one submission. Answers reach the agent as one message once it is idle and none of its blocking questions are unanswered. requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/InboxRespond" } responses: "200": description: The updated items. content: application/json: schema: type: array items: { $ref: "#/components/schemas/InboxItem" } "400": { $ref: "#/components/responses/Error" } "404": { $ref: "#/components/responses/Error" } "409": { $ref: "#/components/responses/Error" } /api/events: get: operationId: streamEvents description: > Server-sent events. Each event's id is its seq; data is the Event as JSON. Events after Last-Event-ID (or the after query parameter) are replayed before live events. With task_id, only that task's events are sent. parameters: - { name: Last-Event-ID, in: header, required: false, schema: { type: integer, format: int64 } } - { name: after, in: query, required: false, schema: { type: integer, format: int64 } } - { name: task_id, in: query, required: false, schema: { type: string } } responses: "200": description: Event stream. content: text/event-stream: schema: { type: string } "404": { $ref: "#/components/responses/Error" } /api/admin/status: get: operationId: getStatus responses: "200": description: Server health and usage. content: application/json: schema: { $ref: "#/components/schemas/Status" } components: responses: Error: description: Error. content: application/json: schema: { $ref: "#/components/schemas/Error" } schemas: Error: type: object required: [message] properties: message: { type: string } User: type: object required: [login, display_name, git_name, git_email, notification_prefs] properties: login: { type: string } display_name: { type: string } git_name: { type: string } git_email: { type: string } notification_prefs: { $ref: "#/components/schemas/NotificationPrefs" } NotificationPrefs: type: object description: Which new inbox items send this user a push notification. required: [blocking_questions, questions, reports, errors, rate_limits] properties: blocking_questions: { type: boolean, description: "Questions asked with blocking: true. On by default." } questions: { type: boolean, description: Non-blocking questions. } reports: { type: boolean, description: Turn summaries. } errors: { type: boolean, description: session_error items. } rate_limits: { type: boolean, description: rate_limited items. } PushSubscription: type: object required: [endpoint, p256dh, auth] properties: endpoint: { type: string } p256dh: { type: string, description: The subscription's p256dh key, base64url. } auth: { type: string, description: The subscription's auth secret, base64url. } PushKey: type: object required: [public_key] properties: public_key: { type: string, description: Base64url, uncompressed P-256 point. } UserUpdate: type: object required: [git_name, git_email] properties: git_name: { type: string } git_email: { type: string } Project: type: object required: [id, name, repo_path, default_branch, remote, created_by, created_at] properties: id: { type: string } name: { type: string } repo_path: { type: string } default_branch: { type: string } remote: { type: string } created_by: { type: string } created_at: { type: string, format: date-time } ProjectCreate: type: object required: [name, repo_path] properties: name: { type: string } repo_path: { type: string } Task: type: object required: [id, project_id, title, merge_title, branch, worktree_path, base_ref, status, created_by, created_at] properties: id: { type: string } project_id: { type: string } title: { type: string } merge_title: { type: string, description: "The title of the squash commit Merge makes: the task title until the agent sets another with set_merge_title." } branch: { type: string } worktree_path: { type: string } base_ref: { type: string } status: type: string enum: [active, merged, discarded] x-enum-varnames: [TaskStatusActive, TaskStatusMerged, TaskStatusDiscarded] created_by: { type: string } created_at: { type: string, format: date-time } closed_at: { type: string, format: date-time, description: When the task was merged or discarded. } merge_commit: { type: string, description: "merged: the squash commit on the default branch; empty when there was nothing to merge." } Unmerged: type: object required: [commits, files, insertions, deletions, uncommitted] properties: commits: { type: integer, description: Commits on the task branch that aren't on the default branch. } files: { type: integer, description: Files that differ from the merge base, uncommitted changes included. } insertions: { type: integer } deletions: { type: integer } uncommitted: { type: integer, description: Paths with uncommitted changes in the worktree. } TaskCreate: type: object required: [title] properties: title: { type: string } TaskChanges: type: object required: [base, tree, files] properties: base: { type: string, description: Merge-base commit of the task branch and the default branch. } tree: { type: string, description: Tree of the worktree as if everything were staged. } files: type: array items: { $ref: "#/components/schemas/FileChange" } FileChange: type: object required: [path, status, additions, deletions, binary] properties: path: { type: string } old_path: { type: string, description: "renamed: the path before the rename." } status: { type: string, enum: [added, modified, deleted, renamed, type_changed] } additions: { type: integer } deletions: { type: integer } binary: { type: boolean } FileDiff: type: object required: [path, patch] properties: path: { type: string } patch: { type: string } Snapshot: type: object required: [id, task_id, agent_session_id, n, commit_sha, tree_sha, event_seq, trigger, created_at] properties: id: { type: string } task_id: { type: string } agent_session_id: { type: string } n: { type: integer, description: "Position in the task's snapshots, from 1; the ref is refs/byre/tasks/{task_id}/snap/{n}." } commit_sha: { type: string, description: A commit of tree_sha whose parent is the worktree's HEAD when it was taken. } tree_sha: { type: string } event_seq: { type: integer, format: int64, description: "The task's latest event when the snapshot was started: the tree holds at least the work up to it." } trigger: type: string enum: [tool_call, turn_end, ask_human, timer] x-enum-varnames: [SnapshotTriggerToolCall, SnapshotTriggerTurnEnd, SnapshotTriggerAskHuman, SnapshotTriggerTimer] created_at: { type: string, format: date-time } AgentSession: type: object required: [id, task_id, vendor, model, vendor_session_id, state, role, created_by, created_at] properties: id: { type: string } task_id: { type: string } vendor: { type: string, enum: [claude] } model: { type: string, description: Requested model; empty means the vendor's default. } vendor_session_id: { type: string } state: type: string enum: [idle, queued, running, completed] x-enum-varnames: [AgentSessionStateIdle, AgentSessionStateQueued, AgentSessionStateRunning, AgentSessionStateCompleted] description: queued means a turn is waiting for a free slot under the concurrency caps. role: { type: string, enum: [implementer] } created_by: { type: string } created_at: { type: string, format: date-time } SessionCreate: type: object required: [prompt] properties: prompt: { type: string } model: { type: string } images: type: array items: { type: string } description: Blob ids from uploadBlob, sent to the agent as images alongside the prompt. MessageCreate: type: object required: [text] properties: text: { type: string, description: May be empty when images are given. } images: type: array items: { type: string } description: Blob ids from uploadBlob, sent to the agent as images alongside the text. model: type: string description: Model for this turn and later ones. When it differs from the session's model, the session switches before the message is sent. Blob: type: object required: [id, content_type, created_by, created_at] properties: id: { type: string } content_type: { type: string } created_by: { type: string } created_at: { type: string, format: date-time } Attachment: type: object required: [kind] properties: kind: { type: string, enum: [file, diff, image, diagram, task, snapshot, url] } path: { type: string, description: "file: repo-relative path." } start_line: { type: integer } end_line: { type: integer } ref: { type: string, description: "diff: git revision range." } url: { type: string } mermaid: { type: string, description: "diagram: Mermaid source." } image: { type: string, description: "image: data URL." } task_id: { type: string } snapshot_id: { type: string } caption: { type: string } InboxState: type: string enum: [open, answered, dismissed, superseded] InboxItem: type: object required: [id, project_id, task_id, agent_session_id, thread_id, kind, priority, blocking, payload, state, created_at, responses] properties: id: { type: string } project_id: { type: string } task_id: { type: string } agent_session_id: { type: string } thread_id: { type: string } kind: { type: string, enum: [question, report, session_error, rate_limited] } priority: { type: string, enum: [blocked, gate, review, fyi] } blocking: { type: boolean } payload: type: object description: > question: context (Markdown with the choices inline as `- ( ) text` / `- (*) text` pick-one or `- [ ] text` / `- [x] text` pick-any list items), question, why_blocked, options (each choice's text, in order), multiple (true for pick-any), recommended (the marked choices' text), attachments. report: text (the final message of a turn that ended normally). session_error: error. rate_limited: vendor, rate_limit (the vendor's rate-limit status), error. state: { $ref: "#/components/schemas/InboxState" } created_at: { type: string, format: date-time } resolved_by: { type: string } resolved_at: { type: string, format: date-time } responses: type: array items: { $ref: "#/components/schemas/InboxResponse" } InboxResponseType: type: string enum: [option, other, dismiss] InboxResponse: type: object required: [id, item_id, user, type, attachments, created_at] properties: id: { type: string } item_id: { type: string } user: { type: string } type: { $ref: "#/components/schemas/InboxResponseType" } options: type: array items: { type: string } text: { type: string } attachments: type: array items: { $ref: "#/components/schemas/Attachment" } created_at: { type: string, format: date-time } delivered_at: { type: string, format: date-time } InboxResponseCreate: type: object required: [item_id, type] properties: item_id: { type: string } type: { $ref: "#/components/schemas/InboxResponseType" } options: type: array items: { type: string } description: "option: the chosen options; exactly one for a pick-one question, one or more for a pick-any question." text: { type: string, description: "other: the text sent to the agent." } attachments: type: array items: { $ref: "#/components/schemas/Attachment" } InboxRespond: type: object required: [responses] properties: responses: type: array minItems: 1 items: { $ref: "#/components/schemas/InboxResponseCreate" } Event: type: object required: [seq, kind, actor, payload, created_at] properties: seq: { type: integer, format: int64 } kind: { type: string } project_id: { type: string } task_id: { type: string } agent_session_id: { type: string } actor: { type: string } payload: { type: object } created_at: { type: string, format: date-time } Status: type: object required: [started_at, db_bytes, last_event_seq, projects, active_tasks, sse_clients, live_agents, running_turns, queued_turns, caps, rate_limits, agent_processes, zombies, stuck_after_seconds, protocol_errors] properties: live_agents: { type: integer, description: Agent processes currently running. } agent_processes: type: array description: Every agent process byre is tracking, idle sessions included. items: { $ref: "#/components/schemas/AgentProcess" } zombies: type: array description: Sessions marked running with no live process, and agent processes byre has no session for. items: { $ref: "#/components/schemas/Zombie" } stuck_after_seconds: { type: integer, description: How long a running turn may go without a stream event before it counts as stuck. } protocol_errors: { $ref: "#/components/schemas/ProtocolErrors" } running_turns: type: array description: Turns running now, oldest session first. items: { $ref: "#/components/schemas/TurnRef" } queued_turns: type: array description: Turns waiting for room under the caps, in the order they will start. items: { $ref: "#/components/schemas/TurnRef" } caps: { $ref: "#/components/schemas/Caps" } rate_limits: type: object description: The latest rate-limit status each vendor reported, keyed by vendor. additionalProperties: { type: object } started_at: { type: string, format: date-time } db_bytes: { type: integer, format: int64 } last_event_seq: { type: integer, format: int64 } projects: { type: integer } active_tasks: { type: integer } sse_clients: { type: integer } Caps: type: object required: [global, per_vendor] properties: global: { type: integer, description: Most turns running at once across all vendors. } per_vendor: type: object description: Most turns running at once per vendor, keyed by vendor. additionalProperties: { type: integer } TurnRef: type: object required: [session_id, task_id, vendor] properties: session_id: { type: string } task_id: { type: string } vendor: { type: string } AgentProcess: type: object required: [session_id, task_id, vendor, pid, state, last_event_at, health] properties: session_id: { type: string } task_id: { type: string } vendor: { type: string } pid: { type: integer } state: { type: string, description: The session's state, as on AgentSession. } last_event_at: { type: string, format: date-time, description: When the process last wrote a line to its stream, or when it started. } health: type: string enum: [ok, stuck, exited] x-enum-varnames: [HealthOk, HealthStuck, HealthExited] description: stuck means a turn is running and no stream event has arrived for stuck_after_seconds; exited means the process is gone. Zombie: type: object required: [kind, detail] properties: kind: type: string enum: [running_without_process, process_without_session] detail: { type: string } session_id: { type: string } task_id: { type: string } pid: { type: integer } ProtocolErrors: type: object required: [counts, recent] properties: counts: type: object description: Stream lines the adapter couldn't parse or didn't recognise since byre started, keyed by vendor. additionalProperties: { type: integer } recent: type: array description: The most recent ones, newest first. items: { $ref: "#/components/schemas/ProtocolError" } ProtocolError: type: object required: [vendor, session_id, at, reason, raw, size] properties: vendor: { type: string } session_id: { type: string } at: { type: string, format: date-time } reason: { type: string } raw: { type: string, description: The line as received, cut to its first 4 KiB. } size: { type: integer, description: The line's full length in bytes. }