API Reference
HITL exposes two sets of endpoints on separate servers:
- API server (port 3000) — agent-facing, JSON REST API
- User server (port 3001) — human-facing, HTML pages and form submission
POST /api/pages
Create a new interactive page.
Server: API server (port 3000)
Request Body
Content-Type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
content |
string | Yes | Markdown content with optional annotation fields (see Annotation Syntax) |
title |
string | No | Page title displayed in the browser tab and page header |
callback_url |
string | No | HTTP or HTTPS URL to POST responses to when the human submits. Enables webhook-style notifications instead of polling. |
Request Example
{
"title": "Code Review",
"content": "## PR #42: Add login page\n\nPlease review the changes.\n\n\n\n",
"callback_url": "https://hooks.example.com/api/webhooks/whk_abc123"
}
Response 201 Created
{
"id": "aBcDeFgHiJ",
"url": "http://localhost:3001/p/aBcDeFgHiJ",
"status": "waiting"
}
Response Schema
| Field | Type | Description |
|---|---|---|
id |
string | Unique 10-character page identifier (nanoid) |
url |
string | Full URL for the human to open (uses USER_BASE_URL env var) |
status |
string | Always "waiting" for a newly created page |
Errors
| Status | Body | Cause |
|---|---|---|
400 |
{"error": "content is required and must be a string"} |
Missing content field, or content is not a string |
400 |
{"error": "callback_url must be a valid HTTP or HTTPS URL"} |
callback_url is not a valid HTTP/HTTPS URL |
Callback Behavior
When callback_url is provided and the human submits the form, HITL will POST the response to the callback URL:
POST {callback_url}
Content-Type: application/json
{
"id": "aBcDeFgHiJ",
"status": "responded",
"responses": { "review": "approved", "comments": "LGTM" },
"responded_at": "2025-01-15 10:32:45"
}
- Fire-and-forget: The callback never blocks the human’s submission. If the callback fails, the submission still succeeds.
- Single attempt: No retries. 5-second timeout.
- Double-submit safe: The callback only fires on the first submission.
GET /api/pages/:id
Retrieve page status and responses. This is the endpoint agents poll to check whether a human has responded.
Server: API server (port 3000)
Path Parameters
| Param | Type | Description |
|---|---|---|
id |
string | The page ID returned by POST /api/pages |
Response 200 OK (waiting)
{
"id": "aBcDeFgHiJ",
"status": "waiting",
"responses": null,
"created_at": "2025-01-15 10:30:00",
"responded_at": null
}
Response 200 OK (responded)
{
"id": "aBcDeFgHiJ",
"status": "responded",
"responses": {
"review": "approved",
"comments": "LGTM, ship it"
},
"created_at": "2025-01-15 10:30:00",
"responded_at": "2025-01-15 10:32:45"
}
Response Schema
| Field | Type | Description |
|---|---|---|
id |
string | Page identifier |
status |
string | "waiting" (no response yet) or "responded" (human submitted the form) |
responses |
object | null | null if waiting. When responded: object with field names as keys and submitted values as values. Checkbox fields return arrays of selected values. |
created_at |
string | ISO-ish datetime when the page was created |
responded_at |
string | null | Datetime when the human submitted, or null |
Errors
| Status | Body | Cause |
|---|---|---|
404 |
{"error": "page not found"} |
No page exists with this ID |
GET /api/pages/:id/full
Retrieve full page data including title and content. Use this when an agent needs to re-read the original page content (e.g., after a context window reset).
Server: API server (port 3000)
Path Parameters
| Param | Type | Description |
|---|---|---|
id |
string | The page ID returned by POST /api/pages |
Response 200 OK
{
"id": "aBcDeFgHiJ",
"title": "Code Review",
"content": "## PR #42: Add login page\n\n",
"status": "responded",
"responses": {
"review": "approved"
},
"created_at": "2025-01-15 10:30:00",
"responded_at": "2025-01-15 10:32:45"
}
Response Schema
| Field | Type | Description |
|---|---|---|
id |
string | Page identifier |
title |
string | null | Page title |
content |
string | Original Markdown content with annotations |
status |
string | "waiting" or "responded" |
responses |
object | null | Submitted responses or null |
created_at |
string | Creation datetime |
responded_at |
string | null | Submission datetime or null |
Errors
| Status | Body | Cause |
|---|---|---|
404 |
{"error": "page not found"} |
No page exists with this ID |
GET /p/:id (User Server)
Render the interactive page for the human.
Server: User server (port 3001)
This endpoint:
- Returns HTML (not JSON)
- Fetches the stored page from SQLite, parses Markdown and annotation fields, and renders a complete HTML page with an interactive form
- Uses Pico CSS for styling and EJS for templating
- Shows an “already submitted” message if the page status is
responded - Returns
404plain text ("Page not found") if the page ID does not exist
Path Parameters
| Param | Type | Description |
|---|---|---|
id |
string | The page ID from the url field in the create-page response |
Errors
| Status | Body | Cause |
|---|---|---|
404 |
Page not found (plain text) |
No page exists with this ID |
POST /p/:id/submit (User Server)
Submit form responses. This is a standard HTML form submission triggered by the browser, not an API call.
Server: User server (port 3001)
This endpoint:
- Accepts
application/x-www-form-urlencoded(standard form POST) - Collects all annotated fields from the submitted form data
- Stores the responses in SQLite and sets the page status to
responded - Returns a “Thank You” confirmation page on success
- Returns an “Already Submitted” page if the page was previously responded to
- Is idempotent in the sense that double-submits are rejected gracefully — the human sees a message that the page was already submitted, and the original responses are preserved
Path Parameters
| Param | Type | Description |
|---|---|---|
id |
string | The page ID |
Errors
| Status | Body | Cause |
|---|---|---|
404 |
Page not found (plain text) |
No page exists with this ID |