API Reference
List and read Letterflows, submit runs, poll their status, and inspect regional inputs and outputs for every Letterflow step.
Tinkerer keys can read, pin, and delete Letterbox entries. Aspiring keys can also read saved definitions, submit runs, and poll status when you select those scopes. A new key defaults to Letterbox reading only; a run scope does not grant polling or result access.
Overview
Authentication
Generate API keys in Settings โ Integration API keys. Include your token in the Authorization header:
Choose resource and action permissions for each key. New keys allow Letterbox reading only; running, polling, pinning, and deletion require their own scopes.
Authorization: Bearer hato_live_<token>
Rate Limiting & Caching
API keys are limited to 30 requests per minute regardless of source IP. Metadata supports ETag conditional requests. Regional capability URLs expire within five minutes and follow content retention.
Error Format
Errors are returned as standard JSON objects with error codes and descriptive messages:
{"error": {"code": "unauthorized", "message": "A valid Bearer API key is required."}}
Saved definitions
List owned Letterflows with limit and cursor pagination. Detail endpoints provide metadata, readiness, and signed links to saved regional inputs. Private keys and account credentials are excluded.
| Endpoint | API permissions |
|---|---|
GET /api/v1/letterflows/GET /api/v1/letterflows/{id}/ | letterflows:read |
Submit and poll a run
POST an empty JSON object to run a saved Letterflow immediately. Idempotency-Key is required: use a new key for each intended run. Repeating the same key returns the same run; overrides and API scheduling are not supported.
| Endpoint | API permissions |
|---|---|
POST /api/v1/letterflows/{id}/runs/ | letterflows:run |
GET /api/v1/runs/{uuid}/ | runs:read |
A new run returns 202 and a Location polling URL; an idempotent replay returns 200. States are waiting, queued, running, completed, or failed. Waiting means account capacity is not yet reserved; queued means admitted and pending regional execution. Follow letterbox_url when it becomes available. Readiness and plan limits apply to every submission.
Run polling includes queue_position, wait_reason, and admitted_at. A full waiting queue returns 429 with error.code=queue_full. An accepted idempotency key always replays the same run, including after cancellation; use a new key for a new delivery.
Delivery queues โcurl -X POST -H "Authorization: Bearer $HATO_API_KEY" \
-H 'Idempotency-Key: example-run-001' -H 'Content-Type: application/json' \
--data '{}' 'https://hato-api.example/api/v1/letterflows/7/runs/'
Inspect Letterflow steps
Use the user-defined component ID, such as value1, in step routes. IDs are case-sensitive and resolve against the frozen execution snapshot. Later renaming does not change historical access.
GET /api/v1/letterbox/{id}/steps/
GET /api/v1/letterbox/{id}/steps/{step_id}/
GET /api/v1/letterbox/{id}/steps/{step_id}/input/
GET /api/v1/letterbox/{id}/steps/{step_id}/output/
GET /api/v1/letterbox/{id}/steps/{step_id}/request-headers/
GET /api/v1/letterbox/{id}/steps/{step_id}/request-body/
GET /api/v1/letterbox/{id}/steps/{step_id}/response-headers/
GET /api/v1/letterbox/{id}/steps/{step_id}/response-body/
GET /api/v1/letterbox/{id}/steps/{step_id}/request-metadata/
GET /api/v1/letterbox/{id}/steps/{step_id}/value/
Step inspection requires letterbox:read and redirects to the selected region. Attempted components appear in execution order, followed by skipped components. Skipped inputs remain readable; unproduced outputs return 404. Stored results are never re-evaluated.
Value outputs are text and Condition outputs are booleans. Large Values provide result_url; Letter steps provide captured request and response artifact links. Expired content returns 410.
Letter step metadata includes the recorded http_version when available. This is the final response's negotiated protocol, not the client's preference.
Send the Bearer key to Hub only. Fetch regional capability URLs with a separate request without forwarding your API key.
Letterbox lists also accept letterflow_id and run_id filters. source_type (letter, condition, value, or email) identifies the final result type, the kind of the final component.
List
Retrieve a cursor-paginated list of Letterbox response metadata, ordered newest first.
| Authorizationrequired | Bearer hato_live_... |
| If-None-Match | Optional ETag for 304 conditional cache validation. |
| Parameter | Description |
|---|---|
| limitinteger | Number of records to return (1โ100, default: 50). |
| cursorstring | Opaque pagination cursor from previous response. |
| sinceISO-8601 | Filter entries sent on or after datetime (e.g. 2026-08-01T00:00:00Z). |
| untilISO-8601 | Filter entries sent on or before datetime. |
| letter_idinteger | Filter entries by specific Letter ID. |
| source_typestring | Filter by final result type: 'letter', 'condition', or 'value'. |
| status_codeinteger | Filter by HTTP/FTP response code (e.g. 200, 404, 500). |
| pinnedboolean | Filter by pinned state: 'true' or 'false'. |
curl -H "Authorization: Bearer $HATO_API_KEY" \
"https://hato-api.example/api/v1/letterbox/?limit=20&pinned;=true"
{
"data": [
{
"id": 42,
"letterflow_id": 7,
"letterflow_name": "Health Check",
"source_type": "letter",
"run_id": null,
"steps_url": null,
"request": {
"method": "GET"
},
"response": {
"status_code": 200
},
"duration_ms": 142,
"sent_at": "2026-08-28T14:30:00+00:00",
"request_sent_at": "2026-08-28T14:30:00.010000+00:00",
"response_received_at": "2026-08-28T14:30:00.152000+00:00",
"is_pinned": true,
"bodies_pruned": false,
"error_message": "",
"region": "au",
"spoke_id": "spoke-au-1",
"artifact_available": true,
"request_headers_url": "https://spoke-au.example/artifacts/...",
"request_body_url": null,
"response_headers_url": "https://spoke-au.example/artifacts/...",
"response_body_url": "https://spoke-au.example/artifacts/..."
}
],
"next_cursor": "eyJzZW50X2F0IjoiMjAyNi0wOC0yOFQxNDozMDowMCIsImlkIjo0Mn0"
}
Deletes all unpinned Letterbox responses and immediately deletes their artifacts from regional storage. If a Spoke is temporarily unavailable, cleanup is queued for retry.
curl -X DELETE \
-H "Authorization: Bearer $HATO_API_KEY" \
"https://hato-api.example/api/v1/letterbox/"
Retrieve full metadata for a single Letterbox response by ID, including fresh signed regional artifact URLs.
| idintegerrequired | The unique ID of the Letterbox entry. |
curl -H "Authorization: Bearer $HATO_API_KEY" \
"https://hato-api.example/api/v1/letterbox/42/"
{
"data": {
"id": 42,
"letterflow_id": 7,
"letterflow_name": "Health Check",
"source_type": "letter",
"run_id": null,
"steps_url": null,
"request": {
"method": "GET"
},
"response": {
"status_code": 200
},
"duration_ms": 142,
"sent_at": "2026-08-28T14:30:00+00:00",
"request_sent_at": "2026-08-28T14:30:00.010000+00:00",
"response_received_at": "2026-08-28T14:30:00.152000+00:00",
"is_pinned": false,
"bodies_pruned": false,
"error_message": "",
"region": "au",
"spoke_id": "spoke-au-1",
"artifact_available": true,
"request_headers_url": "https://spoke-au.example/artifacts/read?token=...",
"request_body_url": null,
"response_headers_url": "https://spoke-au.example/artifacts/read?token=...",
"response_body_url": "https://spoke-au.example/artifacts/read?token=..."
}
}
Pin or unpin a Letterbox entry. Pinning protects the response body from expiration and FIFO capacity pruning.
| Field | Type | Description |
|---|---|---|
| is_pinnedrequired | boolean | Set true to pin, false to unpin. |
curl -X PATCH \
-H "Authorization: Bearer $HATO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"is_pinned": true}' \
"https://hato-api.example/api/v1/letterbox/42/"
{
"data": {
"id": 42,
"letterflow_id": 7,
"letterflow_name": "Health Check",
"source_type": "letter",
"run_id": null,
"steps_url": null,
"request": {
"method": "GET"
},
"response": {
"status_code": 200
},
"duration_ms": 142,
"sent_at": "2026-08-28T14:30:00+00:00",
"request_sent_at": "2026-08-28T14:30:00.010000+00:00",
"response_received_at": "2026-08-28T14:30:00.152000+00:00",
"is_pinned": true,
"bodies_pruned": false,
"error_message": "",
"region": "au",
"spoke_id": "spoke-au-1",
"artifact_available": true,
"request_headers_url": "https://spoke-au.example/artifacts/read?token=...",
"request_body_url": null,
"response_headers_url": "https://spoke-au.example/artifacts/read?token=...",
"response_body_url": "https://spoke-au.example/artifacts/read?token=..."
}
}
Permanently deletes a single response record and triggers cleanup of stored artifacts from the regional Spoke.
| idintegerrequired | The unique ID of the Letterbox entry to delete. |
curl -X DELETE \
-H "Authorization: Bearer $HATO_API_KEY" \
"https://hato-api.example/api/v1/letterbox/42/"
Returns a redirect to a short-lived regional URL. Send your Bearer key to the Hub, then fetch the regional Location separately without forwarding the key. Content grants follow retention and ownership checks.
| idintegerrequired | Letterbox entry ID. |
| artifactstringrequired | One of: request-headers, request-body, response-headers, response-body. |
curl -sS -D - -o /dev/null -H "Authorization: Bearer $HATO_API_KEY" \
"https://hato-api.example/api/v1/letterbox/42/response-body/"
curl -o response_payload.json "REGIONAL_LOCATION_URL"
HTTP/1.1 302 Found Location: https://spoke-au.example/artifacts/read?token=ey... Cache-Control: private, no-store
Directory
Paid integration keys may explicitly select directory:read to list variables and obtain regional value URLs, including secrets. This permission is never automatic. Directory changes remain dashboard-only.
GET /api/v1/directory/?region=au
GET /api/v1/directory/{id}/