Skip to content

API reference

All endpoints require a bearer token. Pass a JWT or API key issued by the identity service in the Authorization header:

Authorization: Bearer <token>

The token is validated by calling identity.v1.AuthServiceClient.VerifyToken on every request. Missing or invalid tokens return 401.

Create an agent.

Request body

FieldTypeRequiredConstraints
namestringyesnon-empty
slugstringyesnon-empty, unique/org
modelstringyesnon-empty
systemPromptstringnooptional system prompt
// from paper-board/agents/internal/api/api.go:87-92
var req struct {
Name string `json:"name"`
Slug string `json:"slug"`
Model string `json:"model"`
SystemPrompt string `json:"systemPrompt,omitempty"`
}

Response201 Created with Agent object.

Errors400 invalid JSON or missing required fields; 409 slug already exists within the org.


List agents for the authenticated org. Cursor-paginated.

Query parameters

ParameterTypeDefaultDescription
limitint201–100; 400 outside range
afterstringOpaque cursor from previous next

Response200 OK

{
"items": [ /* Agent objects */ ],
"next": "eyJzb3J0IjoiY3JlYXRlZF9hdCIsInZhbCI6IjIwMjYtMDU..."
}

next is null when no further pages exist.


Fetch one agent by UUID.

Path parametersid: UUID of the agent.

Response200 OK with Agent object.

Errors400 malformed UUID; 404 agent not found or belongs to a different org.


Create a session for an agent.

Request body

FieldTypeRequiredDescription
agentIdstringyesUUID of the agent

Response201 Created with Session object.

Errors400 malformed UUID; 404 agent not found within the org.


Fetch a session and its full event log.

Response200 OK

{
"session": { /* Session object */ },
"events": [ /* SessionEvent objects */ ]
}

Send messages and stream the LLM response as Server-Sent Events (SSE).

Request headers (required for SSE clients):

Accept: text/event-stream
Content-Type: application/json

Request body

FieldTypeRequiredDescription
messages[]MessageyesNon-empty array

Message shape:

FieldTypeNotes
rolestringuser | assistant | tool
contentstringText content
toolUseIdstringSet when role is tool
isErrorboolSet when tool result signals an error

Response200 OK, Content-Type: text/event-stream

// from paper-board/agents/internal/api/sse.go:60-63
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("X-Accel-Buffering", "no")

Each SSE line has the form data: <JSON>\n\n. The JSON object carries a type field that identifies the event.

// from paper-board/agents/internal/api/sse.go:78-83
type sseEvent struct {
Type string `json:"type"`
Content string `json:"content,omitempty"`
TokensIn int `json:"tokensIn,omitempty"`
TokensOut int `json:"tokensOut,omitempty"`
}
typePayload fieldsDescription
textcontentOne streamed text fragment from the LLM
tool_usecontent (tool name + input JSON)LLM requested a tool call
tool_resultcontent (stdout/exit code)Result returned from compute; fed back to LLM
donetokensIn, tokensOutStream complete; cumulative token counts
errorcontent (error message)Terminal error; stream closes after this event

The stream closes after done or error. Clients must handle both.

PathAuthDescription
GET /liveznoReturns 200 when the process is alive
GET /healthznoReturns 200 when the process is alive
GET /readyznoReturns 200 when DB + identity gRPC are reachable

The agents service does not expose a public gRPC server in Phases 1–6. It acts as a gRPC client toward identity and compute.

Outbound calls made by agents

ServiceRPCPurpose
identity.v1.AuthServiceClientVerifyTokenAuthenticate every HTTP request
compute.v1.ComputeServiceClientExecCommand, CreateSandbox, etc.Tool dispatch (Phase 3+)

Proto source: paper-board/proto/identity/v1/auth.proto and paper-board/proto/compute/v1/compute.proto.

The SDK maps Postgres and sentinel errors to HTTP status codes at the handler boundary.

// from paper-board/agents/internal/store/errors.go:24-36
func mapErr(err error, op string) error {
if err == nil {
return nil
}
if errors.Is(err, pgx.ErrNoRows) {
return pberrs.Wrap(pberrs.ErrNotFound, op)
}
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) && strings.HasPrefix(pgErr.Code, "23") {
return pberrs.Wrap(pberrs.ErrConflict, op, "constraint", pgErr.ConstraintName)
}
return pberrs.Wrap(pberrs.ErrInternal, op, "cause", err.Error())
}
SDK sentinelHTTP statusWhen
ErrInvalidInput400Malformed JSON, missing field, bad UUID
ErrUnauthorized401Missing or invalid bearer token
ErrNotFound404Row not found, or row belongs to diff org
ErrConflict409Unique constraint violation (e.g., slug)
ErrUnavailable503LLM provider unreachable
ErrInternal500Unexpected Postgres or internal error

All error responses use the SDK envelope shape:

{
"error": {
"code": "not_found",
"message": "get agent"
}
}