Organizations
/api/v1/organizationsList all organizations user is member of.
Authorization
HTTPBearer In: header
Response Body
application/json
curl -X GET "https://example.com/api/v1/organizations"[ { "name": "string", "slug": "string", "logo_url": "string", "timezone": "UTC", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "data_region": "string", "zitadel_org_id": "string", "created_at": "2019-08-24T14:15:22Z", "updated_at": "2019-08-24T14:15:22Z", "user_role": "owner", "member_count": 0, "features": [], "user_permissions": [] }]/api/v1/organizationsCreate new organization with current user as owner. Also provisions a matching Zitadel org.
Authorization
HTTPBearer In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Schema for creating an organization.
Response Body
application/json
application/json
curl -X POST "https://example.com/api/v1/organizations" \ -H "Content-Type: application/json" \ -d '{ "name": "string", "slug": "string" }'{ "name": "string", "slug": "string", "logo_url": "string", "timezone": "UTC", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "data_region": "string", "zitadel_org_id": "string", "created_at": "2019-08-24T14:15:22Z", "updated_at": "2019-08-24T14:15:22Z"}/api/v1/organizations/invitations/pendingList pending invitations for current user.
Authorization
HTTPBearer In: header
Response Body
application/json
curl -X GET "https://example.com/api/v1/organizations/invitations/pending"[ { "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b", "email": "string", "role": "owner", "token": "string", "invited_by": "0fdabe0c-eb7c-440a-96d2-2c65906c3777", "status": "pending", "created_at": "2019-08-24T14:15:22Z", "expires_at": "2019-08-24T14:15:22Z", "is_expired": true, "is_valid": true, "organization_name": "string", "inviter_name": "string", "inviter_email": "string" }]/api/v1/organizations/invitations/acceptAccept invitation and join organization.
Authorization
HTTPBearer In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Schema for accepting an invitation.
Response Body
application/json
application/json
curl -X POST "https://example.com/api/v1/organizations/invitations/accept" \ -H "Content-Type: application/json" \ -d '{ "token": "string" }'{ "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b", "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5", "role": "owner", "invited_by": "0fdabe0c-eb7c-440a-96d2-2c65906c3777", "joined_at": "2019-08-24T14:15:22Z", "created_at": "2019-08-24T14:15:22Z", "user_email": "string", "user_name": "string", "user_avatar_url": "string", "org_slug": "string"}/api/v1/me/orgGet organization details.
Authorization
APIKeyHeader In: header
Response Body
application/json
curl -X GET "https://example.com/api/v1/me/org"{ "name": "string", "slug": "string", "logo_url": "string", "timezone": "UTC", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "data_region": "string", "zitadel_org_id": "string", "created_at": "2019-08-24T14:15:22Z", "updated_at": "2019-08-24T14:15:22Z", "user_role": "owner", "member_count": 0, "features": [], "user_permissions": []}/api/v1/me/orgUpdate organization (owner only).
The actor is resolved rather than required. This route is gated by
require_organization_write, which accepts an API key carrying
organization:write as well as a member bearer token, so demanding
get_current_user here would 403 every machine caller that the scope
check had just authorized (issue #603 did exactly that). get_audit_actor
(app/dependencies/audit_actor.py) resolves either credential without
requiring one, unlike get_optional_user, which still 403s on an
API-key-only request because it depends on the same eager
HTTPBearer(auto_error=True). The four other audited routes in this
module are gated by require_owner / require_admin_or_above, which are
bearer-only, so a required user is correct there and is left alone.
AT §4.1's envelope has actor_type = "api_key" for precisely this case,
so a machine call is recorded as the key that made it, not as a null
user.
In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Schema for updating organization.
Deliberately excludes data_region — it is set once at creation and is
immutable afterward (TELEMETRY_INGESTION_ARCHITECTURE.md §7.2). extra="forbid"
below ensures a data_region field in the request body is rejected (422)
rather than silently ignored.
Response Body
application/json
application/json
curl -X PATCH "https://example.com/api/v1/me/org" \ -H "Content-Type: application/json" \ -d '{}'{ "name": "string", "slug": "string", "logo_url": "string", "timezone": "UTC", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "data_region": "string", "zitadel_org_id": "string", "created_at": "2019-08-24T14:15:22Z", "updated_at": "2019-08-24T14:15:22Z"}/api/v1/me/orgDelete organization (owner only).
Authorization
HTTPBearer In: header
Response Body
curl -X DELETE "https://example.com/api/v1/me/org"/api/v1/me/membersList organization members.
Authorization
APIKeyHeader In: header
Response Body
application/json
curl -X GET "https://example.com/api/v1/me/members"[ { "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b", "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5", "role": "owner", "invited_by": "0fdabe0c-eb7c-440a-96d2-2c65906c3777", "joined_at": "2019-08-24T14:15:22Z", "created_at": "2019-08-24T14:15:22Z", "user_email": "string", "user_name": "string", "user_avatar_url": "string", "org_slug": "string" }]/api/v1/me/members/inviteInvite a user to this organization.
If a Zitadel org is provisioned:
- Looks up the user in Zitadel by email
- If found: adds them as an org member in Zitadel immediately
- If not found: creates a Zitadel user (triggers init email) and adds as member
Also creates a UP0 invitation record for backward compatibility.
Authorization
HTTPBearer In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Schema for creating an invitation.
Response Body
application/json
application/json
curl -X POST "https://example.com/api/v1/me/members/invite" \ -H "Content-Type: application/json" \ -d '{ "email": "string" }'{ "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b", "email": "string", "role": "owner", "token": "string", "invited_by": "0fdabe0c-eb7c-440a-96d2-2c65906c3777", "status": "pending", "created_at": "2019-08-24T14:15:22Z", "expires_at": "2019-08-24T14:15:22Z", "is_expired": true, "is_valid": true, "organization_name": "string", "inviter_name": "string", "inviter_email": "string"}/api/v1/me/members/{user_id}Remove member from organization (admin or above). Removes from Zitadel org too.
Authorization
HTTPBearer In: header
Path Parameters
uuidResponse Body
application/json
curl -X DELETE "https://example.com/api/v1/me/members/497f6eca-6276-4993-bfeb-53cbbbba6f08"/api/v1/me/members/{user_id}/roleUpdate a member's role (owner only).
Two request bodies (app.schemas.organization.MemberRoleUpdate):
role_id(issue #597, RBAC §3.5/§8) — binds the member to any role visible to the org (system, this org's custom roles, or an entitled plan role), viaRoleService.assign_role_to_member, which enforces invariant 1 (refuses to downgrade the last Owner) and invariant 3 (only a current Owner may raise a member to Owner — checked independently of this endpoint's ownrequire_ownergate).role(legacy) — the original four-value enum path, unchanged.
Both refuse to downgrade the last owner away from OWNER, preventing orphaned organizations.
Authorization
HTTPBearer In: header
Path Parameters
uuidRequest Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Request body for updating a member's role.
role_id is the RBAC §3.5 / §8 role-assignment path (issue #597) —
binds the member to any role visible to the org (system, its own
custom roles, or an entitled plan role), including one the legacy
four-value role enum cannot represent. When set, it takes priority
over role and the legacy enum-only path is not exercised. role
stays required when role_id is omitted, so every existing caller
(console, CLI, tests) keeps working unchanged.
Response Body
application/json
application/json
curl -X PATCH "https://example.com/api/v1/me/members/497f6eca-6276-4993-bfeb-53cbbbba6f08/role" \ -H "Content-Type: application/json" \ -d '{}'{ "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b", "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5", "role": "owner", "invited_by": "0fdabe0c-eb7c-440a-96d2-2c65906c3777", "joined_at": "2019-08-24T14:15:22Z", "created_at": "2019-08-24T14:15:22Z", "user_email": "string", "user_name": "string", "user_avatar_url": "string", "org_slug": "string"}/api/v1/me/invitationsList pending invitations for organization (admin or above).
Authorization
HTTPBearer In: header
Response Body
application/json
curl -X GET "https://example.com/api/v1/me/invitations"[ { "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b", "email": "string", "role": "owner", "token": "string", "invited_by": "0fdabe0c-eb7c-440a-96d2-2c65906c3777", "status": "pending", "created_at": "2019-08-24T14:15:22Z", "expires_at": "2019-08-24T14:15:22Z", "is_expired": true, "is_valid": true, "organization_name": "string", "inviter_name": "string", "inviter_email": "string" }]/api/v1/me/invitations/{invitation_id}Revoke invitation (admin or above).
Authorization
HTTPBearer In: header
Path Parameters
uuidResponse Body
application/json
curl -X DELETE "https://example.com/api/v1/me/invitations/497f6eca-6276-4993-bfeb-53cbbbba6f08"/api/v1/me/api-keysList active API keys for the organization. Owner only.
Authorization
HTTPBearer In: header
Response Body
application/json
curl -X GET "https://example.com/api/v1/me/api-keys"[ { "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b", "name": "string", "key_prefix": "string", "scopes": [ "string" ], "role_id": "ac4e70c8-d5be-48af-93eb-760f58fc91a9", "is_active": true, "expires_at": "2019-08-24T14:15:22Z", "last_used_at": "2019-08-24T14:15:22Z", "created_at": "2019-08-24T14:15:22Z" }]/api/v1/me/api-keysCreate an API key for the organization. Owner only.
⚠️ The full key is returned once in the response field key.
Store it securely — it cannot be retrieved again.
Authorization
HTTPBearer In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Request body to create an API key.
role_id binds the key to an RBAC role (RBAC §3.6, issue #598) — reads
prefer it over scopes once set. Optional during the transition window:
scopes remains the legacy, still-supported vocabulary for a key that
doesn't set it.
Response Body
application/json
application/json
curl -X POST "https://example.com/api/v1/me/api-keys" \ -H "Content-Type: application/json" \ -d '{ "name": "string", "scopes": [ "string" ] }'{ "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b", "name": "string", "key_prefix": "string", "scopes": [ "string" ], "role_id": "ac4e70c8-d5be-48af-93eb-760f58fc91a9", "is_active": true, "expires_at": "2019-08-24T14:15:22Z", "last_used_at": "2019-08-24T14:15:22Z", "created_at": "2019-08-24T14:15:22Z", "key": "string"}/api/v1/me/api-keys/{key_id}Revoke an API key. Owner only.
Authorization
HTTPBearer In: header
Path Parameters
uuidResponse Body
application/json
curl -X DELETE "https://example.com/api/v1/me/api-keys/497f6eca-6276-4993-bfeb-53cbbbba6f08"/api/v1/me/service-accountsList the active service accounts for this org. Metadata only — no secret; a client_secret is never reproducible after creation.
require_admin_or_above, not require_owner like creation and
rotation below: an Admin building a GitOps Project needs to see which
accounts exist in order to bind one (#567's Add Project picker,
GITOPS_ARCHITECTURE.md §7.4), even though only an Owner may mint a
new one. Binding an already-authorized identity is a narrower action
than creating one.
#562 and #567 each added a GET here independently, with different
gates and response models. They are one route: this one. The wider
ServiceAccountResponse is kept because the /settings/service-accounts
page reads revoked_at, which the picker's narrower shape omitted.
Authorization
HTTPBearer In: header
Response Body
application/json
curl -X GET "https://example.com/api/v1/me/service-accounts"[ { "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b", "zitadel_user_id": "string", "name": "string", "role": "owner", "created_at": "2019-08-24T14:15:22Z", "revoked_at": "2019-08-24T14:15:22Z" }]/api/v1/me/service-accountsCreate a Zitadel machine user (service account) for this org.
Use this for CLI tools, agents, and packages that need to authenticate
against UP0 from private environments (up0 auth login --client-id --client-secret, GITOPS_ARCHITECTURE.md §3.2/§3.3). The client secret
returned is shown once.
Owner only. Per GITOPS_ARCHITECTURE.md §2/§3.2, this is the only way a
service account is ever created — there is no public signup or
bootstrap endpoint. The first service account for an org is necessarily
created this way, by an already-logged-in owner on the console's
/settings/service-accounts page; up0 service-accounts create (#562)
calls this same endpoint once an authenticated caller already exists.
Also binds the new machine user to this org in the UP0-side
service_accounts table (issue #561, fix for #489) — without this row
the machine user could authenticate to Zitadel but not to UP0's own API,
since get_org_context resolves a machine subject's org from this table,
not from Zitadel. If the DB write fails, the Zitadel-side machine user is
torn down rather than left orphaned and unusable.
Authorization
HTTPBearer In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Request body to create a Zitadel machine user (service account).
role is the console/CLI role picker (issue #562): only the four
system roles, since no custom-role authoring surface exists yet
(CUSTOMIZABLE_RBAC_ACL_ARCHITECTURE.md §3.3, §7). Required, with no
default (issue #598, mirroring gitops-sync #564's required
service-account binding): an implicit default is how a machine subject
ends up with more authority than anyone chose. Editor — a sync agent
needs to write Monitor/StatusPage/IngestionToken/Workflow, and cannot
delete the org or touch billing
(MULTI_TENANCY_AUTHORIZATION_ARCHITECTURE.md §3.2's OWNER-exclusive
actions) — is the commonly-right choice, but the caller must say so
explicitly. The endpoint resolves this to a role_id before persisting
(issue #864 — service_accounts stores role_id, not this enum).
Response Body
application/json
application/json
curl -X POST "https://example.com/api/v1/me/service-accounts" \ -H "Content-Type: application/json" \ -d '{ "name": "string", "username": "string", "role": "owner" }'{ "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "machine_user_id": "string", "username": "string", "name": "string", "role": "owner", "client_id": "string", "client_secret": "string", "message": "Save this secret now. You cannot retrieve it again."}/api/v1/me/service-accounts/{service_account_id}/rotateRotate a service account's client secret. Owner only.
Invalidates the prior secret immediately (Zitadel holds one active
secret per machine user — see zitadel_admin.rotate_machine_secret).
The new secret is shown once.
Authorization
HTTPBearer In: header
Path Parameters
uuidResponse Body
application/json
application/json
curl -X POST "https://example.com/api/v1/me/service-accounts/497f6eca-6276-4993-bfeb-53cbbbba6f08/rotate"{ "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "client_id": "string", "client_secret": "string", "message": "Save this secret now. You cannot retrieve it again."}/api/v1/me/service-accounts/{service_account_id}Revoke a service account. Owner only.
Marks it revoked in service_accounts (checked by get_current_principal
on every subsequent request — see app/dependencies/zitadel_auth.py) and
deletes the Zitadel-side machine user, so it can no longer be issued new
client_credentials tokens either.
DB-first ordering: the row is marked revoked before the Zitadel call is
attempted, so a second request against an already-revoked account 404s
from service.revoke()'s own None return rather than re-attempting the
Zitadel delete (code review on #562/#864 confirmed this idempotency).
Authorization
HTTPBearer In: header
Path Parameters
uuidResponse Body
application/json
curl -X DELETE "https://example.com/api/v1/me/service-accounts/497f6eca-6276-4993-bfeb-53cbbbba6f08"