---
title: "HTTP Surfaces"
url: "https://control.actana.ai/docs/reference/http-surfaces"
description: "Three HTTP surfaces, none a public API: Panel routes, the loopback hook receiver, and the mTLS file routes."
updated: 2026-09-01T09:03:20+00:00
---

Actana Control has three HTTP surfaces, and **none is a public integration API**. There is no `POST /api/projects/:id/tasks` — that HTTP task API is retired and those routes do not exist. Writes travel as [core-link](/docs/architecture/core-link) mutation frames. Third parties type against [`@actana/sdk`](/docs/reference/sdk).

## 1. Panel — Operator browser

The Panel's HTTP plus its [panel link](/docs/architecture/panel-link) serve the Operator's tab. Auth is the session cookie. There is no bearer-token mode and no versioning promise. Harnesses never call it.

Task, project, and session reads and writes do **not** appear here: each Core owns that state. What remains is Panel-local: liveness (`GET /api/healthz`), Operator auth, the Core registry, project presentation and groups, preferences, usage, SSE (`GET /api/events`), and `GET /api/update-check`. Treat that list as a map, not a contract.

## 2. Core hook receiver — loopback

Each Core runs a small HTTP server so harnesses it spawned can report what they are doing. Not operator-facing. Nothing to configure: the Core writes hook entries into each harness's config and hands credentials in the PTY environment.

| Property | Value |
| --- | --- |
| Bind | `127.0.0.1` only — never the Core's public host |
| Port | Ephemeral (`listen(0)`), chosen at boot |
| Route | `POST /api/hooks/<slug>?taskId=…&hookEvent=…` |
| Auth | `Authorization: Bearer` — 32 random bytes minted **per boot**, in memory, never persisted |
| Env | `AC_HOOK_URL`, `AC_HOOK_TOKEN`, `AC_HOOK_TASK_ID` |

A restart mints a fresh token, so a hook from a previous boot fails auth — that session's process is gone.

## 3. Core file routes — mTLS HTTPS

`/v1/...` answers on the **same** HTTPS server the core-link WebSocket is mounted on: one port, one certificate, one bearer, two protocols. File bytes cross here, never over the core link. Auth is the pinned client certificate **and** `Authorization: Bearer <bearer>`. Announced as `files: { version: 1 }` on `ready`; absent means no file surface.

| Route | Does |
| --- | --- |
| `GET /v1/projects/:projectId/files?path=` | file bytes, or a directory as streamed tar |
| `PUT /v1/projects/:projectId/files?path=` | write a file, or unpack a tar |
| `GET /v1/projects/:projectId/files/list?path=` | tree as chunked NDJSON |

Type `project.files.*` on the SDK, not these URLs.

## Pairing redeem

`POST /v1/pair/redeem` is the **only pre-auth route** on a Core. A client compares the CA fingerprint **before** sending the code, then posts a CSR. The private key is born on the client and never crosses the wire. Enrollment is a short pairing code, not a pasted blob.

## See also

- [Filesystem](/docs/architecture/filesystem) — why bytes are on HTTPS.
- [Write path](/docs/architecture/write-path) — mutations are frames, not REST.
- [SDK](/docs/reference/sdk) — the surface to write against.
- [Pairing](/docs/pairing) — mint and redeem a code.
