Two HTTP APIs are live today, both authenticated with the same developer key. The Tasks API puts work into Hollow from outside it. The Lumen API takes logs, metrics and uptime monitors from services you run yourself.
The rest of the suite is reachable from inside the product instead: Hollow AI’s native tools for anything you can ask for, and Cascade for anything you want to run on a schedule or a webhook.
Every request carries a developer key as a bearer token in the Authorization header. Mint, name and revoke keys in Settings → API keys. One key works for both APIs.
curl -X POST https://europe-west2-sstk-hollow.cloudfunctions.net/taskIntakeApi/v1/tasks \\
-H "Authorization: Bearer hlw_sk_…" \\
-H "Content-Type: application/json" \\
-d '{ "text": "Investigate slow checkout" }'KEY_REVOKED or INVALID_KEY.ACCOUNT_NOT_APPROVED, whatever the key.Shared by both APIs.
Each API has its own base; the version sits in the path after it, as /v1/….
https://europe-west2-sstk-hollow.cloudfunctions.net/taskIntakeApihttps://projecthollow.com/monitoring/ingest: a hosting rewrite in front of the same function, purely for a friendlier URL. The raw function URL, https://europe-west2-sstk-hollow.cloudfunctions.net/monitoringIngest, still works identically and is not deprecated.JSON in, JSON out. Send Content-Type: application/json on any request with a body.
Send an Idempotency-Key header, or an idempotencyKey field in the body on the Tasks API. Repeating a key returns the original record with "duplicate": true instead of creating a second one. On POST /v1/monitors the key goes further: it is the monitor’s identity, so a repeat is an update.
Every error body has the same shape.
{ "error": { "code": "RATE_LIMITED", "status": 429, "message": "Too many requests" } }MISSING_TEXT, MISSING_FIELD, MISSING_ENTRIES, MISSING_POINTS, INVALID_FIELD, INVALID_REQUEST, TOO_MANY_ENTRIES, TOO_MANY_POINTS, MISSING_IDEMPOTENCY_KEYMISSING_AUTH_HEADER, INVALID_KEY_FORMAT, INVALID_KEY, KEY_REVOKEDACCOUNT_NOT_APPROVEDNOT_FOUND: unknown id, or not visible to this key.CONFLICT: two concurrent calls shared one idempotency key and the first has not finished. Retry.PAYLOAD_TOO_LARGE: the body is over 256Â KiB.RATE_LIMITED or QUOTA_EXCEEDED.The two limiters are separate counters, so a busy ingestion integration cannot eat into your task-creation budget.
Put work into Hollow from anywhere: a script, CI, a form, another product. Two front doors on the same intake spine: a developer key for code you write, and a webhook URL for services you cannot.
Create one task, synchronously. text is the only required field; everything else narrows where it lands.
title is accepted as an alias. Required.low · medium · high · urgent. Optional.YYYY-MM-DD and HH:mm. Optional.Idempotency-Key header. Optional.Request
curl -X POST https://europe-west2-sstk-hollow.cloudfunctions.net/taskIntakeApi/v1/tasks \\
-H "Authorization: Bearer hlw_sk_…" \\
-H "Content-Type: application/json" \\
-d '{
"text": "Investigate slow checkout",
"description": "p95 latency doubled after the deploy",
"list": "Engineering",
"priority": "high",
"labels": ["perf"],
"idempotencyKey": "deploy-1234"
}'id, ticketKey and listId, plus misrouted: true if your target list no longer existed and the task fell back to a default one.{ "id", "duplicate": true } for an idempotent repeat.An unmatched list name falls back to the default list of your default board. Lists are never created for you.
A no-code URL you paste into GitHub, Zapier or anything else that can call one. Create it in Tasks → Integrations: pick the destination list, map which JSON paths hold the title, description, priority and event id, and add routing rules if you want them.
Endpoint
https://europe-west2-sstk-hollow.cloudfunctions.net/taskIntakeHook/hook/{hookId}?token=hook_…The URL embeds an unguessable token, shown once. That token is the authentication, and you can rotate it at any time. It can be sent as an X-Hollow-Token header instead of a query parameter.
issue.titleissue.bodyissue.id: the dedupe keyGitHub redelivers events. Because the mapping picks out an event id, a redelivery returns the original task rather than creating a second one.
Both front doors take an ordered list of routing rules. Each rule’s condition is a HEL predicate (the same query language Tasks filtering uses) evaluated against the normalised ticket. First match wins; if nothing matches, the channel’s default destination applies.
The fields a rule can read are title, description, priority, label, emaildomain and source.
title:billing OR title:invoice → a "Finance" list priority>=high emaildomain:acme.com → a "VIP" list
Send logs, metrics and uptime monitors from services you run yourself, and read a monitor’s state back. Same developer key as the Tasks API.
Batched log ingestion. Up to 500 entries per request, inside a 256Â KiB body.
Request
curl -X POST https://projecthollow.com/monitoring/ingest/v1/logs \\
-H "Authorization: Bearer hlw_sk_…" \\
-H "Content-Type: application/json" \\
-d '{
"service": "checkout-api",
"entries": [
{ "level": "error", "message": "party lookup timed out", "ts": "2026-07-26T09:14:02Z" }
]
}'Response · 202
{ "accepted": 1 }Batched metric points. Up to 1,000 points per request, inside a 256Â KiB body.
Request
curl -X POST https://projecthollow.com/monitoring/ingest/v1/metrics \\
-H "Authorization: Bearer hlw_sk_…" \\
-H "Content-Type: application/json" \\
-d '{
"service": "checkout-api",
"points": [
{ "name": "request_latency_ms", "value": 214, "ts": "2026-07-26T09:14:02Z",
"tags": { "route": "/checkout/start" } }
]
}'Response · 202
{ "accepted": 1 }Create or update a monitor as code. An Idempotency-Key is required, as a header or a body field, and here it is the monitor’s identity rather than a replay guard: the first call with a given key creates a monitor, and every later call with that key applies the declaration to the same one. Changing intervalMinutes or target in a config file and re-running is an update, never a second monitor.
Request
curl -X POST https://projecthollow.com/monitoring/ingest/v1/monitors \\
-H "Authorization: Bearer hlw_sk_…" \\
-H "Content-Type: application/json" \\
-H "Idempotency-Key: checkout-api-uptime" \\
-d '{
"name": "Checkout API uptime",
"type": "http",
"target": "https://checkout.example.com/health",
"intervalMinutes": 5
}'{ "id": "mon_abc123" }{ "id": "mon_abc123", "duplicate": true }A browser monitor also takes a steps array (at most 20 entries of kind, selector, optional value and timeoutMs, where kind is click, fill, wait_for or assert_text) and is rejected without one. Validation is identical on create and update, so a repeat with a bad target fails with INVALID_REQUEST rather than silently keeping the old value.
Every monitor belonging to the key’s owner.
Response · 200
{
"monitors": [
{ "id": "mon_abc123", "name": "Checkout API uptime", "type": "http",
"target": "https://checkout.example.com/health", "status": "up",
"intervalMinutes": 5 }
]
}The current state of one monitor, and when it was last checked.
Response · 200
{ "id": "mon_abc123", "status": "up", "lastCheckedAt": "2026-07-26T09:14:02Z" }