Add a user to an organization
POST /v1/organizations/{org_id}/memberships · permission organizations:write
Adds an existing user to the organization directly (source admin_api, status active). Without role_ids the organization's default member role is assigned; each role must be an environment role or a role of this organization. The response carries an ETag; the write is audited as organization_membership.created. Idempotency-Key is optional.
Request
| Field | Type | Required | Description |
|---|---|---|---|
org_id |
path | yes | Organization ID (org_…). |
user_id |
string | yes | User ID (usr_…). |
role_ids |
string[] | no | Role IDs (rol_…), at most 50. |
expires_at |
string | no | RFC 3339 time in the future when the membership ends. |
Response
201 Created with the membership.
{
"id": "mem_01j9xn5p9s0t4v6w8x0y2z4a6c",
"organization_id": "org_01j9xk3m7q8r2s4t6v8w0y2z4a",
"user_id": "usr_01j9xh2k6p7q1r3s5t7v9w1x3y",
"status": "active",
"source": "admin_api",
"reason": null,
"roles": [
{
"role_id": "rol_01j9xr7s1t2w6x8y0z2a4b6c8e",
"source": "admin_api",
"assigned_at": "2026-10-12T09:31:00.123456Z"
}
],
"expires_at": null,
"created_at": "2026-10-12T09:31:00.123456Z",
"activated_at": "2026-10-12T09:31:00.123456Z",
"suspended_at": null,
"removed_at": null,
"updated_at": "2026-10-12T09:31:00.123456Z",
"version": 1
}
Errors
| HTTP | code | When |
|---|---|---|
| 404 | not_found |
The organization or user does not exist in this environment. |
| 403 | role_not_assignable |
A role is not an environment role or a role of this organization. |
| 403 | user_not_active |
The user is not active. |
| 409 | membership_already_exists |
The user is already a member of the organization. |
| 409 | organization_disabled |
The organization is disabled. |
| 409 | organization_membership_limit_reached |
The organization's membership limit is reached. |
| 409 | membership_limit_reached |
The user's membership limit is reached. |
| 422 | anonymous_user_not_allowed |
The user is anonymous. |
| 422 | validation_failed |
A field is missing or invalid. |
| 401 | unauthenticated |
The credential is missing, invalid or revoked. |
| 403 | insufficient_permissions |
The credential lacks the required permission. Assigning a privileged (is_privileged) role also requires roles:write; without it nothing is written and required_permissions lists both permissions. |
| 429 | rate_limited |
The rate limit was exceeded; retry after the Retry-After period. |
| 503 | service_unavailable |
A dependency is temporarily unavailable; retry after the Retry-After period. |
Example
curl -i -X POST "https://api.eu.keenauth.com/v1/organizations/org_01j9xk3m7q8r2s4t6v8w0y2z4a/memberships" \
-H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"user_id":"usr_01j9xh2k6p7q1r3s5t7v9w1x3y","role_ids":["rol_01j9xr7s1t2w6x8y0z2a4b6c8e"]}'
Personal data
This endpoint carries personal data: organization memberships and invitations. 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.