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 fromexpo-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
- Create your app from the Add App screen in the NotiPilot dashboard.
- In the Expo Project ID field, enter your EAS project ID (a UUID, found at
app.json→extra.eas.projectId). - 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.)
- The
app_idyour mobile app sends to the API must exactly match the Expo Project ID entered in the dashboard. If you send an unregisteredapp_id, the API returns404 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-deviceand/api/identify-devicepaths behave exactly like v1. Use/api/v1for 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
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
{
"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 |
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" }
}'
{ "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.
{ "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
attributesfield 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_idto 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:
{
"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.
8. Recommended flow
- The app launches →
device_uidis read (or generated if missing). - Notification permission is requested (required on Android 13+ and iOS).
- If permission is granted, the push token is retrieved →
register-deviceis called (with locale/country/city). - The user signs in →
identify-deviceis called (withexternal_id). - The user signs out →
register-deviceis called with"external_id": null. - If the push token is refreshed (
onNewToken/ token listener) →register-deviceis 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:
{ "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. |