Skip to content

Working with Customer Data

Customer endpoints live under /projects/{projectId}/users. Responses use the { "success": true, "data": … } envelope. The model and the create-vs-upsert decision are covered in customer concepts.

POST /projects/{projectId}/users

{
"userId": "cus_8f2k",
"firstName": "Alice",
"lastName": "Smith",
"email": "[email protected]",
"phone": "+905551112233",
"birthday": "1990-04-21",
"metadata": { "tier": "silver", "signupSource": "ios" }
}

userId, firstName and lastName (non-empty) are required. Returns 201 with the profile. If the userId already exists, you get 409 with error.code 2500 and the profile is unchanged. Treat that as “already registered”, not as a failure to retry.

PATCH /projects/{projectId}/users/{userId}

{
"email": "[email protected]",
"metadata": { "tier": "gold", "signupSource": null }
}

Only the fields you send change. metadata is merged: tier is set, signupSource is removed, and every other key stays. Returns 200 when the profile was updated and 201 when it was created because the userId was unknown. Names are sent as non-empty strings or left out; to clear a top-level field such as email, there is no null option over REST.

POST /projects/{projectId}/users/bulk

{
"users": [
{ "userId": "cus_001", "firstName": "Ada", "lastName": "Lovelace", "email": "[email protected]" },
{ "userId": "cus_002", "firstName": "Alan", "lastName": "Turing" }
]
}

1–1000 users per request, each in the same shape as register. The batch is all-or-nothing: if any userId appears twice in the request or already exists, the whole request fails with 409 2501 and nothing is created. On success it returns { "created": n, "failed": 0 }. For re-runnable imports of data that may already exist, split into batches and, on 409, fall back to PATCH per user.

NeedRequest
Exact customer by IDGET /projects/{projectId}/users/{userId}; 404 2502 if missing
Find by email, phone or nameGET /projects/{projectId}/[email protected]
Page through all customersGET /projects/{projectId}/users?limit=100&offset=200

search is a case-insensitive substring match across id, firstName, lastName, the full name, email and phone (max 200 characters). It can return several customers, for example search=smith. Confirm an exact field match before acting on a search result, especially before wallet writes. Lists return { "items": [...], "total": n } with the newest first; limit is 1–100 (default 50).

metadata comes back as a JSON-encoded string, e.g. "{\"tier\":\"gold\"}". Parse it before reading keys.

GET /projects/{projectId}/users/{userId}/events?limit=20 lists the workflows started by events sent with this userId, newest first. Use it to see whether a purchase event was processed and which rules ran. See workflows.

DELETE /projects/{projectId}/users/{userId} returns { "deleted": true }, also when the profile did not exist. It removes only the profile; the wallet, referral data and workflow history stay. See customer concepts.

HTTPerror.codeWhen
4092500Register: the userId already exists
4092501Bulk: duplicate IDs in the request, or some IDs already exist
4042502Get: no profile for this userId
400—Validation: missing names on register, invalid email, more than 1000 users

After a write, GET /projects/{projectId}/users/{userId} and compare each field you sent; check that other metadata keys were kept. After a bulk import, page through the list with search on a few IDs, or compare total before and after.