{
  "openapi": "3.1.0",
  "info": {
    "title": "Forge API",
    "version": "v1",
    "description": "Start conversations with Forge agents, send them messages and read what they say back."
  },
  "tags": [
    {
      "name": "Conversations"
    },
    {
      "name": "Messages"
    }
  ],
  "paths": {
    "/conversations": {
      "post": {
        "operationId": "createConversation",
        "tags": [
          "Conversations"
        ],
        "summary": "Start a conversation",
        "description": "Creates a conversation and sends its first message. The response shape follows `reply` and `stream`.\n\nScopes: `conversations:write`.",
        "x-forge-scopes": [
          "conversations:write"
        ],
        "x-forge-metered": true,
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateConversationRequest"
              },
              "example": {
                "text": "Write a haiku about a forge",
                "reply": "required"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The agent's replies (`reply` optional or required).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendResult"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string"
                },
                "description": "With `stream: true`: an `ack` event with the ids, a `message` event per reply as it is said, then `done`. An `error` event precedes `done` when `reply` was required and none came."
              }
            }
          },
          "202": {
            "description": "Accepted (`reply: \"none\"`): the ids, with nothing awaited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendResult"
                }
              }
            }
          },
          "400": {
            "description": "The request is malformed: the message says what to fix.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "No credential, or one Forge does not accept.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "402": {
            "description": "Your balance is empty: add credits to continue.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The credential lacks a scope this route needs, or the account cannot use it yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "No such conversation, or no such route.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "The message was not accepted (a duplicate id with a different body, or a conflicting decision).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "504": {
            "description": "reply was `required` and the turn ended (or the 10-minute wait ran out) without a reply.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listConversations",
        "tags": [
          "Conversations"
        ],
        "summary": "List conversations",
        "description": "Your active conversations, newest first. A ticket is one row.\n\nScopes: `conversations:read`.",
        "x-forge-scopes": [
          "conversations:read"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            },
            "description": "1 to 100, default 20."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversationList"
                }
              }
            }
          },
          "400": {
            "description": "The request is malformed: the message says what to fix.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "No credential, or one Forge does not accept.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The credential lacks a scope this route needs, or the account cannot use it yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "No such conversation, or no such route.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/conversations/{conversationId}": {
      "get": {
        "operationId": "getConversation",
        "tags": [
          "Conversations"
        ],
        "summary": "Get a conversation",
        "description": "Whether the agent is working, what waits on you, and the end of its newest message.\n\nScopes: `conversations:read`.",
        "x-forge-scopes": [
          "conversations:read"
        ],
        "parameters": [
          {
            "name": "conversationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversationStatus"
                }
              }
            }
          },
          "400": {
            "description": "The request is malformed: the message says what to fix.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "No credential, or one Forge does not accept.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The credential lacks a scope this route needs, or the account cannot use it yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "No such conversation, or no such route.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/messages": {
      "post": {
        "operationId": "sendMessage",
        "tags": [
          "Messages"
        ],
        "summary": "Send a message",
        "description": "Sends a message to a conversation. The response shape follows `reply` and `stream`.\n\nScopes: `conversations:write`.",
        "x-forge-scopes": [
          "conversations:write"
        ],
        "x-forge-metered": true,
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendMessageRequest"
              },
              "example": {
                "conversationId": "CONVERSATION_ID",
                "text": "Now make it rhyme",
                "reply": "required"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The agent's replies (`reply` optional or required).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendResult"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string"
                },
                "description": "With `stream: true`: an `ack` event with the ids, a `message` event per reply as it is said, then `done`. An `error` event precedes `done` when `reply` was required and none came."
              }
            }
          },
          "202": {
            "description": "Accepted (`reply: \"none\"`): the ids, with nothing awaited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendResult"
                }
              }
            }
          },
          "400": {
            "description": "The request is malformed: the message says what to fix.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "No credential, or one Forge does not accept.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "402": {
            "description": "Your balance is empty: add credits to continue.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The credential lacks a scope this route needs, or the account cannot use it yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "No such conversation, or no such route.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "The message was not accepted (a duplicate id with a different body, or a conflicting decision).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "504": {
            "description": "reply was `required` and the turn ended (or the 10-minute wait ran out) without a reply.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/conversations/{conversationId}/messages": {
      "get": {
        "operationId": "listMessages",
        "tags": [
          "Messages"
        ],
        "summary": "List messages",
        "description": "What was said in a conversation, newest page first.\n\nScopes: `conversations:read`.",
        "x-forge-scopes": [
          "conversations:read"
        ],
        "parameters": [
          {
            "name": "conversationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "before",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "From the previous page, to read the messages before it. Opaque: pass it back as given."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            },
            "description": "1 to 1,000, default 50."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessagePage"
                }
              }
            }
          },
          "400": {
            "description": "The request is malformed: the message says what to fix.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "No credential, or one Forge does not accept.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The credential lacks a scope this route needs, or the account cannot use it yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "No such conversation, or no such route.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ApiError": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string"
          },
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false
      },
      "Conversation": {
        "type": "object",
        "properties": {
          "busy": {
            "type": "boolean",
            "description": "The agent is working right now."
          },
          "conversationId": {
            "type": "string",
            "description": "The conversation to send to. For a ticket, its live member."
          },
          "cwd": {
            "type": [
              "string",
              "null"
            ]
          },
          "members": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConversationMember"
            }
          },
          "model": {
            "type": [
              "string",
              "null"
            ]
          },
          "stage": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "done",
              "execute",
              "finalise",
              "plan",
              "review",
              null
            ]
          },
          "ticketId": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "updatedAtMs": {
            "type": "number",
            "description": "Epoch milliseconds."
          }
        },
        "required": [
          "busy",
          "conversationId",
          "cwd",
          "model",
          "stage",
          "title",
          "updatedAtMs"
        ],
        "additionalProperties": false,
        "description": "One row per piece of work: a ticket with several member conversations is one row, addressed by\nthe member it is live on."
      },
      "ConversationList": {
        "type": "object",
        "properties": {
          "conversations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Conversation"
            }
          }
        },
        "required": [
          "conversations"
        ],
        "additionalProperties": false
      },
      "ConversationMember": {
        "type": "object",
        "properties": {
          "conversationId": {
            "type": "string"
          },
          "role": {
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "type": "string"
          }
        },
        "required": [
          "conversationId",
          "role",
          "title"
        ],
        "additionalProperties": false
      },
      "ConversationPath": {
        "type": "object",
        "properties": {
          "conversationId": {
            "type": "string"
          }
        },
        "required": [
          "conversationId"
        ],
        "additionalProperties": false
      },
      "ConversationState": {
        "type": "object",
        "properties": {
          "bucket": {
            "$ref": "#/components/schemas/ConvoBucket"
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "automatic-wake",
              "cron",
              "error",
              "executing",
              "gave-up",
              "held",
              "idle",
              "paused-all",
              "paused-one",
              "paused-subtree",
              "question",
              "queue-rule",
              "queued-for-slot",
              "recovery-wait",
              "request",
              "retrying",
              "stopped-mid-turn",
              "ticket-unknown",
              "waiting-on-convo",
              null
            ]
          }
        },
        "required": [
          "bucket",
          "reason"
        ],
        "additionalProperties": false
      },
      "ConversationStatus": {
        "type": "object",
        "properties": {
          "acceptedInputs": {
            "type": "number",
            "description": "Messages accepted that the next turn has not read yet."
          },
          "busy": {
            "type": "boolean"
          },
          "conversationId": {
            "type": "string"
          },
          "deferred": {
            "type": "boolean",
            "description": "The next turn is waiting for its turn to run."
          },
          "deferredBy": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "account_cap",
              "database",
              "demand",
              "memory",
              "net",
              "plan_cap",
              "priority",
              "provider",
              "slot",
              "user_cap",
              "waitlist",
              null
            ]
          },
          "deferredSince": {
            "type": [
              "number",
              "null"
            ]
          },
          "eta": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/ConversationStatus_eta_estimate_complete"
              },
              {
                "$ref": "#/components/schemas/ConversationStatus_eta_unknown"
              }
            ]
          },
          "etaSeconds": {
            "type": [
              "number",
              "null"
            ],
            "description": "The measured time left, in seconds, when there is evidence for one."
          },
          "lastAssistantText": {
            "type": [
              "string",
              "null"
            ],
            "description": "The end of the agent's newest message."
          },
          "nextAttemptAtMs": {
            "type": [
              "number",
              "null"
            ]
          },
          "pendingGates": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PendingGate"
            }
          },
          "phase": {
            "$ref": "#/components/schemas/RunPhase"
          },
          "runState": {
            "type": "string"
          },
          "stage": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "done",
              "execute",
              "finalise",
              "plan",
              "review",
              null
            ]
          },
          "state": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ConversationState"
              },
              {
                "type": "null"
              }
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "description": "The agent's own one-line status, when it set one."
          },
          "title": {
            "type": "string"
          },
          "updatedAtMs": {
            "type": "number",
            "description": "Epoch milliseconds."
          },
          "waitingFor": {
            "type": [
              "string",
              "null"
            ],
            "description": "What the deferred turn waits on, in plain words."
          }
        },
        "required": [
          "acceptedInputs",
          "busy",
          "conversationId",
          "deferred",
          "deferredBy",
          "deferredSince",
          "eta",
          "etaSeconds",
          "lastAssistantText",
          "nextAttemptAtMs",
          "pendingGates",
          "phase",
          "runState",
          "stage",
          "state",
          "status",
          "title",
          "updatedAtMs",
          "waitingFor"
        ],
        "additionalProperties": false,
        "description": "Where a conversation is now: whether the agent is working, what waits on you, and its latest words."
      },
      "ConversationStatus_eta_estimate_complete": {
        "type": "object",
        "properties": {
          "asOfMs": {
            "type": "number",
            "description": "the clock this reading is AS OF: the reader's own for a live read, the held one for a stale copy. A\nreader rendering it later says \"as of X ago\" once it is past STALE_AFTER_MS."
          },
          "basis": {
            "$ref": "#/components/schemas/ProjectionBasis"
          },
          "confidence": {
            "$ref": "#/components/schemas/ProjectionConfidence"
          },
          "highMs": {
            "type": "number"
          },
          "kind": {
            "type": "string",
            "enum": [
              "complete",
              "estimate"
            ]
          },
          "lowMs": {
            "type": "number"
          },
          "progressAgeMs": {
            "type": "number",
            "description": "active ms since the last evidence (`WorkProjection.progressAgeMs`)."
          },
          "remainingMs": {
            "type": "number"
          },
          "runningLong": {
            "type": "boolean",
            "const": true,
            "description": "an in-flight tick is past its reference, or the work outran its estimate: say it is running long, never present the range as on track."
          },
          "snapshotAgeMs": {
            "type": "number",
            "description": "Derived age of that confirmed copy at the reader's clock; never persisted in storage."
          }
        },
        "required": [
          "basis",
          "confidence",
          "highMs",
          "kind",
          "lowMs",
          "remainingMs"
        ],
        "additionalProperties": false
      },
      "ConversationStatus_eta_unknown": {
        "type": "object",
        "properties": {
          "asOfMs": {
            "type": "number",
            "description": "the clock this reading is AS OF: the reader's own for a live read, the held one for a stale copy. A\nreader rendering it later says \"as of X ago\" once it is past STALE_AFTER_MS."
          },
          "basis": {
            "type": "string",
            "const": "none"
          },
          "confidence": {
            "type": "string",
            "const": "none"
          },
          "kind": {
            "type": "string",
            "const": "unknown"
          },
          "progressAgeMs": {
            "type": "number",
            "description": "active ms since the last evidence (`WorkProjection.progressAgeMs`)."
          },
          "reason": {
            "type": "string",
            "enum": [
              "no-plan",
              "no-progress"
            ]
          },
          "runningLong": {
            "type": "boolean",
            "const": true,
            "description": "an in-flight tick is past its reference, or the work outran its estimate: say it is running long, never present the range as on track."
          },
          "snapshotAgeMs": {
            "type": "number",
            "description": "Derived age of that confirmed copy at the reader's clock; never persisted in storage."
          }
        },
        "required": [
          "basis",
          "confidence",
          "kind"
        ],
        "additionalProperties": false
      },
      "ConvoBucket": {
        "type": [
          "string",
          "null"
        ],
        "enum": [
          "inactive",
          "needs-you",
          "paused",
          "running",
          "scheduled",
          null
        ],
        "description": "The bucket a row displays in — a SUPERSET of the `state:` buckets.\n\n`needs-you` is an `is:` property rather than a state value: it spans several unrelated runtime\nsituations (a question, a spent error, a row the machine gave up on), so making it a `state:`\nvalue would break the rule that every row sits in exactly one State bucket — which group-by\ndepends on. `null` is the resting state: a dormant conversation, and `done`, which is a resting\nstate rather than a runtime one. Nullable so group-by keeps its \"no state\" section instead of\ninventing a Running bucket for every dormant conversation."
      },
      "CreateConversationRequest": {
        "type": "object",
        "properties": {
          "cwd": {
            "type": "string",
            "description": "The working folder the agent starts in. Omit for your default."
          },
          "reply": {
            "type": "string",
            "enum": [
              "none",
              "optional",
              "required"
            ],
            "description": "Default `optional`."
          },
          "stream": {
            "type": "boolean",
            "description": "Stream the agent's messages as Server-Sent Events instead of one JSON body. Default false."
          },
          "text": {
            "type": "string",
            "description": "The first message. Up to 100,000 characters."
          },
          "title": {
            "type": "string",
            "description": "A title for the conversation. Omit and Forge names it from the first message."
          }
        },
        "required": [
          "text"
        ],
        "additionalProperties": false
      },
      "ListConversationsQuery": {
        "type": "object",
        "properties": {
          "limit": {
            "type": "number",
            "description": "1 to 100, default 20."
          }
        },
        "additionalProperties": false
      },
      "ListMessagesQuery": {
        "type": "object",
        "properties": {
          "before": {
            "type": "string",
            "description": "From the previous page, to read the messages before it. Opaque: pass it back as given."
          },
          "limit": {
            "type": "number",
            "description": "1 to 1,000, default 50."
          }
        },
        "additionalProperties": false
      },
      "Message": {
        "type": "object",
        "properties": {
          "messageId": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "enum": [
              "assistant",
              "user"
            ]
          },
          "text": {
            "type": "string"
          },
          "timestamp": {
            "type": [
              "string",
              "null"
            ],
            "description": "ISO 8601, when known."
          }
        },
        "required": [
          "messageId",
          "role",
          "text",
          "timestamp"
        ],
        "additionalProperties": false,
        "description": "A message in a conversation: what you (or anyone in your workspace) said, and what the agent said\nback. Tool activity, thinking and notes are not part of the API."
      },
      "MessagePage": {
        "type": "object",
        "properties": {
          "before": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pass as `before` to read older messages; null when this page reaches the start."
          },
          "conversationId": {
            "type": "string"
          },
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            }
          }
        },
        "required": [
          "before",
          "conversationId",
          "messages"
        ],
        "additionalProperties": false,
        "description": "A page of messages, oldest first within the page. The first request returns the newest page; pass\n`before` back to read older ones. `limit` bounds the transcript entries read and anything that is\nnot a message (tool activity, notes) is left out, so a page can hold fewer than `limit`."
      },
      "PendingGate": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string"
          },
          "prompt": {
            "type": "string"
          },
          "requestId": {
            "type": [
              "string",
              "null"
            ]
          },
          "tool": {
            "type": "string"
          }
        },
        "required": [
          "kind",
          "prompt",
          "requestId"
        ],
        "additionalProperties": false,
        "description": "Something waiting on you: a question, an approval or a task."
      },
      "ProjectionBasis": {
        "type": "string",
        "enum": [
          "history",
          "none",
          "ticks",
          "ticks+history"
        ]
      },
      "ProjectionConfidence": {
        "type": "string",
        "enum": [
          "high",
          "low",
          "medium",
          "none"
        ]
      },
      "Reply": {
        "type": "object",
        "properties": {
          "messageId": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "timestamp": {
            "type": "string",
            "description": "ISO 8601."
          }
        },
        "required": [
          "messageId",
          "text",
          "timestamp"
        ],
        "additionalProperties": false,
        "description": "One message the agent sent to you."
      },
      "RunPhase": {
        "type": "string",
        "enum": [
          "awaiting",
          "draining",
          "error",
          "idle",
          "queued",
          "stopping",
          "streaming",
          "thinking",
          "tool",
          "working"
        ]
      },
      "SendMessageRequest": {
        "type": "object",
        "properties": {
          "conversationId": {
            "type": "string"
          },
          "reply": {
            "type": "string",
            "enum": [
              "none",
              "optional",
              "required"
            ],
            "description": "Default `optional`."
          },
          "stream": {
            "type": "boolean",
            "description": "Stream the agent's messages as Server-Sent Events instead of one JSON body. Default false."
          },
          "text": {
            "type": "string",
            "description": "The message. Up to 100,000 characters."
          }
        },
        "required": [
          "conversationId",
          "text"
        ],
        "additionalProperties": false
      },
      "SendResult": {
        "type": "object",
        "properties": {
          "conversationId": {
            "type": "string"
          },
          "messageId": {
            "type": "string",
            "description": "The id of the message you sent."
          },
          "replies": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Reply"
            },
            "description": "The agent's messages, in order. Absent on a 202 (`reply: \"none\"`)."
          },
          "timedOut": {
            "type": "boolean",
            "description": "True when the 10-minute wait ran out before the turn ended."
          }
        },
        "required": [
          "conversationId",
          "messageId"
        ],
        "additionalProperties": false
      }
    },
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "An API key (`fg_...`) from Settings > Account > API access, or an OAuth access token for this API."
      }
    }
  },
  "security": [
    {
      "bearer": []
    }
  ],
  "servers": [
    {
      "url": "https://api.forge.team/v1"
    }
  ]
}