Docs

Server API

Everything the SDKs, the report widget and the generator send goes to https://api.hlpd.uk. These are its public endpoints and what they expect.

SDK API

Requests to /v1/* carry the app's SDK key and identify the bundle and device:

HTTP
Authorization: Bearer hk_…
X-Hlpd-Bundle: drink-ios
X-Hlpd-Bundle-Version: 2026.10.12-1
X-Hlpd-Device: 7fQ2kLp9Xc3mVb8N
EndpointPurpose
POST /v1/chat/completionsOpenAI-style chat completions for the on-device agent, within the app's daily, monthly and per-device limits
POST /v1/eventsAnalytics events as {"events": [...]}, at most 200 per request
GET /v1/orderTopic order and service-notice pins, optionally for one screen, with ETag support
GET /v1/bundleThe live published manifest for over-the-air updates, or 204 when none is live
GET /v1/files/{sha256}/{name}One bundle file by content hash

Event types are topic_view, search, walkthrough_start, walkthrough_complete, chat_question, feedback, suggestion_shown, suggestion_opened and report_filed.

The chat agent runs on the device and the API only gates the language model. The agent uses plain tool calling with search_topics, get_topic, show_topic, show_walkthrough and escalate, with at most 6 tool rounds and 600 tokens per completion.

Reports and tickets

Reports and feature requests are sent as multipart/form-data to POST /api/reports:

FieldNotes
project_idThe public project id, brp_…
titleRequired
kindbug (default) or feature
bodyThe description
client_idThe device id. Required when attaching files
classification_levellow, medium (default) or high
metadataA JSON object as a string, such as the screen, app version and topics viewed
attachmentsImages, up to 4 files of 5 MB each
Shell
curl https://api.hlpd.uk/api/reports \
  -F project_id=brp_your_project \
  -F title="Reminders won't stop" \
  -F kind=bug \
  -F body="They keep buzzing after 10pm." \
  -F client_id=7fQ2kLp9Xc3mVb8N \
  -F 'metadata={"screen_name": "Settings"}'

The response identifies the ticket. reply_token is only returned when the app has public ticket status turned on; keep it to read and reply to the thread.

JSON
{
  "id": "…",
  "ticket_id": "…",
  "ticket_url": "https://api.hlpd.uk/tickets/…",
  "client_id": "7fQ2kLp9Xc3mVb8N",
  "kind": "bug",
  "status": "received",
  "classification_level": "medium",
  "status_visible": true,
  "can_reply": true,
  "reply_token": "…"
}
EndpointPurpose
GET /api/tickets/{ticket_id}Ticket status and public messages
POST /api/tickets/{ticket_id}/messagesReply as the reporter with {"reply_token": "…", "body": "…"}
POST /api/tickets/{ticket_id}/pushRegister for reply pushes with {"reply_token": "…", "platform": "apns", "token": "…"}; platform is apns or fcm
POST /api/tickets/{ticket_id}/push/unregisterStop reply pushes
GET /report/{project_id}The hosted report form
GET /tickets/{ticket_id}The hosted ticket status page

When an app has allowed origins, browser requests must come from one of them.

Owner API

The admin, the native admin app and the generator use owner endpoints under /admin/api. Native apps and command-line tools open a bearer session, valid for 30 days, with POST /admin/api/session and {"email": "…", "password": "…"}. Coding agents can create an account and first app with POST /api/signup; see For coding agents.

EndpointPurpose
GET /admin/api/projectsYour apps
POST /admin/api/projectsCreate an app within your plan's app limit
PATCH /admin/api/projects/{id}Bundle id, quotas and store text
POST /admin/api/projects/{id}/keysCreate an SDK key
DELETE /admin/api/projects/{id}/keys/{key}Revoke an SDK key
POST /admin/api/projects/{id}/bundlesUpload a bundle as a gzipped tar
GET /admin/api/projects/{id}/bundlesList uploaded bundles
PUT /admin/api/projects/{id}/bundleChoose the live bundle version
GET /admin/api/projects/{id}/usageChat usage against the app's limits
GET /admin/api/projects/{id}/analyticsHelp analytics; topics and gaps sit alongside it
GET /admin/api/projects/{id}/pinsService-notice pins; PUT replaces them, with ETag
GET /admin/api/reportsReports across your apps

GET /health reports whether the server is up.

Accounts and email

Owners sign in with an email address and a password. When an account is created, hlpd sends a verification email. The account works straight away; upgrading to a paid plan needs a verified email address.

EndpointPurpose
POST /api/signupCreate an account and first app with {"email": "…", "password": "…", "project": "…"}; see For coding agents
POST /api/verifyVerify the email address with {"token": "…"} from the verification email
POST /api/verify/resendSend the signed-in owner's verification email again
POST /api/password/forgotSend a password reset email with {"email": "…"}
POST /api/password/resetSet a new password with {"token": "…", "password": "…"} from the reset email

Run your own server

The server is a FastAPI app with one SQLite database. To deploy it to a Linux host running Caddy:

  1. Copy deploy/env.example to /opt/hlpd/server/.env on the host and fill it in.
  2. Point the API domain at the host. Caddy obtains the certificate.
  3. Run deploy/server.sh user@host, and again after every change.

At startup the server creates two accounts if they are missing: a super-admin from HLPD_ADMIN_EMAIL and HLPD_ADMIN_PASSWORD, and an owner from HLPD_OWNER_EMAIL and HLPD_OWNER_PASSWORD.

Verification and password reset emails use these settings:

VariablePurpose
HLPD_MAIL_BACKENDlog writes each email to the server log instead of sending it; ses sends it through Amazon SES
HLPD_MAIL_FROMSender address, which must be a verified SES identity when sending through SES
HLPD_MAIL_REPLY_TOReply-to address
AWS_REGIONThe SES region

With ses, also provide AWS credentials allowed to send through SES, such as AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.

Data lives in the service user's ~/.hlpd: the SQLite database, uploads and bundle objects.