REST API and SSE stream¶
The agent has its own REST surface on the web editor backend. This page is the canonical contract: the endpoints, the request body, and the Server-Sent Events the run emits.
All paths below are relative to the /besser_api router prefix.
Endpoints¶
Endpoint |
Method |
Purpose |
|---|---|---|
|
POST |
Start a run; streams SSE, returns a download URL on completion. |
|
POST |
Pre-flight plan (primary model + target generator). No API key, no LLM call. |
|
GET |
Caps, feature flags, provider default models, and free-tier availability. |
|
GET |
Durable lifecycle metadata for a run (never the request or the key). |
|
GET |
Replay this run’s events after |
|
GET |
Polling alternative to the stream: the run’s lifecycle metadata plus
up to |
|
POST |
Resume an interrupted run from its checkpoint (streams SSE). |
|
POST |
Cancel an in-flight run at its next turn boundary. |
|
GET |
Download the generated ZIP/file (re-fetchable within a TTL). |
|
POST |
Push a finished run’s code — plus the re-importable model under
|
|
POST |
Import a BESSER-created repo back as a run, so it can be used as the
seed of a subsequent modify run. Requires an |
Request body¶
Request body of POST /spec-driven/generate:
Field |
Type |
Notes |
|---|---|---|
|
ProjectInput |
The full project payload (same shape as |
|
string |
Natural-language description of what to build (1–64,000 characters). This limit includes any modeling-agent summary and appended original request. Longer input is rejected before generation, not truncated. Accepted instructions reach planning and requirement extraction in full; inventories and model summaries have separate context budgets. |
|
string (secret) |
BYOK. Required for |
|
|
Which provider serves the run. Default |
|
string (optional) |
Model override; falls back to the provider default. For |
|
float |
Soft spend cap, clamped to the server hard cap (default 5.0, max 5.0). |
|
int |
Soft runtime cap, clamped to the server hard cap (default 2400, max 2400). |
|
int |
Soft cap on LLM turns, clamped to the server hard cap (default 80, max 120). |
|
|
|
|
string (optional) |
The 32-hex run id of the run to modify. Optional even for
|
|
string (optional) |
Force the primary model kind ( |
|
string (optional) |
Bind the Phase-1 deterministic generator (a registered generator-tool
name from an approved preview plan), skipping the agent’s own
selection. Mutually exclusive with |
|
bool |
Explicitly skip Phase 1 and let the LLM scaffold from scratch.
Default |
Note
The caps above are the shipped defaults. Every one of them is overridable
per deployment via BESSER_LLM_* environment variables — read the live
values from GET /besser_api/spec-driven/config rather than hardcoding
them in a client. The full variable reference is in Configuration reference.
SSE event types¶
SSE event types emitted by /spec-driven/generate:
Event |
Meaning |
|---|---|
|
Run accepted; carries |
|
Pipeline advanced to a phase: |
|
Extra detail for the current phase (e.g. the gap task list). |
|
A streaming text delta from the LLM. |
|
The LLM invoked a tool (read/write/modify a file, query
the model, update its checklist, run a generator). Carries the turn
number, the tool name, a status, and a short non-content |
|
The model serving this run changed mid-run — the fallback chain took
over after the primary failed or hit its quota. Only the
server-configured tiers ( |
|
Periodic cost / runtime / turn-count tick. |
|
Success. Carries |
|
|
Example¶
Example — start a run with curl and read the stream:
curl -N -X POST https://<host>/besser_api/spec-driven/generate \
-H 'Content-Type: application/json' \
-H 'Accept: text/event-stream' \
-d '{
"project": { "...": "full ProjectInput payload" },
"instructions": "FastAPI backend with JWT auth and a Dockerfile",
"api_key": "sk-...",
"provider": "openai",
"llm_model": "gpt-4o",
"max_cost_usd": 1.0,
"max_runtime_seconds": 600
}'
The response is a Server-Sent Events stream:
event: start
data: {"event":"start","runId":"4f3c9a1d8e2b47c6905af1e3d7b28c40","provider":"openai","llmModel":"gpt-4o","maxCost":1.0,"maxRuntime":600}
event: phase
data: {"event":"phase","phase":"generate","message":"running generate_fastapi_backend"}
event: tool_call
data: {"event":"tool_call","turn":1,"tool":"write_file","status":"executing","detail":"auth.py"}
event: cost
data: {"event":"cost","usd":0.07,"turns":3,"elapsedSeconds":24.1}
event: phase
data: {"event":"phase","phase":"validate","message":"1 blockers / 4 total"}
event: done
data: {"event":"done","runId":"4f3c9a1d8e2b47c6905af1e3d7b28c40","downloadUrl":"/besser_api/spec-driven/download/4f3c9a1d8e2b47c6905af1e3d7b28c40","fileName":"besser_smart_4f3c9a1d8e2b47c6905af1e3d7b28c40.zip","isZip":true,"blockerCount":0}
Then issue a GET to the downloadUrl to retrieve the ZIP — it is
re-fetchable within the run’s TTL.
Note
The frames above are shown without their sequence number for readability.
As actually delivered, every frame additionally carries an SSE id: line
and a sequence field in the JSON body — id: 3 /
data: {"event":"cost", …,"sequence":3}. That number is the cursor for
?after= and for Last-Event-ID, so a client that wants to reconnect
must record it. See Durable runs for the reconnect flow.