API Reference
Complete reference for all Orqestra HTTP endpoints.
Authentication
All tenant API calls require the x-api-key header:
x-api-key: YOUR_TENANT_API_KEY
Your API key is shown in the Admin UI. Never expose it in client-side code.
Trigger an event
POST /api/v1/events/trigger
The primary endpoint. Fires a notification event that Orqestra resolves against your configured templates and dispatches.
Headers
| Header | Required | Description |
|---|---|---|
Content-Type | Yes | application/json |
x-api-key | Yes | Your tenant API key |
X-Orqestra-Signature | Conditional | HMAC-SHA256 hex of raw body — required if webhook secret is configured |
X-Idempotency-Key | No | Custom deduplication key (24-hour window) |
X-Trace-Id | No | Trace ID for distributed tracing |
Request body
{
"eventType": "order.confirmed",
"eventId": "evt_optional_custom_id",
"channels": ["EMAIL"],
"payload": {
"userId": "uuid-of-recipient",
"recipientEmail": "user@example.com",
"recipientPhone": "+254712345678"
},
"variables": {
"title": "Order Confirmed",
"orderNumber": "ORD-2026-1234",
"amount": "$49.99",
"customField": "Any key here becomes {{customField}} in templates"
}
}
payload holds routing identity (who receives the notification). variables holds template data (what gets rendered). Keep them separate — only the three payload fields are recognised by the engine; everything else belongs in variables.
| Field | Type | Required | Notes |
|---|---|---|---|
eventType | string | Yes | Must match a configured template's event type |
eventId | string | No | Auto-generated UUID if omitted |
channels | string[] | No | Restricts this dispatch: "EMAIL", "SMS", "PUSH" |
payload.userId | string | Yes | Recipient's ID in your system |
payload.recipientEmail | string | No | Required for email channel |
payload.recipientPhone | string | No | Required for SMS (E.164 format) |
variables | object | No | Arbitrary key/value pairs. Every key is available as {{fieldName}} in template content and subject lines. For global.* event types, include at least title and message. |
Success response (200)
{
"success": true,
"message": "Event order.confirmed dispatched securely for YourTenantName"
}
Error responses
| Status | Error | Common cause |
|---|---|---|
401 | Unauthorized | Invalid/missing API key, invalid HMAC signature, inactive tenant |
422 | Validation Error | Missing eventType, payload, or payload.userId |
429 | Too Many Requests | Rate limit exceeded — read Retry-After header |
500 | Internal Server Error | Kafka unavailable, database error |
{
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded for tenant YourApp. Limit: 100/min",
"retryAfter": 45
}
Request a real-time token
POST /api/v1/auth/realtime-token
Issues a Centrifugo connection token for WebSocket push notifications.
Headers
| Header | Required |
|---|---|
Content-Type | Yes (application/json) |
x-api-key | Yes |
Request body
{ "userId": "uuid-of-logged-in-user" }
Response
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"channels": [
"global_system#uuid-of-logged-in-user",
"orders#uuid-of-logged-in-user"
]
}
Tokens expire after 2 hours. Refresh before expiry using Centrifugo's getToken callback.
Fetch notification history
GET /api/v1/notifications/{tenantId}/{userId}
Returns the user's in-app notification history (Push channel only).
Headers
| Header | Required |
|---|---|
x-api-key | Yes |
Query parameters
| Param | Default | Description |
|---|---|---|
limit | 50 | Max items to return |
offset | 0 | Pagination offset |
Response
{
"success": true,
"data": [
{
"id": "notif-uuid",
"type": "order.confirmed",
"title": "Order Confirmed",
"body": "Your order ORD-2026-1234 has been confirmed.",
"status": "UNREAD",
"created_at": "2026-04-22T12:00:00Z"
}
]
}
Mark notification as read
PUT /api/v1/notifications/{tenantId}/{userId}/{notificationId}/read
Headers
| Header | Required |
|---|---|
x-api-key | Yes |
Response
{ "success": true }
Rate limits
| Limit | Scope | Behaviour |
|---|---|---|
| Per-minute limit | Per tenant | Sliding window counter — configurable at provisioning time |
| Daily cap | Per tenant | Resets at midnight UTC |
When exceeded, the response includes:
- HTTP
429 Too Many Requests - JSON body with
retryAfter(seconds) Retry-Afterheader
Pre-production checklist
Before going live:
- API Key is stored as an environment variable — never in client-side code
- Webhook HMAC signing is enabled and tested
- Error responses (401, 422, 429) are handled gracefully with retries
- Frontend requests a new Centrifugo token on login and refreshes before expiry
- Template variables in
variablesmatch what your templates expect ({{fieldName}}→variables.fieldName) - Idempotency is tested: the same event twice returns the same response without duplicate delivery
- Rate limit behaviour is understood and your backend respects
Retry-Afterheaders