Merge a duplicate user into a user
Merge a duplicate user that has only a WorkOS login into the user in the path. The user in the path gets the WorkOS login, the verified email, and the organization memberships. The duplicate user becomes inactive and keeps its history. Requires the org:users:write permission on the cowdi organization. Returns 200 with no change when the merge already happened. Returns 409 when the duplicate user has a phone sign-in, an open account, or an active merchant membership. Returns 409 when the user in the path already has a WorkOS login or an email, or when either user is merged into another user.
Authorization
oauth2 Create a client with POST /v1/oauth/clients, then exchange its credentials for an access token. AUTHORIZATION_CODE clients act on behalf of a signed-in user and always use PKCE; CLIENT_CREDENTIALS clients act as themselves. A client may only be granted scopes its creator already holds — read the grantable list from GET /v1/oauth/clients/scopes.
In: header
Path Parameters
Unique identifier of the user that keeps the identity.
uuidRequest Body
application/json
Request to merge a duplicate user into the user in the path.
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/v1/users/550e8400-e29b-41d4-a716-446655440000/merge" \ -H "Content-Type: application/json" \ -d '{ "duplicate_user_id": "550e8400-e29b-41d4-a716-446655440000" }'{ "id": "b50e8400-e29b-41d4-a716-446655440000", "auth_id": "sadoj21su9b23b19gvgb", "first_name": "John", "last_name": "Doe", "status": "ACTIVE", "email": "john.doe@example.com", "email_verified": true, "phone_number": "+254712345678", "tag": "@johnd1234", "locale": "en-KE", "timezone": "Africa/Nairobi", "last_login_at": "2026-02-03T10:15:30Z", "organization": "cowdi", "external_user_id": "GW-12345", "is_test": false, "source": "ADMIN_API", "suspension": { "at": "2026-03-10T08:00:00Z", "reason": "FRAUD", "internal_note": "string", "external_message": "string" }, "avatar_options": "seed=k3f9x2&skinColor=edb98a", "resource_id": "string"}{ "code": "BAD_REQUEST", "description": "Bad Request"}{ "code": "UNAUTHORISED", "description": "Unauthorised"}{ "code": "FORBIDDEN", "description": "Forbidden"}{ "code": "BAD_REQUEST", "description": "Invalid input"}{ "code": "RATE_LIMIT_EXCEEDED", "description": "Too Many Requests"}{ "code": "INTERNAL_ERROR", "description": "Internal Server Error"}Link a phone number to a user POST
Link a phone number to a user that has no phone number. The phone number stays unverified. The next phone sign-in with this number signs in as this user. Requires the `org:users:write` permission on the cowdi organization. Returns 200 with no change when the user already has this phone number. Returns 409 when the user has another phone number or an open account, or when another user has the phone number.
List merchants GET
Lists canonical Sales App merchants with contact details, lifecycle status and timestamps. Requires `org:merchants:read`, `org:merchants:write` or `org:merchants:admin` through a verified user or machine token. Partner tokens read their tenant. `user_id` narrows the list to the merchants linked to one Cowdi user. Cowdi platform tokens retain the detail API's existing all-tenant view. Impersonated reads mask names, email and phone. Read full merchant details through `GET /v1/merchants/{merchantId}`.