HUMANin theLOOP

API

Ingest and decide HTTP endpoints.

Authenticate ingest with a project API key: Authorization: Bearer hitly_...

The key must belong to HITLY_PROJECT_ID. Send Idempotency-Key when the origin may retry the suspend POST. A matching open item (pending or failed_resume) is replayed. After reject, cancel, decide, or expiry, the same key creates a new inbox item.

Create approval

POST /api/v1/approvals

{
  "plugin": "mastra",
  "projectId": "prj_...",
  "kind": "workflow",
  "workflowId": "refund-workflow",
  "runId": "run_...",
  "stepId": "hitly-approval",
  "action": { "name": "send-refund", "args": { "orderId": "1842" } },
  "suspendPayload": { "reason": "Human approval required." }
}

mastraBaseUrl is optional when the project already stores the origin base URL.

HTTP origins send plugin: "http", resumeUrl, and optional metadata. Hitly echoes metadata on the resume POST:

{
  "plugin": "http",
  "projectId": "prj_...",
  "runId": "execution-or-page-id",
  "actionName": "http-callback",
  "metadata": { "pageId": "…" },
  "resumeUrl": "https://your-callback.example/hitly-resume"
}

On accept or reject, Hitly POSTs that metadata unchanged to resumeUrl:

{
  "decision": "accept",
  "id": "apr_...",
  "metadata": { "pageId": "…" }
}

Decide

POST /api/v1/approvals/:id/decide (session cookie)

{ "decision": "accept" }

or { "decision": "edit", "editedArgs": { "amount": 2000 } }.

The JSON response includes resumeResponse: the payload sent to the origin (resumeData) plus the origin HTTP status and body. For Mastra workflows that body is the completed run result.

Retry resume

POST /api/v1/approvals/:id/retry-resume when status is failed_resume.

Force cancel

POST /api/v1/approvals/:id/cancel (session) when status is pending or failed_resume. Closes the item as cancelled without calling the origin. Use when the origin run is no longer suspended.

Inbox

GET /api/v1/inbox (session cookie or Authorization: Bearer <session token>)

Query: scope=all|open|closed, q, projectId. open is pending + failed resume. closed is decided + expired + cancelled.

GET /api/v1/inbox/summary returns { pending, failedResume, decidedToday, projectCount }.

GET /api/v1/approvals/:id returns envelope, origin, originFields, decisions, and canAct (session cookie or reviewer Bearer token).

A project API key on the same route returns a slim origin-poll payload: { id, status, decision?, response? }. Hermes uses this while present() waits and while the gateway kanban poller applies comment + unblock.

Workspaces and devices

GET /api/v1/workspaces lists memberships. POST /api/v1/workspaces/current with { "workspaceId" } selects one. Mobile also sends x-hitly-workspace-id.

GET /api/v1/workspace returns the current workspace (name, timezone, default SLA, canManage). PATCH /api/v1/workspace with { "name", "timezone", "sla" } updates it (workspace admins).

GET /api/v1/projects lists visible projects. GET /api/v1/projects/:id is the summary. GET/PATCH /api/v1/projects/:id/config is project config for admins (name, SLA, assignee, origin URL; token is write-only). Ingest API keys stay on the web app.

POST /api/v1/devices registers an Expo push token ({ "token", "platform": "ios"|"android" }). DELETE /api/v1/devices unregisters { "token" }. Project API keys are ingest-only — never use them from the reviewer app.

GET /api/v1/health is public: { "ok": true, "product": "hitly", "edition": "oss"|"cloud" }.

POST /api/v1/housekeeping runs due jobs (Authorization: Bearer $HITLY_CRON_SECRET, or unauthenticated in development when the secret is unset). Body is optional: { "job": "expire-overdue-approvals", "force": true }.

See Mobile for the Expo app, deep links, and push payload.

On this page