Users (REST)

Read and search anonymous and identified users, update their saved properties, or schedule privacy exports and deletions. List, profile, timeline and privacy operations use a WorkOS access token and the required workspace role. Property updates use the separate SDK or scoped backend authentication described below.

List

GET /v1/apps/:appId/users
Authorization: Bearer <workos_access_token>
 
# Query params
?q=[email protected]
&limit=50
&cursor=cur_xyz
{
  "data": {
    "items": [
      {
        "subjectId": "0198d5c3-…",
        "anonymousId": "device_a1b2c3",
        "anonymousIds": ["device_a1b2c3"],
        "externalId": "user_42",
        "firstSeenAt": "2026-01-12T00:00:00.000Z",
        "lastSeenAt": "2026-05-13T09:11:00.000Z",
        "eventCount": 42,
        "topProperties": { "plan": "pro" },
        "country": "DE",
        "platform": "ios"
      }
    ],
    "total": 1,
    "nextCursor": null
  }
}

Get one

GET /v1/apps/:appId/users/:anonymousId
Authorization: Bearer <workos_access_token>

Returns the user profile + device list + most-recent properties.

Timeline

GET /v1/apps/:appId/users/:anonymousId/events?limit=100&cursor=…

Returns paginated events for that user.

Update user properties

For the active app user, prefer the React Native or web SDK's setUserProperties(values, unsetKeys?). Its endpoint requires both the public write key and the current subject token:

POST /v1/sdk/user-properties
Authorization: Bearer <write_key>
X-UserGist-Subject-Token: <subject_token>
Content-Type: application/json
 
{
  "anonymousId": "device_a1b2c3",
  "mutationId": "00000000-0000-4000-8000-000000000002",
  "set": { "first_name": "Ava", "country": "IL" },
  "unset": ["old_plan"]
}

The anonymous alias must belong to the authenticated subject. This works for anonymous and identified users and requires analytics consent.

For backend-owned state, such as a currently eligible resume target, use an API token with users.properties.write in the app's workspace:

PATCH /v1/apps/:appId/users/properties
Authorization: Bearer <api_token_with_users.properties.write>
Content-Type: application/json
 
{
  "subject": { "externalId": "user_42" },
  "update": {
    "mutationId": "00000000-0000-4000-8000-000000000003",
    "set": {
      "resume_show_id": "00123",
      "resume_show_title": "Midnight Orbit",
      "resume_position_seconds": 1234
    }
  }
}

Supply exactly one subject reference: externalId, anonymousId or subjectId. The backend endpoint also enforces analytics consent and the app's privacy allow-list. Keep its API token on your server.

Both forms use the same update contract:

  • mutationId is a UUID. Reuse it with the same payload and alias for retries; reusing it with changed data returns a conflict.
  • set accepts at most 64 flat properties: strings up to 8,192 characters, finite numbers, booleans or null. Keys are literal, at most 120 characters.
  • unset accepts at most 64 keys and explicitly removes those values. A key cannot appear in both set and unset; an empty update is rejected.
  • A successful response contains data.applied and data.filteredKeys. A replay returns applied: false. Inspect filteredKeys for values removed by privacy policy; do not assume every submitted property was stored.

Send related current-state values in one update. Null is treated as missing by personalization; use unset when removing a stored property. See Personalize messages for typed fields, fallbacks and JSON destinations.

Privacy export

POST /v1/apps/:appId/gdpr/export
Authorization: Bearer <workos_access_token>
Content-Type: application/json
 
{ "externalId": "user_42" }

Provide either externalId or anonymousId. This requires the admin role and returns an exportId. Poll the status endpoint until it returns completed and a short-lived download URL:

GET /v1/apps/:appId/gdpr/exports/:exportId
Authorization: Bearer <workos_access_token>

Delete (GDPR)

POST /v1/apps/:appId/gdpr/delete
Authorization: Bearer <workos_access_token>
Content-Type: application/json
 
{ "anonymousId": "device_a1b2c3" }

Provide either externalId or anonymousId. This requires the admin role and queues an auditable deletion job. The job resolves every linked anonymous and identified alias, removes operational records and push tokens, and does not report completion until analytics-store deletion has succeeded.

What's next