# HTTP API (/docs/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](/docs/threads) and [Artifacts](/docs/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](/docs/threads#host-data-metadata-and-context).

## 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](/docs/evals#test-approvals)). `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](/docs/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](/docs/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](/docs/approvals) 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](/docs/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](/docs/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](/docs/backend).