Link a federated identity to a user
POST /v1/users/{user_id}/identities · permission users:write
Links a federated identity (the external subject of a social, SAML or OIDC connection) to the user, so the user can sign in with that connection. linked_via is always admin_api. The connection must be an enabled connection of this environment whose kind matches type. If the same identity is already linked to this or another user the request fails with 409 and the owner is not revealed. The external subject is personal data: it is stored encrypted and never returned, and the management audit record user.identity_linked (actor: the API key) does not contain it. Merging two users (source_user_id) is not available; an unknown field returns 400 unknown_field.
Request
| Parameter | In | Required | Description |
|---|---|---|---|
user_id |
path | yes | User id, prefixed usr_. |
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | yes | social, saml or oidc; must match the connection (social for a social connection, saml/oidc for an enterprise connection of the same strategy). |
connection_id |
string | yes | Connection id, prefixed conn_. |
subject |
string | yes | External subject at the identity provider (OIDC sub, SAML NameID), at most 1024 bytes of UTF-8. |
Response
201 Created with the new identity, in the shape returned by GET /v1/users/{user_id}/identities.
{
"id": "idn_0a1b2c3d4e5f6g7h8j9k0m1n2r",
"type": "oidc",
"connection_id": "conn_0a1b2c3d4e5f6g7h8j9k0m1n2s",
"provider": null,
"linked_via": "admin_api",
"created_at": "2026-01-15T09:30:00Z"
}
Errors
Every error is an application/problem+json body with a stable code.
| HTTP | code |
When |
|---|---|---|
| 400 | malformed_request, unknown_field |
The body is not valid JSON or has an unknown field. |
| 409 | conflict |
The identity is already linked to a user (errors[]: identity_already_linked), or the user reached the per-user identity limit (errors[]: identity_limit_reached). |
| 415 | unsupported_media_type |
The body was sent without Content-Type: application/json. |
| 422 | validation_failed |
A field is missing or invalid (/type, /connection_id, /subject), or the connection is not an enabled connection of this environment matching type (errors[]: /connection_id, connection_unavailable). |
| 404 | not_found |
The user does not exist in this environment, is being deleted, or the id is malformed. |
| 401 | unauthenticated |
The API key is missing, invalid or revoked. |
| 403 | insufficient_permissions |
The key lacks the users:write permission. |
| 429 | rate_limited |
The rate limit was exceeded; honor Retry-After. |
| 503 | service_unavailable |
A dependency is temporarily unavailable; retry later. |
Example
curl -X POST https://api.eu.keenauth.com/v1/users/usr_0a1b2c3d4e5f6g7h8j9k0m1n2p/identities \
-H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"type": "oidc", "connection_id": "conn_0a1b2c3d4e5f6g7h8j9k0m1n2s", "subject": "248289761001"}'
Personal data
This endpoint carries personal data: linked identities. Personal data in the KeenAuth APIs describes what is stored, how long it is kept and how it is deleted. Do not send special categories of personal data (health, biometric data, religion, political opinions, sexual orientation) in any field.