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/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: > Squashes the task's worktree, uncommitted changes included, into one commit on the project's default branch, fast-forwards the project's checkout to it, then removes the worktree and branch. Nothing is pushed. Refused while a turn is running or queued, and on merge conflicts. responses: "200": description: The merged task. content: application/json: schema: { $ref: "#/components/schemas/Task" } "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}/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" } } - { name: include_snoozed, in: query, required: false, schema: { type: boolean } } responses: "200": description: Matching items with their responses. Defaults to open, unsnoozed 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. 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 } } responses: "200": description: Event stream. content: text/event-stream: schema: { type: string } /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] properties: login: { type: string } display_name: { type: string } git_name: { type: string } git_email: { type: string } 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, branch, worktree_path, base_ref, status, created_by, created_at] properties: id: { type: string } project_id: { type: string } title: { type: string } 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 } 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, question, why_blocked, options, recommended_option, 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" } snoozed_until: { type: string, format: date-time } 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, snooze, 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" } option: { type: string } text: { type: string } attachments: type: array items: { $ref: "#/components/schemas/Attachment" } snooze_until: { type: string, format: date-time } 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" } option: { type: string, description: "option: one of the question's options." } text: { type: string, description: "other: the text sent to the agent." } attachments: type: array items: { $ref: "#/components/schemas/Attachment" } snooze_until: { type: string, format: date-time, description: "snooze: when the item reappears." } 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, rate_limits] properties: live_agents: { type: integer, description: Agent processes currently running. } running_turns: { type: integer } queued_turns: { type: integer } 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 }