HTTP API
The native HTTP API served by plico serve.
plico serve exposes a native API over the runtime and event store. This page covers
the access rules; Threads and Artifacts describe
their endpoints.
Access and safety
The server has no authentication, so it listens on loopback only and refuses requests a web page could send:
| Request | Response |
|---|---|
Foreign Host header (DNS rebinding) | 421 |
Cross-origin Origin header | 403 |
Body that is not application/json | 415 |
Clients that send no Origin, such as curl and the CLI, are unaffected.
plico serve --allow-remote exposes the server on another host and skips the Host
check; the Origin check still applies. A host app that serves the API behind its own
authentication passes allowRemote: true to servePlico and owns authentication. Origin must
match in scheme, host and port, so behind a TLS proxy the host app also passes
publicOrigin: "https://its.host".
Approval signals sent to POST /v1/runs/<run-id>/signal have any context dropped, so a client
cannot say who decided. See Threads.
Cancel a run
Cancel an active persisted run with POST /v1/runs/<run-id>/cancel or
plico cancel <run-id> <project>. Pressing Ctrl+C during plico run also cancels
that run.
Scripted runs
A script in a run request replays steps the caller picks and skips the model. The API
answers 403 run.script_disabled unless the server starts with
plico serve --allow-script (allowScript: true for servePlico).
Scripted runs over HTTP never run tool handlers. Each tool.call step needs a result, which
is recorded as the tool's output, the same way evals mock tools. A step without result
or with "live": true is refused with 400 request.invalid_body before the run starts. Approval policies still apply, so a step for a tool that needs
approval waits for a decision first, unless an approval.decide step follows it (see Evals). plico run --dry --script on the command line, and
plico.runs.start({ script }) in your backend, still run the real handlers.
With plico serve --inspect (inspect: true for servePlico) the server only reads: it recovers
nothing, GET /health returns { "ok": true, "inspection": true }, and every request other than
GET or HEAD is refused with 403 inspection.read_only. See
Use Plico from your backend.
Routes
Every route is under /v1. Errors are JSON with an error.code and error.message.
| Route | Purpose |
|---|---|
GET /health | Liveness. |
GET /v1/project | The loaded project: its root and config. |
GET /v1/instructions | The composed instructions. |
GET /v1/readiness | Readiness diagnostics. |
POST /v1/runs | Start a run. Returns 202 and the runId. |
POST /v1/runs/dry | Run the project without a model and return the result. |
GET /v1/runs | List runs. |
GET /v1/runs/<run-id> | One run. |
GET /v1/runs/<run-id>/events | The run's events. ?after=<sequence> returns only newer ones. See Events. |
GET /v1/runs/<run-id>/events/stream | The same events as Server-Sent Events. |
POST /v1/runs/<run-id>/cancel | Cancel a run. |
POST /v1/runs/<run-id>/signal | Send a signal, such as an approval decision. |
GET /v1/runs/<run-id>/workspace | The run's artifact files and versions. Sub-routes /files/<path>, /diffs, /exports and /snapshots/<id> are in Artifacts. |
POST /v1/threads, GET /v1/threads | Create and list threads. |
GET /v1/threads/<id>, PATCH /v1/threads/<id> | Read or rename a thread. |
GET /v1/threads/<id>/messages, POST /v1/threads/<id>/messages | Read history and send a message. See Threads. |
GET /v1/threads/<id>/workspace | The thread's shared artifact workspace. |
POST /v1/eval, GET /v1/eval, GET /v1/eval/<name> | Run evals and read the latest report or one case. |
/v1/project, /v1/instructions, /v1/readiness, /v1/eval and /v1/runs/dry are not served on a
mounted, scoped API.