REST API reference

Base URL https://api.notibase.com. Authenticate with Authorization: Bearer <key>. Two key scopes exist — read the key model before integrating.

Conventions

IdempotencySend an Idempotency-Key header on POST /v1/messages. Same key → same message returned with "replayed": true, never a second send.
Rate limitsPer key, plan-based (free: 10 rps server / 50 rps client). Exceeding returns 429.
QuotasMonthly message quota per plan; exceeded sends return 429 with an upgrade note.
Errors400 validation (with issues) · 401 bad/missing key · 403 wrong scope / suspended app / failed identity check · 404 not yours or missing · 429 limits · 5xx ours (raw details are logged server-side, never leaked).

Endpoints

EndpointKeyPurpose
POST /v1/messagesskSend now, or schedule with send_at (ISO-8601, ≥30s ahead)
GET /v1/messages/:id/deliveriesskPer-device delivery timeline, raw provider responses included
POST /v1/usersskUpsert a user profile by external_id; custom properties auto-catalog
GET /v1/propertiesskThe property catalog (drives what's filterable)
POST /v1/segmentsskSave a named filter (validated against the catalog)
POST /v1/segments/previewskLive count for any filter
POST /v1/devicesck / skRegister a device (web subscription JSON, APNs or FCM token)
GET /v1/devicessk onlyList devices — client keys get a structural 403
POST /v1/identifyck / skAlias device → user; HMAC-verified when an identify secret is set
POST /v1/eventsck / skTrack custom events, single or {"events":[…]} batch ≤ 50
GET /v1/channelspublicChannels this Notibase supports

Send a message

POST /v1/messages
{
  "audience": { "all": true },
  // or: { "device_ids": ["…"] } | { "segment_id": "…" } | { "filter": <AST> }
  "content": {
    "title": "Back in stock: {{properties.wishlist_item}}",
    "body":  "Grab it before it goes again.",
    "url":   "https://shop.example/item"
  },
  "send_at": "2026-08-21T10:00:00+05:45"   // optional — omit to send now
}

Response (immediate send): {"id","status":"sent","replayed":false,"report":{"accepted","sent","failed","skipped"}}. Scheduled: {"id","status":"scheduled","run_at"}.

Per-channel content: top-level fields apply everywhere; override per channel with "content": {"webpush": {…}, "apns": {…}, "fcm": {…}}. Personalization: {{first_name | default: 'there'}} and any {{properties.x}}.

Register a device

POST /v1/devices          // Bearer ck_live_... (from your app)
{ "platform": "web" | "ios" | "android",
  "token": "<push subscription JSON | APNs hex token | FCM token>",
  "locale": "en-US", "timezone": "Asia/Kathmandu" }

Track events

POST /v1/events
{ "events": [
  { "name": "purchase", "properties": { "amount": 9.99, "currency": "USD" }, "device_id": "…" },
  { "name": "session_start" } ] }
Reserved names install, session_start, purchase (with amount/currency) power upcoming revenue & attribution reports — use them from day one and the reports will be retroactive.

Upsert a user

POST /v1/users
{ "external_id": "user-42", "first_name": "Aasha", "country": "NP",
  "language": "ne", "timezone": "Asia/Kathmandu",
  "properties": { "plan": "pro", "lifetime_value": 420.5 },
  "device_id": "<optional: alias an anonymous device>" }

New property keys auto-register with inferred types; a later type conflict is reported in property_conflicts, never silently coerced.