API Reference
Base URL: https://api.alrt.dev
Quick Start
Get up and running in three steps. Every request uses your server key as a Bearer token.
Create a team to get your API keys. The response includes your raw_key (shown only once).
1curl -X POST https://api.alrt.dev/teams \2 -H "Content-Type: application/json" \3 -d '{"name": "My App"}'Register a user who will receive notifications.
1curl -X POST https://api.alrt.dev/subscribers \2 -H "Authorization: Bearer $KEY" \3 -H "Content-Type: application/json" \4 -d '{"external_id": "user_1", "email": "jane@example.com"}'Fire an event that triggers a workflow and delivers notifications.
1curl -X POST https://api.alrt.dev/events/trigger \2 -H "Authorization: Bearer $KEY" \3 -H "Content-Type: application/json" \4 -d '{5 "workflow": "welcome",6 "subscriber_id": "user_1",7 "payload": {"name": "Jane"}8 }'Authentication
All API requests require a Bearer token in the Authorization header:
1Authorization: Bearer alrt_sk_live_abc123...Key Types
| Prefix | Type | Access |
|---|---|---|
| alrt_sk_ | Server Key | Full access (read + write) |
| alrt_ck_ | Client Key | Read-only (frontend / WebSocket) |
Rate Limits
| Tier | Limit | Applies To |
|---|---|---|
| Write | 60 req/min | POST, PATCH, PUT, DELETE |
| Read | 120 req/min | GET |
| Public | 30 req/min | Unauthenticated endpoints |
Events
Trigger workflow execution by firing named events. Each event name maps to exactly one workflow.
/events/triggerTrigger a workflow by event name. The matched workflow will execute asynchronously and deliver to all configured channels.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| workflow | string | Yes | The event name mapped to a workflow (e.g. 'welcome', 'invoice.paid'). |
| subscriber_id | string | Yes | The external_id of the subscriber to notify. |
| channels | string[] | No | Optional channel override. Values: "in_app", "email", "slack". If omitted, uses workflow definition. |
| payload | object | No ({}) | Key-value data passed to templates for variable substitution. |
| idempotency_key | string | No | Unique key to prevent duplicate triggers within a 24h window. |
Errors
1curl -X POST https://api.alrt.dev/events/trigger \2 -H "Authorization: Bearer $KEY" \3 -H "Content-Type: application/json" \4 -d '{5 "workflow": "welcome",6 "subscriber_id": "user_1",7 "channels": ["in_app", "email"],8 "payload": {"name": "Jane", "plan": "Pro"},9 "idempotency_key": "evt_abc123"10 }'1{2 "event_id": "evt_01HX...",3 "status": "accepted",4 "channels_requested": ["in_app", "email"],5 "channels_matched": ["in_app", "email"],6 "warnings": []7}Subscribers
Manage the users who receive notifications. Each subscriber is identified by a unique external_id that you define.
/subscribersCreate a new subscriber. The external_id must be unique within your team.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| external_id | string | Yes | Your application's unique user identifier. |
| string | No | Email address for the email channel. | |
| name | string | No | Display name used in templates. |
| slack_user_id | string | No | Slack member ID for the Slack channel. |
| custom_properties | object | No | Arbitrary key-value metadata for template rendering. |
| channel_preferences | object | No | Per-channel opt-in/out. E.g. {"email": true, "in_app": true, "slack": false}. |
Errors
1curl -X POST https://api.alrt.dev/subscribers \2 -H "Authorization: Bearer $KEY" \3 -H "Content-Type: application/json" \4 -d '{5 "external_id": "user_1",6 "email": "jane@example.com",7 "name": "Jane Doe",8 "channel_preferences": {9 "email": true,10 "in_app": true,11 "slack": false12 }13 }'1{2 "id": "sub_01HX...",3 "external_id": "user_1",4 "email": "jane@example.com",5 "name": "Jane Doe",6 "slack_user_id": null,7 "custom_properties": {},8 "channel_preferences": {9 "email": true,10 "in_app": true,11 "slack": false12 },13 "created_at": "2025-01-15T10:30:00Z",14 "updated_at": "2025-01-15T10:30:00Z"15}/subscribers/{external_id}Retrieve a single subscriber by their external_id.
Path Parameters
| Field | Type | Req | Description |
|---|---|---|---|
| external_id | string | Yes | The subscriber's unique identifier. |
Errors
1curl https://api.alrt.dev/subscribers/user_1 \2 -H "Authorization: Bearer $KEY"1{2 "id": "sub_01HX...",3 "external_id": "user_1",4 "email": "jane@example.com",5 "name": "Jane Doe",6 "slack_user_id": null,7 "custom_properties": {},8 "channel_preferences": {9 "email": true,10 "in_app": true,11 "slack": false12 },13 "created_at": "2025-01-15T10:30:00Z",14 "updated_at": "2025-01-15T10:30:00Z"15}/subscribers/{external_id}Update subscriber fields. Only provided fields are changed.
Path Parameters
| Field | Type | Req | Description |
|---|---|---|---|
| external_id | string | Yes | The subscriber's unique identifier. |
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| string | No | Updated email address. | |
| name | string | No | Updated display name. |
| slack_user_id | string | No | Updated Slack member ID. |
| custom_properties | object | No | Merged with existing custom properties. |
| channel_preferences | object | No | Updated channel preferences. |
Errors
1curl -X PATCH https://api.alrt.dev/subscribers/user_1 \2 -H "Authorization: Bearer $KEY" \3 -H "Content-Type: application/json" \4 -d '{"name": "Jane Smith", "email": "jane.smith@example.com"}'1{2 "id": "sub_01HX...",3 "external_id": "user_1",4 "email": "jane.smith@example.com",5 "name": "Jane Smith",6 "slack_user_id": null,7 "custom_properties": {},8 "channel_preferences": {9 "email": true,10 "in_app": true,11 "slack": false12 },13 "created_at": "2025-01-15T10:30:00Z",14 "updated_at": "2025-01-15T12:00:00Z"15}/subscribers/{external_id}Permanently delete a subscriber and all their notifications.
Path Parameters
| Field | Type | Req | Description |
|---|---|---|---|
| external_id | string | Yes | The subscriber's unique identifier. |
Errors
1curl -X DELETE https://api.alrt.dev/subscribers/user_1 \2 -H "Authorization: Bearer $KEY"/subscribers/{external_id}/preferencesRetrieve a subscriber's per-channel notification preferences.
Path Parameters
| Field | Type | Req | Description |
|---|---|---|---|
| external_id | string | Yes | The subscriber's unique identifier. |
Errors
1curl https://api.alrt.dev/subscribers/user_1/preferences \2 -H "Authorization: Bearer $KEY"1{2 "channel_preferences": {3 "email": true,4 "in_app": true,5 "slack": false6 }7}/subscribers/{external_id}/preferencesReplace a subscriber's channel preferences entirely. This is a full replacement, not a merge.
Path Parameters
| Field | Type | Req | Description |
|---|---|---|---|
| external_id | string | Yes | The subscriber's unique identifier. |
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| channel_preferences | object | Yes | Full replacement of channel preferences. E.g. {"email": true, "in_app": true, "slack": true}. |
Errors
1curl -X PATCH https://api.alrt.dev/subscribers/user_1/preferences \2 -H "Authorization: Bearer $KEY" \3 -H "Content-Type: application/json" \4 -d '{5 "channel_preferences": {6 "email": true,7 "in_app": true,8 "slack": true9 }10 }'1{2 "channel_preferences": {3 "email": true,4 "in_app": true,5 "slack": true6 }7}/subscribers/{external_id}/tokenGenerate a short-lived JWT for WebSocket authentication. The token expires after 24 hours.
Path Parameters
| Field | Type | Req | Description |
|---|---|---|---|
| external_id | string | Yes | The subscriber's unique identifier. |
Errors
1curl -X POST https://api.alrt.dev/subscribers/user_1/token \2 -H "Authorization: Bearer $KEY"1{2 "token": "eyJhbGciOiJIUzI1NiIs..."3}Notifications
Query and manage a subscriber's in-app notification feed.
/subscribers/{external_id}/notificationsList notifications for a subscriber with filtering and pagination.
Path Parameters
| Field | Type | Req | Description |
|---|---|---|---|
| external_id | string | Yes | The subscriber's unique identifier. |
Query Parameters
| Field | Type | Req | Description |
|---|---|---|---|
| channel | string | No | Filter by channel: "in_app", "email", or "slack". |
| is_read | boolean | No | Filter by read status. |
| limit | integer | No (20) | Number of results to return. Max 100. |
| offset | integer | No (0) | Number of results to skip. |
Errors
1curl "https://api.alrt.dev/subscribers/user_1/notifications?is_read=false&limit=10" \2 -H "Authorization: Bearer $KEY"1[2 {3 "id": "ntf_01HX...",4 "channel": "in_app",5 "subject": "Welcome to Acme!",6 "body": "Hey Jane, your account is ready.",7 "is_read": false,8 "is_archived": false,9 "payload": {"name": "Jane"},10 "created_at": "2025-01-15T10:31:00Z"11 }12]/subscribers/{external_id}/notifications/{notification_id}Update a single notification's read or archived status.
Path Parameters
| Field | Type | Req | Description |
|---|---|---|---|
| external_id | string | Yes | The subscriber's unique identifier. |
| notification_id | string | Yes | The notification ID. |
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| is_read | boolean | No | Mark as read or unread. |
| is_archived | boolean | No | Mark as archived or unarchived. |
Errors
1curl -X PATCH https://api.alrt.dev/subscribers/user_1/notifications/ntf_01HX \2 -H "Authorization: Bearer $KEY" \3 -H "Content-Type: application/json" \4 -d '{"is_read": true}'1{2 "id": "ntf_01HX...",3 "channel": "in_app",4 "subject": "Welcome to Acme!",5 "body": "Hey Jane, your account is ready.",6 "is_read": true,7 "is_archived": false,8 "payload": {"name": "Jane"},9 "created_at": "2025-01-15T10:31:00Z"10}/subscribers/{external_id}/notifications/mark-all-readMark all of a subscriber's notifications as read in one batch.
Path Parameters
| Field | Type | Req | Description |
|---|---|---|---|
| external_id | string | Yes | The subscriber's unique identifier. |
Errors
1curl -X POST https://api.alrt.dev/subscribers/user_1/notifications/mark-all-read \2 -H "Authorization: Bearer $KEY"Workflows
Create and manage notification workflows. Each workflow is triggered by a unique event name and defines a sequence of notification steps.
/workflowsCreate a new workflow. The event_name must be unique within your team.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| name | string | Yes | Human-readable name for the workflow. |
| event_name | string | Yes | Unique event name that triggers this workflow. |
| definition | object | No | Workflow definition (nodes and edges from the visual builder). |
Errors
1curl -X POST https://api.alrt.dev/workflows \2 -H "Authorization: Bearer $KEY" \3 -H "Content-Type: application/json" \4 -d '{5 "name": "Welcome Flow",6 "event_name": "welcome",7 "definition": {}8 }'1{2 "id": "wf_01HX...",3 "name": "Welcome Flow",4 "event_name": "welcome",5 "definition": {},6 "status": "draft",7 "created_at": "2025-01-15T10:30:00Z",8 "updated_at": "2025-01-15T10:30:00Z"9}/workflowsList all workflows for the current team.
1curl https://api.alrt.dev/workflows \2 -H "Authorization: Bearer $KEY"1[2 {3 "id": "wf_01HX...",4 "name": "Welcome Flow",5 "event_name": "welcome",6 "status": "published",7 "created_at": "2025-01-15T10:30:00Z",8 "updated_at": "2025-01-15T12:00:00Z"9 }10]/workflows/{workflow_id}Retrieve a single workflow including its full definition.
Path Parameters
| Field | Type | Req | Description |
|---|---|---|---|
| workflow_id | string | Yes | The workflow ID. |
Errors
1curl https://api.alrt.dev/workflows/wf_01HX \2 -H "Authorization: Bearer $KEY"1{2 "id": "wf_01HX...",3 "name": "Welcome Flow",4 "event_name": "welcome",5 "definition": {6 "nodes": [...],7 "edges": [...]8 },9 "status": "published",10 "created_at": "2025-01-15T10:30:00Z",11 "updated_at": "2025-01-15T12:00:00Z"12}/workflows/{workflow_id}Update a workflow's name, event_name, or definition. Published workflows cannot be edited — create a new version instead.
Path Parameters
| Field | Type | Req | Description |
|---|---|---|---|
| workflow_id | string | Yes | The workflow ID. |
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| name | string | No | Updated workflow name. |
| event_name | string | No | Updated event name. |
| definition | object | No | Updated workflow definition. |
Errors
1curl -X PUT https://api.alrt.dev/workflows/wf_01HX \2 -H "Authorization: Bearer $KEY" \3 -H "Content-Type: application/json" \4 -d '{5 "name": "Welcome Flow v2",6 "definition": {7 "nodes": [...],8 "edges": [...]9 }10 }'1{2 "id": "wf_01HX...",3 "name": "Welcome Flow v2",4 "event_name": "welcome",5 "definition": {6 "nodes": [...],7 "edges": [...]8 },9 "status": "draft",10 "created_at": "2025-01-15T10:30:00Z",11 "updated_at": "2025-01-15T14:00:00Z"12}/workflows/{workflow_id}/publishValidate and publish a workflow. The definition must contain a trigger node and no more than 10 steps.
Path Parameters
| Field | Type | Req | Description |
|---|---|---|---|
| workflow_id | string | Yes | The workflow ID. |
Errors
1curl -X POST https://api.alrt.dev/workflows/wf_01HX/publish \2 -H "Authorization: Bearer $KEY"1{2 "id": "wf_01HX...",3 "name": "Welcome Flow",4 "event_name": "welcome",5 "status": "published",6 "published_at": "2025-01-15T14:05:00Z"7}/workflows/{workflow_id}Permanently delete a workflow.
Path Parameters
| Field | Type | Req | Description |
|---|---|---|---|
| workflow_id | string | Yes | The workflow ID. |
Errors
1curl -X DELETE https://api.alrt.dev/workflows/wf_01HX \2 -H "Authorization: Bearer $KEY"Providers
Configure delivery providers for each channel (e.g. SendGrid for email, Slack API for Slack). Provider config is encrypted at rest and never returned in API responses.
/providersAdd a new delivery provider. The config object is encrypted and will not be returned in any response.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| channel | string | Yes | The channel this provider serves: "email", "slack", or "in_app". |
| provider_type | string | Yes | Provider identifier (e.g. "sendgrid", "ses", "resend", "slack"). |
| config | object | Yes | Provider-specific configuration (API keys, tokens, etc). Encrypted at rest. |
1curl -X POST https://api.alrt.dev/providers \2 -H "Authorization: Bearer $KEY" \3 -H "Content-Type: application/json" \4 -d '{5 "channel": "email",6 "provider_type": "sendgrid",7 "config": {8 "api_key": "SG.xxxx...",9 "from_email": "noreply@myapp.com",10 "from_name": "My App"11 }12 }'1{2 "id": "prv_01HX...",3 "channel": "email",4 "provider_type": "sendgrid",5 "is_active": true,6 "created_at": "2025-01-15T10:30:00Z"7}/providersList all configured providers. Config objects are never included in the response.
1curl https://api.alrt.dev/providers \2 -H "Authorization: Bearer $KEY"1[2 {3 "id": "prv_01HX...",4 "channel": "email",5 "provider_type": "sendgrid",6 "is_active": true,7 "created_at": "2025-01-15T10:30:00Z"8 },9 {10 "id": "prv_02HX...",11 "channel": "slack",12 "provider_type": "slack",13 "is_active": true,14 "created_at": "2025-01-16T09:00:00Z"15 }16]/providers/{provider_id}Remove a delivery provider.
Path Parameters
| Field | Type | Req | Description |
|---|---|---|---|
| provider_id | string | Yes | The provider ID. |
Errors
1curl -X DELETE https://api.alrt.dev/providers/prv_01HX \2 -H "Authorization: Bearer $KEY"WebSocket
Receive real-time in-app notifications over a persistent WebSocket connection. Authenticate using a subscriber-scoped JWT.
Endpoint
ws://api.alrt.dev/ws?token=<jwt>Authentication
Obtain a JWT via POST /subscribers/{external_id}/token using your server key. Pass the token as the token query parameter. Tokens expire after 24 hours.
Client Messages
| Type | Payload | Description |
|---|---|---|
| ping | — | Server responds with pong. Use as a keep-alive. |
| mark_read | notification_id | Mark a single notification as read. |
| mark_all_read | — | Mark all notifications as read. |
Server Messages
When a notification is delivered to the in-app channel, the server pushes the full notification object:
1{2 "type": "notification",3 "data": {4 "id": "ntf_01HX...",5 "channel": "in_app",6 "subject": "New comment on your post",7 "body": "Alex replied to your thread.",8 "is_read": false,9 "payload": {"thread_id": "thr_42"},10 "created_at": "2025-01-15T10:31:00Z"11 }12}Error Codes
| Code | Meaning |
|---|---|
| 4001 | Invalid or expired JWT. Re-fetch a token and reconnect. |
| 4000 | Connection replaced. A new WebSocket connection was opened for the same subscriber, so this one was closed. |
Example Client
1const token = await fetch("/api/ws-token").then(r => r.json());2const ws = new WebSocket("ws://api.alrt.dev/ws?token=" + token);34ws.onmessage = (event) => {5 const msg = JSON.parse(event.data);6 if (msg.type === "notification") {7 showToast(msg.data.subject);8 }9};1011// Keep-alive ping every 30s12setInterval(() => ws.send(JSON.stringify({ type: "ping" })), 30000);1314// Mark a notification as read15ws.send(JSON.stringify({16 type: "mark_read",17 notification_id: "ntf_01HX..."18}));