Developers · API v1.0.0

NotiPilot Device API — Developer Documentation

API version: 1.0.0 · Base path: /api/v1

NotiPilot lets you register the devices running your mobile app, group them into segments, and send them push notifications from the NotiPilot dashboard. Your mobile app only talks to two endpoints:

Endpoint When to call it
POST /api/v1/register-device After notification permission is granted, on every app launch, and whenever the push token changes
POST /api/v1/identify-device When the user signs in (to link the device to your own user ID)

Notifications are sent from the NotiPilot dashboard; your mobile app never needs to send anything itself.

Platform guides

Platform Who it's for
Expo Expo apps (managed workflow / development builds)
React Native Bare / CLI React Native projects
Flutter Flutter apps (Android + iOS)
Ionic Ionic + Capacitor apps
Firebase Projects already using Firebase Cloud Messaging (any stack)
Shopify Teams with a mobile app for their Shopify store (customer matching, e-commerce segments)
Android Native Android — Java
Kotlin Native Android — Kotlin (Views / Jetpack Compose)
iOS Native iOS — Apple Developer / APNs setup and Objective-C
Swift Native iOS — Swift (UIKit / SwiftUI)

Version support and versioning policy


1. How it works

Mobile app ──(Expo Push Token + device info)──▶ NotiPilot API
                                                              │
NotiPilot dashboard ──(title, message, segment)──▶ Expo Push Service ──▶ FCM (Android) / APNs (iOS) ──▶ Device

NotiPilot 1.0.0 uses the Expo Push Service for delivery. This means:

  • Every device needs an Expo Push Token (ExponentPushToken[...]). In Expo and React Native apps, you get this token directly from expo-notifications. In native Android/iOS apps, the device's FCM/APNs token is converted into an Expo token (each guide walks you through this step by step).
  • Your app needs an Expo (EAS) project with FCM V1 / APNs credentials uploaded to it (eas credentials).
  • When adding your app to the NotiPilot dashboard, you enter its Expo Project ID and an Expo Access Token.

You can also send raw FCM or APNs tokens to the API (provider: "fcm" | "apns"). These devices are registered and appear in segments, but in version 1.0.0 notifications are only delivered to devices with an Expo token. Direct FCM/APNs delivery is on the roadmap.

2. Before you begin

  1. Create your app from the Add App screen in the NotiPilot dashboard.
  2. In the Expo Project ID field, enter your EAS project ID (a UUID, found at app.json → extra.eas.projectId).
  3. In the Expo Access Token field, enter a token created at expo.dev → Access Tokens. (Required if "Enhanced Security for Push Notifications" is enabled for your Expo project.)
  4. The app_id your mobile app sends to the API must exactly match the Expo Project ID entered in the dashboard. If you send an unregistered app_id, the API returns 404 unknown_app.

3. General rules

Topic Value
Base URL https://app.notipilot.com/api/v1
Protocol HTTPS only
Request body Content-Type: application/json, UTF-8
Authentication Not required. Because the Device API is embedded in your mobile app, it doesn't use a secret key; app_id is not a secret. Authorization is based on the app_id being registered in the dashboard.
Rate limit 300 requests per minute per IP. When exceeded, the API returns 429 with a Retry-After header.
Version header Every response includes the X-NotiPilot-API-Version: 1.0.0 header.

For backward compatibility, the unversioned /api/register-device and /api/identify-device paths behave exactly like v1. Use /api/v1 for all new integrations.

4. Endpoints

4.1 POST /api/v1/register-device

Registers or updates a device (upsert). Devices are matched on the app_id + device_uid pair, so it's safe to call this repeatedly for the same device.

Request fields

Field Type Required Description
app_id string (≤255) ✅ The Expo Project ID from the dashboard
device_uid string (≤191) ✅ A persistent, unique device identifier per installation (see Device ID)
token string (≤512) ✅ Push token. Expo token format: ExponentPushToken[...]
platform "android" | "ios" | "web" ✅ Device platform
provider "expo" | "fcm" | "apns" – Auto-detected from the token format if omitted
external_id string (≤191) | null – The user ID in your own system. Sending null removes the link (sign-out scenario)
attributes object – Segmentation attributes (see Attributes)
tags string[] – Free-form tags (up to 50, each ≤64 characters)
consent_marketing boolean – Marketing notification consent. Default: true

Example request

Terminal
curl -X POST "https://app.notipilot.com/api/v1/register-device" \
  -H "Content-Type: application/json" \
  -d '{
    "app_id": "1c2d3e4f-5a6b-7c8d-9e0f-112233445566",
    "device_uid": "8f14e45f-ceea-467a-9575-0b3f5c6b2a11",
    "token": "ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]",
    "platform": "android",
    "attributes": {
      "locale": "tr",
      "country": "TR",
      "city": "Istanbul",
      "app_version": "2.4.0"
    },
    "tags": ["beta"]
  }'

Success response — 200 OK

JSON
{
  "success": true,
  "message": "Device registered successfully",
  "device_id": 1234,
  "expo_token_id": 567,
  "provider": "expo"
}

4.2 POST /api/v1/identify-device

Links the device to your own user ID (external_id) and optionally updates attributes/tags. Call it when the user signs in.

Field Type Required Description
app_id string ✅ The Expo Project ID from the dashboard
device_uid string ✅ The same value you used for register-device
external_id string (≤191) ✅ Your user ID
attributes object – Merged with existing attributes
tags string[] – If sent, replaces the existing tags
consent_marketing boolean –
token, platform string – If the device hasn't been registered yet, sending both performs a full registration
Terminal
curl -X POST "https://app.notipilot.com/api/v1/identify-device" \
  -H "Content-Type: application/json" \
  -d '{
    "app_id": "1c2d3e4f-5a6b-7c8d-9e0f-112233445566",
    "device_uid": "8f14e45f-ceea-467a-9575-0b3f5c6b2a11",
    "external_id": "user_98765",
    "attributes": { "gender": "female" }
  }'
JSON
{ "success": true, "message": "Device identified", "device_id": 1234 }

If the device isn't registered yet and no token is sent, the device is created without a token ("auto_registered": true). It won't receive notifications until a token is sent via register-device.

4.3 GET /api/v1/version

Health check and version information.

JSON
{ "success": true, "api_version": "1.0.0", "min_supported_version": "1.0.0" }

5. Attributes and segments

The NotiPilot dashboard automatically builds segments from device attributes (e.g. "Turkish speakers", "Istanbul", "Turkey + Istanbul"). Location-based segments are only created for devices that send country and city. If you don't send these fields, the device is only included in "All users" campaigns.

Standard keys (recognized and labeled by the dashboard):

Key Format Example
locale ISO 639-1 language code (lowercase) "tr", "en", "de"
country ISO 3166-1 alpha-2 country code (uppercase) "TR", "DE", "NL"
city City name, spelled consistently "Istanbul", "Ankara"
gender male | female | other "female"

You can also add your own keys (app_version, plan, favorite_team …).

Rules

  • Up to 50 keys. Keys may contain A-Z a-z 0-9 _ . - and be up to 64 characters long.
  • Values can be strings, numbers, booleans, or null. String values can be up to 255 characters.
  • Don't use , or : in values (they're used as separators in segment keys).
  • Merging: The attributes you send are merged with existing values; keys you omit are preserved. To delete a key, send its value as null.
  • If you omit the attributes field entirely, the stored attributes stay unchanged.
  • Don't put personal data such as email addresses, phone numbers, or national ID numbers in attributes. Use external_id to identify the user.

Where do I get city/country data? The device's region setting (locale → country) doesn't require location permission and is always available. For the city, you can use the city from the user's profile (the most reliable option), reverse geocoding if the user granted location permission, or IP-based geolocation on your own backend. Even without location permission, we recommend sending at least country and locale.

6. Generating the device ID

For the device_uid field:

  • Generate a UUID v4 on first launch and store it persistently (Android: SharedPreferences/DataStore, iOS: Keychain, Expo: expo-secure-store).
  • It should not change across app updates; it's expected to change when the app is uninstalled and reinstalled.
  • Do not use the IMEI, MAC address, or advertising ID (IDFA/GAID).

7. Error responses

All error responses share the same structure:

JSON
{
  "success": false,
  "error": "validation_failed",
  "message": "Validation failed",
  "errors": { "platform": "Must be one of: android, ios, web" }
}
HTTP error Meaning What to do
400 invalid_json The body is not valid JSON Fix the request; don't retry
400 validation_failed One or more fields are invalid; details are in errors Fix the request; don't retry
404 unknown_app The app_id is not registered in NotiPilot Check the Expo Project ID in the dashboard
429 rate_limited Rate limit exceeded Retry after retry_after seconds
500 server_error Server-side error Retry with exponential backoff

Recommended client-side approach: retry only on 429 and 5xx responses (and network errors); log 4xx errors.

  1. The app launches → device_uid is read (or generated if missing).
  2. Notification permission is requested (required on Android 13+ and iOS).
  3. If permission is granted, the push token is retrieved → register-device is called (with locale/country/city).
  4. The user signs in → identify-device is called (with external_id).
  5. The user signs out → register-device is called with "external_id": null.
  6. If the push token is refreshed (onNewToken / token listener) → register-device is called again.

9. Notification content

A notification sent from the dashboard consists of title, body, and an optional JSON data field. The data payload is delivered to your app; use it for scenarios like deep linking, for example:

JSON
{ "screen": "product", "product_id": "12345" }

On Android, notifications are sent to the default channel (channelId: "default"); create a notification channel with this ID in your app.

10. Changelog

Version Date Changes
1.0.0 2026-09 First stable release. /api/v1 base path, version endpoint, standard error format (error codes), rate limiting, app_id validation, attribute merge behavior.