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
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.
bearerTokenAuthorizationBearer <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.
tenantId*stringThe tenant id.
Idempotency-Key*stringRequired: 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.
uuidapplication/json- body
The tenant-level invite -- the member's name and address plus its access matrix.
firstName*stringMember's first (given) name; stored on the invitee's profile. Must not be blank.
1 <= length <= 255lastName*stringMember's last (family) name; stored on the invitee's profile. Must not be blank.
1 <= length <= 255email*stringThe invitee's address; it is also the member's id.
emailsendInvitationEmail?booleanWhether to mail the invitation (sign-in link).
truepersonalMessage?|Optional plain-text message prepended to the invitation email (at most 500 characters, no links); ignored when no email is sent.
length <= 500locale?|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.
items <= 500[]Invitation accepted; the member in its INVITED state plus the dropped rows.
application/json- 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" } } ]}