Three route families. One base URL.
HAL's API surface splits by audience: widget flows (public), account routes (your session), and the public v1 API (API keys). Pick the right family and the rest is plain REST + JSON.
Base URL
https://api.chatwithhal.comRoute families
/api/widget/*— public flows used by the embeddable widget: conversations, identify, stages, CSAT, automations, articles. Project-scoped byapp_id./api/*(account) — dashboard-grade routes authenticated with a bearer token from login. Full workspace management./api/v1/*— the public integration API, authenticated with a project API key. Plan-gated and rate-limited per project.
Authentication
# account routes
Authorization: Bearer <access-token>
# public v1 routes
X-API-Key: <project-api-secret>API keys are per project (Settings → API). The key's project scopes every v1 call — there is no cross-project access.
Public v1: contacts & companies
GET /api/v1/contacts?search=&limit=&offset=
POST /api/v1/contacts
GET /api/v1/contacts/{id}
PATCH /api/v1/contacts/{id}
DELETE /api/v1/contacts/{id}
GET /api/v1/companies?search=&stage_id=&limit=&offset=
POST /api/v1/companies # name required
GET /api/v1/companies/{id}
PATCH /api/v1/companies/{id}Company writes accept deal_value, stage_id, notes, domain, industry, and is_priority. Both resources accept a flat metadata object — plain scalars only, ≤ 50 keys, ≤ 10KB total, replace-on-write.
Public v1: conversations & events
GET /api/v1/conversations
GET /api/v1/conversations/{id} # includes messages
PATCH /api/v1/conversations/{id} # status / assignee
POST /api/v1/events/trackevents/track records a product event and — when it matches your automation or health-signal rules — feeds pipelines and focus. Attach user_id, email, or company_id so the event lands on the right customer:
curl -X POST "https://api.chatwithhal.com/api/v1/events/track" \
-H "Content-Type: application/json" \
-H "X-API-Key: $HAL_API_KEY" \
-d '{
"name": "invoice.paid",
"event_key": "inv_789", # unique key = idempotent
"value": 149.0,
"user_id": "usr_123",
"company": { "name": "Nova Leaf", "domain": "novaleaf.com" }
}'Webhooks
Manage per-project webhooks via the API; deliveries POST a JSON envelope with an optional X-Webhook-Signature (HMAC-SHA256 of the raw body using your webhook secret):
GET/POST /api/v1/webhooks
PUT/DELETE /api/v1/webhooks/{id}
{
"event": "conversation.created",
"payload": { "...": "event-specific fields" },
"timestamp": "2026-01-15T10:30:00.000Z"
}Seventeen event types across conversations, messages, contacts, companies, stage changes, visitor and visitor event tracking, identification, and CSAT — including visitor.event_tracked and company.event_tracked so your systems can react to product signals in real time. Queue-based delivery with retries and a 10-second timeout.
Account routes (selection)
GET/PATCH /api/me # profile, preferences
GET /api/me/data-export # full GDPR export
DELETE /api/me # account deletion (30-day grace)
CRUD /api/apps, /api/contacts, /api/crm-stages
CRUD /api/articles, /api/article-groups
GET /api/ai/usage | /api/ai/quota
POST /api/billing/checkout | /portal | /change-planErrors & limits
- Validation failures return 400 with a specific message; plan-gated features return 402 with upgrade context.
- v1 is rate-limited per project — back off on 429.
- Every write accepts idempotency where it matters most:
events/trackdedupes onevent_key.