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.

Step 1 — Create a Team

Create a team to get your API keys. The response includes your raw_key (shown only once).

Request
1curl -X POST https://api.alrt.dev/teams \
2 -H "Content-Type: application/json" \
3 -d '{"name": "My App"}'
TypeScript3 lines
Step 2 — Create a Subscriber

Register a user who will receive notifications.

Request
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"}'
TypeScript4 lines
Step 3 — Trigger a Notification

Fire an event that triggers a workflow and delivers notifications.

Request
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 }'
TypeScript8 lines

Authentication

All API requests require a Bearer token in the Authorization header:

Header
1Authorization: Bearer alrt_sk_live_abc123...
TypeScript1 lines

Key Types

PrefixTypeAccess
alrt_sk_Server KeyFull access (read + write)
alrt_ck_Client KeyRead-only (frontend / WebSocket)

Rate Limits

TierLimitApplies To
Write60 req/minPOST, PATCH, PUT, DELETE
Read120 req/minGET
Public30 req/minUnauthenticated endpoints

Events

Trigger workflow execution by firing named events. Each event name maps to exactly one workflow.

POST/events/trigger

Trigger a workflow by event name. The matched workflow will execute asynchronously and deliver to all configured channels.

Request Body

FieldTypeReqDescription
workflowstringYesThe event name mapped to a workflow (e.g. 'welcome', 'invoice.paid').
subscriber_idstringYesThe external_id of the subscriber to notify.
channelsstring[]NoOptional channel override. Values: "in_app", "email", "slack". If omitted, uses workflow definition.
payloadobjectNo ({})Key-value data passed to templates for variable substitution.
idempotency_keystringNoUnique key to prevent duplicate triggers within a 24h window.

Errors

404Workflow not found for event name.
404Subscriber not found.
Request
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 }'
TypeScript10 lines
Response — 202 Accepted
1{
2 "event_id": "evt_01HX...",
3 "status": "accepted",
4 "channels_requested": ["in_app", "email"],
5 "channels_matched": ["in_app", "email"],
6 "warnings": []
7}
TypeScript7 lines

Subscribers

Manage the users who receive notifications. Each subscriber is identified by a unique external_id that you define.

POST/subscribers

Create a new subscriber. The external_id must be unique within your team.

Request Body

FieldTypeReqDescription
external_idstringYesYour application's unique user identifier.
emailstringNoEmail address for the email channel.
namestringNoDisplay name used in templates.
slack_user_idstringNoSlack member ID for the Slack channel.
custom_propertiesobjectNoArbitrary key-value metadata for template rendering.
channel_preferencesobjectNoPer-channel opt-in/out. E.g. {"email": true, "in_app": true, "slack": false}.

Errors

409Subscriber with this external_id already exists.
Request
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": false
12 }
13 }'
TypeScript13 lines
Response — 201 Created
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": false
12 },
13 "created_at": "2025-01-15T10:30:00Z",
14 "updated_at": "2025-01-15T10:30:00Z"
15}
TypeScript15 lines
GET/subscribers/{external_id}

Retrieve a single subscriber by their external_id.

Path Parameters

FieldTypeReqDescription
external_idstringYesThe subscriber's unique identifier.

Errors

404Subscriber not found.
Request
1curl https://api.alrt.dev/subscribers/user_1 \
2 -H "Authorization: Bearer $KEY"
TypeScript2 lines
Response — 200 OK
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": false
12 },
13 "created_at": "2025-01-15T10:30:00Z",
14 "updated_at": "2025-01-15T10:30:00Z"
15}
TypeScript15 lines
PATCH/subscribers/{external_id}

Update subscriber fields. Only provided fields are changed.

Path Parameters

FieldTypeReqDescription
external_idstringYesThe subscriber's unique identifier.

Request Body

FieldTypeReqDescription
emailstringNoUpdated email address.
namestringNoUpdated display name.
slack_user_idstringNoUpdated Slack member ID.
custom_propertiesobjectNoMerged with existing custom properties.
channel_preferencesobjectNoUpdated channel preferences.

Errors

404Subscriber not found.
Request
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"}'
TypeScript4 lines
Response — 200 OK
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": false
12 },
13 "created_at": "2025-01-15T10:30:00Z",
14 "updated_at": "2025-01-15T12:00:00Z"
15}
TypeScript15 lines
DELETE/subscribers/{external_id}

Permanently delete a subscriber and all their notifications.

Path Parameters

FieldTypeReqDescription
external_idstringYesThe subscriber's unique identifier.

Errors

404Subscriber not found.
Request
1curl -X DELETE https://api.alrt.dev/subscribers/user_1 \
2 -H "Authorization: Bearer $KEY"
TypeScript2 lines
GET/subscribers/{external_id}/preferences

Retrieve a subscriber's per-channel notification preferences.

Path Parameters

FieldTypeReqDescription
external_idstringYesThe subscriber's unique identifier.

Errors

404Subscriber not found.
Request
1curl https://api.alrt.dev/subscribers/user_1/preferences \
2 -H "Authorization: Bearer $KEY"
TypeScript2 lines
Response — 200 OK
1{
2 "channel_preferences": {
3 "email": true,
4 "in_app": true,
5 "slack": false
6 }
7}
TypeScript7 lines
PATCH/subscribers/{external_id}/preferences

Replace a subscriber's channel preferences entirely. This is a full replacement, not a merge.

Path Parameters

FieldTypeReqDescription
external_idstringYesThe subscriber's unique identifier.

Request Body

FieldTypeReqDescription
channel_preferencesobjectYesFull replacement of channel preferences. E.g. {"email": true, "in_app": true, "slack": true}.

Errors

404Subscriber not found.
Request
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": true
9 }
10 }'
TypeScript10 lines
Response — 200 OK
1{
2 "channel_preferences": {
3 "email": true,
4 "in_app": true,
5 "slack": true
6 }
7}
TypeScript7 lines
POST/subscribers/{external_id}/token

Generate a short-lived JWT for WebSocket authentication. The token expires after 24 hours.

Path Parameters

FieldTypeReqDescription
external_idstringYesThe subscriber's unique identifier.

Errors

404Subscriber not found.
Request
1curl -X POST https://api.alrt.dev/subscribers/user_1/token \
2 -H "Authorization: Bearer $KEY"
TypeScript2 lines
Response — 200 OK
1{
2 "token": "eyJhbGciOiJIUzI1NiIs..."
3}
TypeScript3 lines

Notifications

Query and manage a subscriber's in-app notification feed.

GET/subscribers/{external_id}/notifications

List notifications for a subscriber with filtering and pagination.

Path Parameters

FieldTypeReqDescription
external_idstringYesThe subscriber's unique identifier.

Query Parameters

FieldTypeReqDescription
channelstringNoFilter by channel: "in_app", "email", or "slack".
is_readbooleanNoFilter by read status.
limitintegerNo (20)Number of results to return. Max 100.
offsetintegerNo (0)Number of results to skip.

Errors

404Subscriber not found.
Request
1curl "https://api.alrt.dev/subscribers/user_1/notifications?is_read=false&limit=10" \
2 -H "Authorization: Bearer $KEY"
TypeScript2 lines
Response — 200 OK
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]
TypeScript12 lines
PATCH/subscribers/{external_id}/notifications/{notification_id}

Update a single notification's read or archived status.

Path Parameters

FieldTypeReqDescription
external_idstringYesThe subscriber's unique identifier.
notification_idstringYesThe notification ID.

Request Body

FieldTypeReqDescription
is_readbooleanNoMark as read or unread.
is_archivedbooleanNoMark as archived or unarchived.

Errors

404Notification not found.
Request
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}'
TypeScript4 lines
Response — 200 OK
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}
TypeScript10 lines
POST/subscribers/{external_id}/notifications/mark-all-read

Mark all of a subscriber's notifications as read in one batch.

Path Parameters

FieldTypeReqDescription
external_idstringYesThe subscriber's unique identifier.

Errors

404Subscriber not found.
Request
1curl -X POST https://api.alrt.dev/subscribers/user_1/notifications/mark-all-read \
2 -H "Authorization: Bearer $KEY"
TypeScript2 lines

Workflows

Create and manage notification workflows. Each workflow is triggered by a unique event name and defines a sequence of notification steps.

POST/workflows

Create a new workflow. The event_name must be unique within your team.

Request Body

FieldTypeReqDescription
namestringYesHuman-readable name for the workflow.
event_namestringYesUnique event name that triggers this workflow.
definitionobjectNoWorkflow definition (nodes and edges from the visual builder).

Errors

409A workflow with this event_name already exists.
Request
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 }'
TypeScript8 lines
Response — 201 Created
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}
TypeScript9 lines
GET/workflows

List all workflows for the current team.

Request
1curl https://api.alrt.dev/workflows \
2 -H "Authorization: Bearer $KEY"
TypeScript2 lines
Response — 200 OK
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]
TypeScript10 lines
GET/workflows/{workflow_id}

Retrieve a single workflow including its full definition.

Path Parameters

FieldTypeReqDescription
workflow_idstringYesThe workflow ID.

Errors

404Workflow not found.
Request
1curl https://api.alrt.dev/workflows/wf_01HX \
2 -H "Authorization: Bearer $KEY"
TypeScript2 lines
Response — 200 OK
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}
TypeScript12 lines
PUT/workflows/{workflow_id}

Update a workflow's name, event_name, or definition. Published workflows cannot be edited — create a new version instead.

Path Parameters

FieldTypeReqDescription
workflow_idstringYesThe workflow ID.

Request Body

FieldTypeReqDescription
namestringNoUpdated workflow name.
event_namestringNoUpdated event name.
definitionobjectNoUpdated workflow definition.

Errors

400Cannot edit a published workflow.
404Workflow not found.
Request
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 }'
TypeScript10 lines
Response — 200 OK
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}
TypeScript12 lines
POST/workflows/{workflow_id}/publish

Validate and publish a workflow. The definition must contain a trigger node and no more than 10 steps.

Path Parameters

FieldTypeReqDescription
workflow_idstringYesThe workflow ID.

Errors

400Workflow has no nodes.
400Workflow must have a trigger node.
400Workflow exceeds maximum of 10 steps.
404Workflow not found.
Request
1curl -X POST https://api.alrt.dev/workflows/wf_01HX/publish \
2 -H "Authorization: Bearer $KEY"
TypeScript2 lines
Response — 200 OK
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}
TypeScript7 lines
DELETE/workflows/{workflow_id}

Permanently delete a workflow.

Path Parameters

FieldTypeReqDescription
workflow_idstringYesThe workflow ID.

Errors

404Workflow not found.
Request
1curl -X DELETE https://api.alrt.dev/workflows/wf_01HX \
2 -H "Authorization: Bearer $KEY"
TypeScript2 lines

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.

POST/providers

Add a new delivery provider. The config object is encrypted and will not be returned in any response.

Request Body

FieldTypeReqDescription
channelstringYesThe channel this provider serves: "email", "slack", or "in_app".
provider_typestringYesProvider identifier (e.g. "sendgrid", "ses", "resend", "slack").
configobjectYesProvider-specific configuration (API keys, tokens, etc). Encrypted at rest.
Request
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 }'
TypeScript12 lines
Response — 201 Created
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}
TypeScript7 lines
GET/providers

List all configured providers. Config objects are never included in the response.

Request
1curl https://api.alrt.dev/providers \
2 -H "Authorization: Bearer $KEY"
TypeScript2 lines
Response — 200 OK
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]
TypeScript16 lines
DELETE/providers/{provider_id}

Remove a delivery provider.

Path Parameters

FieldTypeReqDescription
provider_idstringYesThe provider ID.

Errors

404Provider not found.
Request
1curl -X DELETE https://api.alrt.dev/providers/prv_01HX \
2 -H "Authorization: Bearer $KEY"
TypeScript2 lines

WebSocket

Receive real-time in-app notifications over a persistent WebSocket connection. Authenticate using a subscriber-scoped JWT.

Connection

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

TypePayloadDescription
pingServer responds with pong. Use as a keep-alive.
mark_readnotification_idMark a single notification as read.
mark_all_readMark all notifications as read.

Server Messages

When a notification is delivered to the in-app channel, the server pushes the full notification object:

Server Push
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}
TypeScript12 lines

Error Codes

CodeMeaning
4001Invalid or expired JWT. Re-fetch a token and reconnect.
4000Connection replaced. A new WebSocket connection was opened for the same subscriber, so this one was closed.

Example Client

websocket-client.ts
1const token = await fetch("/api/ws-token").then(r => r.json());
2const ws = new WebSocket("ws://api.alrt.dev/ws?token=" + token);
3
4ws.onmessage = (event) => {
5 const msg = JSON.parse(event.data);
6 if (msg.type === "notification") {
7 showToast(msg.data.subject);
8 }
9};
10
11// Keep-alive ping every 30s
12setInterval(() => ws.send(JSON.stringify({ type: "ping" })), 30000);
13
14// Mark a notification as read
15ws.send(JSON.stringify({
16 type: "mark_read",
17 notification_id: "ntf_01HX..."
18}));
TypeScript18 lines