Users API
Server-side user management. Secret key only, scopes users:read / users:write. Both addressing forms — our id and your external id — hit the same handlers.
GET /v1/users/:id · GET /v1/users/by-external-id/:externalId
{
"id": "66f2…",
"externalId": "user_123",
"tags": { "plan": "premium", "firstName": "Ayşe" },
"language": "tr", "timezone": "Europe/Istanbul", "country": "TR",
"createdAt": "2026-07-01T10:00:00.000Z", "lastActiveAt": "2026-09-17T08:12:00.000Z",
"subscriptions": [
{
"id": "66f1…", "platform": "ios", "token": "…a3f9",
"optedIn": true, "appVersion": "3.4.1", "sdkVersion": "0.1.0", "osVersion": "17.5", "deviceModel": "iPhone 15 Pro",
"browser": null, "standalone": null,
"createdAt": "…", "lastActiveAt": "…", "lastNotifiedAt": "…", "invalidatedAt": null
}
]
}
token is masked — this is for support, not for sending.
PATCH /v1/users/:id · PATCH /v1/users/by-external-id/:externalId
{ "tags": { "plan": "premium", "trial": null }, "language": "tr", "timezone": "Europe/Istanbul", "country": "TR" }
At least one field. Tags merge, null deletes. Returns the user as in GET.
The by-external-id form upserts: an unknown external id becomes a user with no devices. That is how a backend sets tags on someone before their phone ever registers — when it does, login("user_123") moves the device under this row and the tags apply.
DELETE /v1/users/:id · DELETE /v1/users/by-external-id/:externalId
204. Deletes the user and every subscription. Past events keep only ids. This is the GDPR erasure path.
Errors
404 not_found, 422 invalid_body, 403 forbidden (public key or missing scope).