List Contact Devices
List every device a contact has registered, including revoked and expired ones.
Use this to answer support questions — "why didn't this customer get the notification?" — by checking device status, permission state, and whether the Arsel SDK is embedded.
Endpoint
GET /push/contacts/{contactId}/devices
Returns: 200 OK
Path Parameters
| Parameter | Type | Description |
|---|---|---|
contactId | string | Arsel contact UUID |
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
platform | string | all | Filter to android, ios, or web. |
status | string | all | Filter to ACTIVE, EXPIRED, REVOKED, or FAILED. Defaults to every status, revoked and expired included. |
limit | integer | 20 | Items per page, 1–100. |
after | string | — | Return items after this id (exclusive). |
before | string | — | Return items before this id (exclusive). |
See Pagination for how cursor paging works.
Headers
| Header | Value | Required |
|---|---|---|
Authorization | Bearer <your-api-key> | Yes |
Response
{
"object": "list",
"has_more": false,
"data": [
{
"id": "01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a",
"installation_id": "install-abc123",
"platform": "android",
"vendor": "fcm",
"status": "ACTIVE",
"revoke_reason": null,
"device_token_masked": "fzXn…J3x9",
"enablement_status": "AUTHORIZED",
"sdk_enabled": true,
"sdk_version": "android/1.2.0",
"app_version": "3.4.1",
"device_model": "Pixel 8",
"device_timezone": "Asia/Riyadh",
"device_locale": "ar-SA",
"last_used_at": "2026-06-01T12:00:00.000Z",
"last_seen_at": "2026-06-01T12:00:00.000Z",
"created_at": "2026-05-20T09:14:00.000Z"
}
]
}
| Field | Type | Description |
|---|---|---|
id | string | Device ID (UUIDv7) |
installation_id | string | The natural key. Correlate on this across every push route. |
platform | string | android, ios, web |
vendor | string | fcm, apns, web_push |
status | string | ACTIVE, EXPIRED, REVOKED, FAILED |
revoke_reason | string | null | user_opt_out, contact_opt_out, dead_token, or admin |
device_token_masked | string | null | First and last few characters only |
enablement_status | string | null | OS permission last reported by the device |
sdk_enabled | boolean | Whether the Arsel SDK is embedded on this device |
sdk_version | string | null | e.g. android/1.2.0 |
last_used_at | string | null | Last time Arsel sent to this device |
last_seen_at | string | null | Last time the device checked in |
The raw device token is never returned
device_token_masked shows only the first and last few characters. The full token is a sendable credential — anyone holding it and your Firebase project could push to that device — so Arsel never returns it, not even to the account that supplied it. Correlate on installation_id instead.
Reading revoke_reason
| Value | Meaning | Reversible? |
|---|---|---|
user_opt_out | The user opted out in your app | No — durable |
contact_opt_out | Revoked via Unregister Device | No — durable |
dead_token | The transport rejected the token permanently (uninstall, ~270 days inactive, or the token belongs to a different Firebase sender) | Yes — a successful re-register clears it |
admin | Revoked by Arsel staff | — |
Examples
- cURL
- JavaScript
- Python
- C#
- PHP
curl "https://api.arsel.sa/v1/push/contacts/01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a/devices?status=ACTIVE&limit=50" \
-H "Authorization: Bearer be_your_api_key"
const contactId = "01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a";
const response = await fetch(
`https://api.arsel.sa/v1/push/contacts/${contactId}/devices?status=ACTIVE`,
{ headers: { Authorization: "Bearer be_your_api_key" } },
);
const { data, has_more } = await response.json();
console.log(data.length, has_more);
import requests
contact_id = "01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a"
response = requests.get(
f"https://api.arsel.sa/v1/push/contacts/{contact_id}/devices",
headers={"Authorization": "Bearer be_your_api_key"},
params={"status": "ACTIVE"},
)
payload = response.json()
print(len(payload["data"]), payload["has_more"])
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer be_your_api_key");
var contactId = "01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a";
var response = await client.GetStringAsync(
$"https://api.arsel.sa/v1/push/contacts/{contactId}/devices?status=ACTIVE");
Console.WriteLine(response);
<?php
$contactId = "01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a";
$ch = curl_init("https://api.arsel.sa/v1/push/contacts/$contactId/devices?status=ACTIVE");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer be_your_api_key"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
echo curl_exec($ch);
curl_close($ch);
Error Responses
- 404 Contact
- 401 Unauthorized
{
"status_code": 404,
"name": "not_found",
"message": "Contact not found"
}
{
"status_code": 401,
"name": "unauthorized",
"message": "Invalid or missing API key"
}