Vault

HTTP API

Complete HTTP API reference for Herald notification endpoints.

Herald exposes a RESTful HTTP API through the Forge framework. When using the Forge extension, all routes are mounted under a configurable base path (default: /herald). The API supports providers, templates, template versions, sending notifications, delivery log, user inbox, preferences, and scoped configuration.

All endpoints accept and return JSON. TypeID strings are used as entity identifiers (e.g., hpvd_01h... for providers).

Send

Send Notification

Sends a notification on a single channel.

POST /herald/send

Request body:

{
  "app_id": "myapp",
  "channel": "email",
  "template": "welcome",
  "locale": "en",
  "to": ["alice@example.com"],
  "data": {
    "name": "Alice",
    "app_name": "My App"
  },
  "org_id": "org-acme",
  "user_id": "user-alice",
  "metadata": {
    "source": "signup"
  },
  "async": false
}
FieldTypeRequiredDescription
app_idstringYesApplication identifier
channelstringYesChannel type: email, sms, push, inapp
tostring[]YesRecipient addresses
templatestringNoTemplate slug (omit to use subject/body directly)
localestringNoLocale code for template version (defaults to config)
dataobjectNoTemplate variable data
subjectstringNoDirect subject (when not using a template)
bodystringNoDirect body (when not using a template)
org_idstringNoOrganization ID for scope resolution
user_idstringNoUser ID for preference checking and scope resolution
env_idstringNoEnvironment ID
metadataobjectNoUser-defined key-value metadata
asyncbooleanNoWhether to send asynchronously

Response (200):

{
  "message_id": "hmsg_01h9xz...",
  "status": "sent",
  "provider_id": "hpvd_01h9xz...",
  "error": ""
}

Multi-Channel Notify

Sends a notification across multiple channels using a template.

POST /herald/notify

Request body:

{
  "app_id": "myapp",
  "template": "order-confirmation",
  "channels": ["email", "inapp"],
  "to": ["alice@example.com"],
  "user_id": "user-alice",
  "data": {
    "name": "Alice",
    "order_id": "ORD-12345"
  }
}
FieldTypeRequiredDescription
app_idstringYesApplication identifier
templatestringYesTemplate slug
channelsstring[]YesChannel types to send on
tostring[]YesRecipient addresses
user_idstringNoUser ID for preferences and scope
localestringNoLocale code
dataobjectNoTemplate variable data
org_idstringNoOrganization ID
env_idstringNoEnvironment ID
metadataobjectNoUser-defined metadata
asyncbooleanNoWhether to send asynchronously

Response (200):

[
  {
    "message_id": "hmsg_01h9xz...",
    "status": "sent",
    "provider_id": "hpvd_01h9xz..."
  },
  {
    "message_id": "hmsg_01h9y0...",
    "status": "sent",
    "provider_id": "hpvd_01h9y0..."
  }
]

Providers

Create Provider

POST /herald/providers

Request body:

{
  "app_id": "myapp",
  "name": "SMTP Production",
  "channel": "email",
  "driver": "smtp",
  "credentials": {
    "host": "smtp.example.com",
    "port": "587",
    "username": "noreply@example.com",
    "password": "secret"
  },
  "settings": {
    "from": "noreply@example.com",
    "from_name": "My App"
  },
  "priority": 0,
  "enabled": true
}

Response (201):

{
  "id": "hpvd_01h9xz...",
  "app_id": "myapp",
  "name": "SMTP Production",
  "channel": "email",
  "driver": "smtp",
  "credentials": {"host": "smtp.example.com", "port": "587", "username": "noreply@example.com", "password": "secret"},
  "settings": {"from": "noreply@example.com", "from_name": "My App"},
  "priority": 0,
  "enabled": true,
  "created_at": "2024-01-15T10:30:00Z",
  "updated_at": "2024-01-15T10:30:00Z"
}

List Providers

GET /herald/providers?app_id=myapp&channel=email
ParameterTypeRequiredDescription
app_idqueryYesFilter by application
channelqueryNoFilter by channel type

Response (200): Array of provider objects.

Get Provider

GET /herald/providers/:id

Response (200): Single provider object.

Update Provider

PUT /herald/providers/:id

Request body (all fields optional):

{
  "name": "Updated Name",
  "credentials": {"host": "new-smtp.example.com"},
  "enabled": false
}

Response (200): Updated provider object.

Delete Provider

DELETE /herald/providers/:id

Response: 204 No Content.

Templates

Create Template

POST /herald/templates

Request body:

{
  "app_id": "myapp",
  "slug": "welcome",
  "name": "Welcome Email",
  "channel": "email",
  "category": "transactional",
  "variables": [
    {"name": "name", "type": "string", "required": true},
    {"name": "app_name", "type": "string", "required": false, "default": "My App"}
  ],
  "enabled": true
}

Response (201): Template object with generated id (htpl_...).

List Templates

GET /herald/templates?app_id=myapp&channel=email
ParameterTypeRequiredDescription
app_idqueryYesFilter by application
channelqueryNoFilter by channel type

Response (200): Array of template objects.

Get Template

GET /herald/templates/:id

Response (200): Template object with versions array populated.

Update Template

PUT /herald/templates/:id

Request body (all fields optional):

{
  "name": "Updated Welcome Email",
  "category": "auth",
  "enabled": false
}

Response (200): Updated template object.

Delete Template

DELETE /herald/templates/:id

Deletes the template and all its associated versions.

Response: 204 No Content.

Template Versions

Create Version

POST /herald/templates/:id/versions

Request body:

{
  "locale": "en",
  "subject": "Welcome to {{.app_name}}, {{.name}}!",
  "html": "<h1>Hello {{.name}}</h1><p>Welcome aboard!</p>",
  "text": "Hello {{.name}}, Welcome aboard!",
  "title": "Welcome"
}

Response (201): Version object with generated id (htpv_...).

List Versions

GET /herald/templates/:id/versions

Response (200): Array of version objects for the specified template.

Update Version

PUT /herald/templates/:id/versions/:versionId

Request body (all fields optional):

{
  "subject": "Updated subject line",
  "html": "<h1>Updated HTML</h1>",
  "active": false
}

Response (200): Updated version object.

Delete Version

DELETE /herald/templates/:id/versions/:versionId

Response: 204 No Content.

Messages

List Messages

GET /herald/messages?app_id=myapp&status=sent&channel=email&offset=0&limit=50
ParameterTypeRequiredDescription
app_idqueryYesFilter by application
channelqueryNoFilter by channel
statusqueryNoFilter by status: queued, sending, sent, failed, bounced, delivered
offsetqueryNoPagination offset (default: 0)
limitqueryNoPage size (default: 50)

Response (200): Array of message objects.

[
  {
    "id": "hmsg_01h9xz...",
    "app_id": "myapp",
    "template_id": "welcome",
    "provider_id": "hpvd_01h9xz...",
    "channel": "email",
    "recipient": "alice@example.com",
    "subject": "Welcome to My App, Alice!",
    "body": "Hello Alice, Welcome aboard!",
    "status": "sent",
    "error": "",
    "metadata": {"source": "signup"},
    "attempts": 1,
    "sent_at": "2024-01-15T10:30:01Z",
    "created_at": "2024-01-15T10:30:00Z"
  }
]

Get Message

GET /herald/messages/:id

Response (200): Single message object.

Inbox

List Notifications

GET /herald/inbox?app_id=myapp&user_id=user-alice&offset=0&limit=50
ParameterTypeRequiredDescription
app_idqueryYesApplication ID
user_idqueryYesUser ID
offsetqueryNoPagination offset (default: 0)
limitqueryNoPage size (default: 50)

Response (200): Array of notification objects.

[
  {
    "id": "hinb_01h9xz...",
    "app_id": "myapp",
    "user_id": "user-alice",
    "type": "welcome",
    "title": "Welcome aboard!",
    "body": "Thanks for signing up.",
    "action_url": "/getting-started",
    "read": false,
    "created_at": "2024-01-15T10:30:00Z"
  }
]

Unread Count

GET /herald/inbox/unread/count?app_id=myapp&user_id=user-alice

Response (200):

{
  "count": 3
}

Mark Read

PUT /herald/inbox/:id/read

Marks a single notification as read.

Response: 204 No Content.

Mark All Read

PUT /herald/inbox/read-all?app_id=myapp&user_id=user-alice

Marks all unread notifications for the user as read.

Response: 204 No Content.

Delete Notification

DELETE /herald/inbox/:id

Response: 204 No Content.

Preferences

Get Preferences

GET /herald/preferences?app_id=myapp&user_id=user-alice

Response (200):

{
  "id": "hprf_01h9xz...",
  "app_id": "myapp",
  "user_id": "user-alice",
  "overrides": {
    "marketing-weekly": {
      "email": false,
      "push": true
    },
    "product-updates": {
      "email": true,
      "push": true,
      "sms": false
    }
  },
  "created_at": "2024-01-15T10:30:00Z",
  "updated_at": "2024-01-15T10:30:00Z"
}

Update Preferences

PUT /herald/preferences

Request body:

{
  "app_id": "myapp",
  "user_id": "user-alice",
  "overrides": {
    "marketing-weekly": {
      "email": false,
      "push": true
    }
  }
}

The overrides map keys are template slugs. Each value is a ChannelPreference object with optional boolean fields for email, sms, push, and inapp. A false value means the user opts out of that channel for that template.

Response (200): Updated preference object.

Scoped Configuration

List Configs

GET /herald/config?app_id=myapp

Returns all scoped configurations (app, org, user) for an application.

Response (200):

[
  {
    "id": "hscf_01h9xz...",
    "app_id": "myapp",
    "scope": "app",
    "scope_id": "myapp",
    "email_provider_id": "hpvd_01h9xz...",
    "from_email": "noreply@myapp.com",
    "from_name": "My App",
    "default_locale": "en",
    "created_at": "2024-01-15T10:30:00Z",
    "updated_at": "2024-01-15T10:30:00Z"
  },
  {
    "id": "hscf_01h9y0...",
    "app_id": "myapp",
    "scope": "org",
    "scope_id": "org-acme",
    "email_provider_id": "hpvd_01h9y0...",
    "from_email": "noreply@acme.com",
    "from_name": "Acme Corp"
  }
]

Set App Config

PUT /herald/config/app

Request body:

{
  "app_id": "myapp",
  "email_provider_id": "hpvd_01h9xz...",
  "sms_provider_id": "hpvd_01h9y0...",
  "push_provider_id": "",
  "from_email": "noreply@myapp.com",
  "from_name": "My App",
  "from_phone": "+15551234567",
  "default_locale": "en"
}

Response (200): Scoped config object.

Set Org Config

PUT /herald/config/org/:orgId

Request body:

{
  "app_id": "myapp",
  "email_provider_id": "hpvd_01h9y0...",
  "from_email": "noreply@acme.com",
  "from_name": "Acme Corp"
}

All provider ID and sender fields are optional -- only set fields are applied.

Response (200): Scoped config object.

Set User Config

PUT /herald/config/user/:userId

Request body:

{
  "app_id": "myapp",
  "from_name": "Personal Sender",
  "default_locale": "fr"
}

Response (200): Scoped config object.

Delete Org Config

DELETE /herald/config/org/:orgId?app_id=myapp

Response: 204 No Content.

Delete User Config

DELETE /herald/config/user/:userId?app_id=myapp

Response: 204 No Content.

Error Responses

All error responses follow the Forge framework's standard error format:

{
  "error": {
    "code": 400,
    "message": "invalid provider ID"
  }
}

Common Error Codes

StatusMeaning
400Bad request -- invalid ID format or missing required fields
404Entity not found
409Conflict -- duplicate slug/channel/locale combination
500Internal server error

Route Summary

MethodPathOperation
POST/herald/sendSend single notification
POST/herald/notifyMulti-channel notify
POST/herald/providersCreate provider
GET/herald/providersList providers
GET/herald/providers/:idGet provider
PUT/herald/providers/:idUpdate provider
DELETE/herald/providers/:idDelete provider
POST/herald/templatesCreate template
GET/herald/templatesList templates
GET/herald/templates/:idGet template
PUT/herald/templates/:idUpdate template
DELETE/herald/templates/:idDelete template
POST/herald/templates/:id/versionsCreate version
GET/herald/templates/:id/versionsList versions
PUT/herald/templates/:id/versions/:versionIdUpdate version
DELETE/herald/templates/:id/versions/:versionIdDelete version
GET/herald/messagesList messages
GET/herald/messages/:idGet message
GET/herald/inboxList inbox
GET/herald/inbox/unread/countUnread count
PUT/herald/inbox/:id/readMark read
PUT/herald/inbox/read-allMark all read
DELETE/herald/inbox/:idDelete notification
GET/herald/preferencesGet preferences
PUT/herald/preferencesUpdate preferences
GET/herald/configList configs
PUT/herald/config/appSet app config
PUT/herald/config/org/:orgIdSet org config
PUT/herald/config/user/:userIdSet user config
DELETE/herald/config/org/:orgIdDelete org config
DELETE/herald/config/user/:userIdDelete user config

On this page