API reference.
A single REST API for OTPs, transactional email, and WhatsApp notifications. Closed beta; access is by invitation. Base URL: https://api.noti5.app/v1.
Introduction
Every request is JSON over HTTPS. Authenticated with a site-scoped bearer token. All POSTs accept an idempotency key. Responses are spec-shape JSON; errors carry a stable code you can switch on.
Authentication
Pass your API key as a bearer token. Keys are generated in the dashboard under Sites → your site → API keys → Issue new key. Format: n5_live_… (production) or n5_test_… (test).
curl https://api.noti5.app/v1/site \
-H "Authorization: Bearer n5_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Key rotation: issue a new key, switch your application to it, then revoke the old one from the dashboard. There's no downtime — multiple active keys per site are fine.
Errors
Every error is a JSON object under error:
{
"error": {
"code": "recipient_suppressed",
"message": "This recipient is on the suppression list.",
"request_id": "req_a3f29b…",
"documentation_url": "https://docs.noti5.app/errors/recipient_suppressed"
}
}
| HTTP | Common codes |
|---|---|
401 | unauthenticated, invalid_api_key, api_key_revoked |
403 | site_suspended |
404 | message_not_found, otp_not_found |
409 | idempotency_conflict |
410 | otp_expired, otp_already_consumed |
422 | validation_failed, otp_invalid, recipient_suppressed |
429 | rate_limited, resend_cooldown, otp_max_attempts |
500 | server_error |
Rate limits
Default: 60 requests per minute per API key. Responses carry X-RateLimit-Limit and X-RateLimit-Remaining headers.
Idempotency
All POST endpoints accept an Idempotency-Key header. Identical key + identical body returns the original response within 24 hours. Different body with the same key returns 409 idempotency_conflict.
Sends
Send a single email or WhatsApp message.
POST /v1/notify
{
"event_type": "form.submission.ack",
"channel": "email",
"recipient": "enduser@example.com",
"template_code": "form_submission_ack",
"variables": {
"user_name": "Priya",
"brand_name": "Aban International School"
},
"metadata": { "form_id": "wpforms-123" }
}
Channels: email, whatsapp, auto (picks based on recipient shape). Response is 202 Accepted:
{
"message": {
"id": "msg_a3f29b1c4d5e6f7g",
"status": "queued",
"channel": "email",
"recipient": "enduser@example.com",
"queued_at": "2026-05-19T10:30:00Z"
}
}
Send the same event to multiple recipients. Up to 1000 per call.
{
"event_type": "form.submission.lead_notify",
"channel": "whatsapp",
"template_code": "form_submission_lead_notify",
"recipients": [
{ "recipient": "+919876543210", "variables": { "team_member": "Sash" } },
{ "recipient": "+919876543211", "variables": { "team_member": "Sriram" } }
],
"shared_variables": {
"brand_name": "Aban International School",
"lead_name": "Priya"
}
}
List messages. Query params: from, to, status, channel, event_type, recipient, limit (default 50, max 200), cursor.
Retrieve a single message with its full event timeline.
OTP
Generate and send a one-time password. The code itself is never returned — it's delivered to the recipient and verified separately.
{
"purpose": "form_verification",
"recipient": "+919876543210",
"channel": "whatsapp",
"fallback_channels": ["email"],
"fallback_recipient_email": "user@example.com",
"variables": { "brand_name": "Aban International School" },
"ttl_seconds": 600,
"code_length": 6
}
Returns 201 Created with otp.id, expires_at, max_attempts.
{ "otp_id": "otp_a3f29b1c4d5e6f7g", "code": "483921" }
On success: { "verified": true, "verified_at": "…" }. On failure: 422 with code = otp_invalid (with attempts_remaining) or 410 for otp_expired/otp_already_consumed, 429 for otp_max_attempts.
Re-issues a fresh code on the same OTP id. Cooldown: 30 seconds between resends — returns 429 resend_cooldown with a retry_after field if invoked too soon.
Site
Returns the resolved brand settings + list of templates available to this site. Used by the WP mu-plugin for self-discovery.
Mu-plugin pings periodically to refresh metadata (WP/PHP version, active plugins, etc.).
Privacy (DPDP)
Spec §15.2 #4 — scrub body/variables for all messages sent to a recipient on this account. Logged in the audit trail.
{ "recipient": "user@example.com" }
Spec §15.2 #5 — returns all messages sent to a recipient on this account, with whatever body content hasn't yet been scrubbed by retention.
Outbound webhooks (Noti5 → your site)
Configure a webhook_url + webhook_secret on your Site to receive structured events when message status changes.
POST https://your-site.com/your-webhook
X-Noti5-Event: message.delivered
X-Noti5-Delivery-Id: whd_…
X-Noti5-Signature: sha256=<HMAC-SHA256(body, webhook_secret)>
{
"event": "message.delivered",
"occurred_at": "2026-05-19T10:30:15Z",
"data": {
"message_id": "msg_a3f29b…",
"channel": "email",
"event_type": "form.submission.ack",
"recipient": "enduser@example.com",
"status": "delivered"
}
}
Expected response: 2xx within 10s. Otherwise retried with exponential backoff up to 8 attempts (~24h), then abandoned. Replay any failed delivery from the dashboard.
Questions? hello@noti5.app · abuse reports: abuse@noti5.app