Identity & Access Management

Preview — pending review

This reference is generated from the platform API contract and is still under review. Operations, fields, and descriptions can change before publication.

Invite a member into the tenant with an access matrix

POST
/tenants/{tenantId}/iam/users

Invites a new member into the TENANT and grants, in the same request, every row of its access matrix: the whole tenant, whole subscriptions, single projects, or any mix, each row with its own role. access: [] invites a member without access. The member is created INVITED; when sendInvitationEmail is true the invitation (sign-in link) is mailed after the commit. Validation: every row is checked BEFORE anything is written, and a refused invite leaves nothing behind (no user, invite, token, grant or Activity Log record). A row's scopeId must be this tenant, one of its subscriptions or a project beneath one of them AND be reached by the caller -- unknown, foreign and unreached are the same 404 (SCOPE_NOT_FOUND). The caller needs iam: manage at the row's own target (INSUFFICIENT_ROLE_FOR_SCOPE), must outrank and cover the role there (CANNOT_GRANT_ABOVE_OWN), and a tenant row needs an owner of the tenant (TENANT_OWNER_REQUIRED) -- all 403. An unknown role is ROLE_NOT_FOUND and a role not assignable at the row's kind ROLE_NOT_ASSIGNABLE_AT_SCOPE -- both 422. Every row is evaluated; the status is the highest of 404 > 403 > 422 and details.errors[] lists every row error of that status as {index, field: "access.<i>.<scopeId|role|scopeKind>", code, message}. Normalization: rows are compared on the canonical role id. An exact duplicate (DUPLICATE), a project row whose subscription has a row with the same role (COVERED_BY_SUBSCRIPTION) and any subscription/project row when a tenant row with the same role exists (COVERED_BY_TENANT) are dropped and returned in dropped. Different roles never cover each other. Async & state: 202 with the INVITED member (its roles and accessSummary) and the dropped rows. The Activity Log records iam.user.invite plus one iam.user.access.grant per written row. Errors: 401 unauthenticated; 403 when the caller has no iam: manage anywhere in the tenant or a row is refused; 404 when the tenant (or, for a bearer, a tenant other than its home tenant) or a row's scope is not reached; 409 when the address is already a member of this tenant; 422 on body or row validation failure (including a personalMessage over 500 characters or carrying a link); 429 RATE_LIMITED (details.reason INVITE_TENANT_DAILY_LIMIT) when the tenant has used today's invitation-mail budget. Idempotency/Concurrency: Idempotency-Key is required; a replay returns the original answer without a second invitation. An address that already has an account elsewhere is answered exactly like a fresh one.

Step-up: granting an owner-ranked role (built-in owner, or a custom role that ranks as owner) from a console session whose account has two-factor authentication needs the factor verified in the last 5 minutes, otherwise 403 MFA_STEP_UP_REQUIRED (verify with POST /auth/step-up, then retry). Other roles, and an owner row the member already holds, are not affected.

Authorization

bearerToken
headerAuthorizationBearer <token>

An API token (pc_live_…) or a service-account key (sak_live_…), created in Identity & Access Management and sent as Authorization: Bearer <token>. Requests are rate limited per credential and per tenant: every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset, and a refused request gets 429 RATE_LIMITED with Retry-After.

Path Parameters

tenantId*string

The tenant id.

Header Parameters

Idempotency-Key*string

Required: this operation creates a new resource asynchronously, so a retry without the key would create a second one. Same key+body within 24h replays the original result; the same key with a different body is 409 CONFLICT. Keys are unique per tenant (the same key in two tenants is two different requests). An API token or service-account (bearer) caller MUST send it -- a request without it is refused with 400 IDEMPOTENCY_KEY_REQUIRED (ErrIdempotencyKeyRequired); reuse the same key on every retry of one logical request. A console session gets one from the edge.

Formatuuid

Request Body

application/json
  1. body

The tenant-level invite -- the member's name and address plus its access matrix.

firstName*string

Member's first (given) name; stored on the invitee's profile. Must not be blank.

Length1 <= length <= 255
lastName*string

Member's last (family) name; stored on the invitee's profile. Must not be blank.

Length1 <= length <= 255
email*string

The invitee's address; it is also the member's id.

Formatemail
sendInvitationEmail?boolean

Whether to mail the invitation (sign-in link).

Defaulttrue
personalMessage?|

Optional plain-text message prepended to the invitation email (at most 500 characters, no links); ignored when no email is sent.

Lengthlength <= 500
locale?|

Console locale for the invitation link (tr, en, de); unsupported values fall back to the default.

access?array<>

The access matrix. Empty = a member without access.

Itemsitems <= 500
Default[]

Response Body

Invitation accepted; the member in its INVITED state plus the dropped rows.

application/json
  1. response

The answer of the tenant invite.

user*

IAM user record (member, guest, or service account) as consumed by the Users page and the user detail page.

dropped*array<>
curl -X POST "https://example.com/tenants/string/iam/users" \  -H "Idempotency-Key: 497f6eca-6276-4993-bfeb-53cbbbba6f08" \  -H "Content-Type: application/json" \  -d '{    "firstName": "string",    "lastName": "string",    "email": "user@example.com"  }'
{  "user": {    "id": "string",    "name": "string",    "email": "string",    "title": "string",    "avatarColor": "string",    "status": "ACTIVE",    "operatorLocked": true,    "type": "MEMBER",    "mfaEnabled": true,    "lastLogin": "2019-08-24T14:15:22Z",    "invitedAt": "2019-08-24T14:15:22Z",    "createdAt": "2019-08-24T14:15:22Z",    "lastPasswordChange": "2019-08-24T14:15:22Z",    "department": "string",    "manager": "string",    "phone": "string",    "employeeId": "string",    "groups": [      "string"    ],    "mfaDevices": [      {        "id": "string",        "kind": "TOTP",        "name": "string",        "addedAt": "2019-08-24T14:15:22Z",        "lastUsedAt": "2019-08-24T14:15:22Z",        "primary": true      }    ],    "apiKeys": [      {        "id": "string",        "name": "string",        "prefix": "string",        "createdAt": "2019-08-24T14:15:22Z",        "lastUsedAt": "2019-08-24T14:15:22Z",        "status": "ACTIVE"      }    ],    "roles": [      {        "role": "owner",        "roleId": "string",        "roleName": "string",        "scopeKind": "tenant",        "scopeLabel": "string",        "assignedAt": "2019-08-24T14:15:22Z"      }    ],    "activity": [      {        "id": "string",        "at": "2019-08-24T14:15:22Z",        "action": "string",        "resource": "string",        "status": "SUCCESS"      }    ],    "tags": [      {        "key": "string",        "value": "string"      }    ],    "accessSummary": {      "kind": "tenant",      "subscriptions": 0,      "projects": 0    }  },  "dropped": [    {      "index": 0,      "scopeKind": "tenant",      "scopeId": "string",      "role": "string",      "reason": "DUPLICATE",      "coveredBy": {        "scopeKind": "tenant",        "scopeId": "string"      }    }  ]}