Core APIsDevices

Devices API

Register and manage device tokens for push notifications

Overview

The Devices API allows you to register and manage device tokens for push notifications. When users install your mobile app, you register their device token with Flameup to enable push messaging. Web push is supported alongside iOS and Android.

Required Permission: register and unregister accept devices:register or devices:write. Listing devices accepts devices:read or devices:write. devices:register deliberately cannot list — see the table below.
Grant these scopes when you create the key via the API. The dashboard's key editor does not yet show a Devices category, so keys created in the UI cannot get them — create the key through the API keys endpoint instead. The devices:* wildcard and full access * also work.
ScopeRegister / UnregisterList devices
devices:registerYesNo
devices:writeYesYes
devices:readNoYes
Use devices:register for keys embedded in a mobile app. It lets the app enrol the device it is running on without being able to enumerate every device token in the workspace — an isolation devices:write cannot provide. See the Flutter guide.

Supported Platforms

Flameup supports push notifications on three platforms:

Device Token Object

A device token in Flameup has the following structure:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "workspace_id": "866911cd-f4a2-4aff-99b1-3ba383f33c3e",
  "person_id": "550e8400-e29b-41d4-a716-446655440001",
  "device_token": "fMhT8K9x...(FCM token)",
  "platform": "ios",
  "device_name": "iPhone 15 Pro",
  "device_model": "iPhone16,1",
  "os_version": "17.2",
  "app_version": "2.1.0",
  "is_active": true,
  "is_valid": true,
  "last_used_at": "2024-01-20T14:45:00Z",
  "registration_count": 3,
  "created_at": "2024-01-15T10:30:00Z",
  "updated_at": "2024-01-20T14:45:00Z"
}

Request Fields

These are the fields you send when registering a device. Note that they are not the same as the response fields shown above.

FieldTypeRequiredDescription
tokenstringYesFCM/APNs token from the device. Must be 10–4096 characters
platformstringYesios, android or web
device_namestringNoHuman-readable device name
device_modelstringNoDevice model identifier
os_versionstringNoOperating system version
app_versionstringNoYour app's version
The token field name differs between request and response. You send token, but the API returns it as device_token. Sending device_token in a registration request fails with 400 — the field is ignored and the required token is then missing.
The identifier in the URL path is your own user id — the same userId you send to /identify and /track. It is not the Flameup person UUID. Pass it in the URL path; an identifier in the request body is ignored.

Register a Device

Register a device token to enable push notifications for a user.

Endpoint: POST /api/v1/people/{external_id}/devices

The workspace is automatically determined from your API key, so you don't need to include it in the URL.
// After obtaining FCM token in your iOS app
async function registerIOSDevice(externalId, fcmToken, deviceInfo) {
  const response = await fetch(
    `https://api.flameup.ai/api/v1/people/${externalId}/devices`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${API_KEY}`
      },
      body: JSON.stringify({
        token: fcmToken,
        platform: 'ios',
        device_name: deviceInfo.name,        // e.g., "John's iPhone"
        device_model: deviceInfo.model,      // e.g., "iPhone16,1"
        os_version: deviceInfo.osVersion,    // e.g., "17.2"
        app_version: deviceInfo.appVersion   // e.g., "2.1.0"
      })
    }
  );

  return response.json();
}

Swift Example (getting the FCM token):

import FirebaseMessaging

Messaging.messaging().token { token, error in
    if let token = token {
        // Send this token to your backend
        registerDevice(fcmToken: token)
    }
}
curl -X POST "https://api.flameup.ai/api/v1/people/{external_id}/devices" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your_api_key" \
  -d '{
    "token": "fMhT8K9x...your_fcm_token",
    "platform": "ios",
    "device_name": "iPhone 15 Pro",
    "device_model": "iPhone16,1",
    "os_version": "17.2",
    "app_version": "2.1.0"
  }'

Response (200 OK):

{
  "success": true,
  "device_id": "550e8400-e29b-41d4-a716-446655440000",
  "message": "Device registered successfully"
}

When the external_id is not yet a known person, registration creates one — a minimal profile with just that identifier — and the response then carries "person_created": true. Its absence means the person already existed; seeing it on an identifier you believed established means you found a typo, not a new user.

Registration returns only the device_id, not the full device object. Use the List Devices endpoint to retrieve full device details.

Registration moves the token

Registration is an atomic upsert keyed on (workspace, token). If the token is already registered — to anyone — the existing row is reassigned to the person in the URL, reactivated (is_active and is_valid back to true), and its registration_count bumped. This is the mechanic the login/logout flow relies on: when a different user logs in on the same handset, re-registering the device's token rebinds the device to them, and the previous user stops receiving pushes on it.

Registration errors:

StatusWhen
400token outside 10–4096 chars, or platform not ios/android/web
404The external_id is whitespace-only, or equals an existing person's Flameup UUID — deliberately refused so devices never bind to raw UUIDs
409Cannot register device for inactive person

Note what is not on that list: an unknown external_id is not an error — registration creates the person, as above.

List Devices

Retrieve all devices for a person.

Endpoint: GET /api/v1/people/{external_id}/devices

This endpoint returns all devices for the person. It takes no query parameters and is not paginated.

// Get all devices for a user
const response = await fetch(
  `https://api.flameup.ai/api/v1/people/${externalId}/devices`,
  {
    headers: {
      'Authorization': `Bearer ${API_KEY}`
    }
  }
);

const { devices } = await response.json();
console.log(`User has ${devices.length} registered devices`);

Response (200 OK):

{
  "success": true,
  "devices": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "workspace_id": "866911cd-f4a2-4aff-99b1-3ba383f33c3e",
      "person_id": "550e8400-e29b-41d4-a716-446655440001",
      "device_token": "fMhT8K9x...",
      "platform": "ios",
      "is_active": true,
      "is_valid": true,
      "last_used_at": "2024-01-20T14:45:00Z",
      "registration_count": 3,
      "created_at": "2024-01-15T10:30:00Z",
      "updated_at": "2024-01-20T14:45:00Z"
    }
  ],
  "count": 1
}

The count field is the number of devices returned. Devices are ordered by last_used_at (most recently used first), then by created_at descending.

Update a Device

Update device information (e.g., when the app version changes).

To update device info, re-register the device with the new information. The device will be matched by token and updated.

Simply call the register endpoint again with the same token and updated fields:

// Re-register with updated info
const response = await fetch(
  `https://api.flameup.ai/api/v1/people/${externalId}/devices`,
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${API_KEY}`
    },
    body: JSON.stringify({
      token: existingToken,
      platform: 'ios',
      app_version: '2.2.0',
      os_version: '17.3'
    })
  }
);

Delete a Device

Stop pushes to a device token (e.g., when user logs out or uninstalls).

Endpoint: DELETE /api/v1/people/{external_id}/devices/{token}

Delete is a soft invalidate: the row is kept with is_active and is_valid set false, it still appears in List Devices, and re-registering the same token reactivates it. That is what makes logout → login on the same handset work without a fresh FCM token.

const response = await fetch(
  `https://api.flameup.ai/api/v1/people/${externalId}/devices/${encodeURIComponent(deviceToken)}`,
  {
    method: 'DELETE',
    headers: {
      'Authorization': `Bearer ${API_KEY}`
    }
  }
);

if (response.ok) {
  console.log('Device unregistered');
}

Token Lifecycle

Device tokens have a lifecycle that Flameup manages automatically:

Registration

Token is registered when user enables notifications or installs the app.

  • is_active: true
  • is_valid: true

Active Use

Token is used for push delivery. last_used_at is updated with each use.

Token Refresh

FCM may issue a new token. Re-register with the new token. Deduplication is by token, not by device: the rotated token inserts a new row, and the old token's row stays active until a delivery failure invalidates it or cleanup reaps it. FCM stops delivering to rotated-away tokens, so the stale row is harmless — but expect to see both in List Devices for a while.

Invalidation

If push delivery fails (uninstall, revoked permissions), token is marked:

  • is_valid: false
  • is_active: false

Handling Token Refresh

FCM tokens can change. Handle token refresh in your app:

extension AppDelegate: MessagingDelegate {
    func messaging(_ messaging: Messaging,
                   didReceiveRegistrationToken fcmToken: String?) {
        guard let token = fcmToken else { return }

        // Re-register with Flameup
        FlameupAPI.registerDevice(token: token)
    }
}

Best Practices

Register the device token after user login to associate it with the correct person. Register with your own user id — the same identifier you send to /identify — never the Flameup UUID, which the register route refuses:

async function onUserLogin(userId) {
  const fcmToken = await getFCMToken();
  await flare.identifyUser(userId);
  await flare.registerDevice(userId, fcmToken);
}

If the token was previously registered to another user on this handset, registration rebinds it — see Registration moves the token.

Troubleshooting

Push not working? Check these common issues:
IssueSolution
Token not registeredCheck the token is valid (10–4096 chars) and the external_id is not whitespace or a Flameup UUID. An unknown id is not the cause — registration creates the person
Wrong platformEnsure platform matches the device (ios/android/web)
Invalid tokenToken may have expired; request a fresh token from FCM
User not receivingCheck is_active and is_valid are both true
Notification blockedUser may have disabled notifications in device settings
403 Insufficient permissionsThe API key lacks the scope for that route (devices:register/devices:write to register, devices:read/devices:write to list). Keys created in the dashboard cannot have any of them yet — create one via the API keys endpoint
404 on the request itselfYou are calling a workspace-scoped path. The API-key route is not workspace-scoped; see below

Using the wrong request shape

The most common failure is a request built against the older workspace-scoped shape. It returns 404 (no such route) rather than a helpful validation error:

# WRONG — this route does not exist for API keys
curl -X POST "https://api.flameup.ai/api/v1/workspaces/{workspace_id}/devices" \
  -H "Authorization: Bearer {API_KEY}" \
  -d '{"person_id": "...", "device_token": "...", "platform": "android"}'

Three things differ from the correct call:

WrongCorrect
Workspace in the URL: /workspaces/{workspace_id}/devicesNo workspace in the URL: /people/{external_id}/devices. The workspace is resolved from the API key
person_id in the JSON bodyYour external_id in the URL path. An identifier in the body is ignored
device_token fieldtoken field. Required, 10–4096 characters
# CORRECT
curl -X POST "https://api.flameup.ai/api/v1/people/user_12345/devices" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer {API_KEY}" \
  -d '{
    "token": "eM_WXDy1SCWh9Op_tY46B3:APA91b...",
    "platform": "android",
    "device_name": "Pixel 7",
    "device_model": "GVU6C",
    "os_version": "15",
    "app_version": "2.2.39"
  }'
A workspace-scoped device path (/api/v1/workspaces/{workspace_id}/people/{person_id}/devices) does exist, but it is authenticated with a dashboard session, not an API key — and it addresses the person by their Flameup UUID, not by external_id. A non-UUID person_id there returns 400 Invalid person ID. API-key integrations should always use the workspace-agnostic /api/v1/people/{external_id}/devices form shown above.