The descriptions of the operations and fields are the contract’s own, in English.
/identity/v1/organizations/{org}/ssoRead the organization's single sign-on connection
- Credential
- API key
- Permission
sso.manage
Requires sso.manage (the metadata names the identity provider and its client id). Returns the connection's metadata - never a secret. 404 sso-not-configured when the organization has none. Single sign-on is optional per organization and applies to users from Admin downwards; the Root who created the organization keeps password login.
curl "https://console.us.ccsio.ai/identity/v1/organizations/{org}/sso" \
-H "Authorization: Bearer $CCSIO_API_KEY"Parameters
| Name | In | Description |
|---|---|---|
X-Request-IDoptional | header | string Optional request ULID. A valid ULID (upper or lowercase) is honored; when the header is absent or invalid, the runtime mints the id. The response X-Request-ID header carries the canonical uppercase form. |
orgrequired | path | string The organization id (C7 identifier). A value outside the pattern is refused with 400 name-invalid. |
Responses
- 200
The connection.
Schema
SSOConnectionproviderstringrequiredThe identity-provider kind: entra (Microsoft Entra ID, registered in Zitadel through its Azure AD template) or oidc (a generic OIDC provider).
Allowed values:
"entra""oidc"display_namestringrequiredThe name the connection is shown under (also the provider's name in the identity provider's login page).
1 to 64 characters
issuerstringoptionaloidc connections only: the OIDC issuer URL (https). Absent for entra.
at most 200 characters
tenant_idstringoptionalentra connections only: the Entra tenant - a tenant GUID or domain, or one of organizations | common | consumers. Absent for oidc.
at most 253 characters
client_idstringrequired1 to 200 characters
scopesarray of stringrequiredThe scopes the identity provider is asked for.
at most 20 items
enabledbooleanrequiredWhether the provider is attached to the organization's login policy. A disabled connection is kept but nobody can sign in through it, and the public discovery answers 404.
zitadel_idp_idstringrequiredThe id of the organization-level identity provider inside the organization's linked Zitadel organization.
redirect_uristring (uri)requiredThe redirect URI the customer must register at their identity provider (for Entra: the app registration's Web redirect URI).
created_atstring (date-time)requiredupdated_atstring (date-time)required
- 400
Client refusal unrelated to authentication, including malformed input, a denied or failed request.
- 401
The request carries no credential, more than one, or one the platform does not accept, and is refused before the operation runs; `error.code` says why. Supply exactly one credential. Codes: invalid-request (no credential, or both Authorization:
Read more
Bearer and x-api-key), api-key-invalid (a value that is not a platform API key, or a key of a kind the platform does not accept), api-key-unknown, api-key-revoked, api-key-expired, bootstrap-disabled (the one-shot installation credential, once a key exists), token-malformed (a bearer token that is not a JWT), token-algorithm-unsupported, token-signature-invalid, token-claim-invalid, token-issuer-mismatch, token-audience-mismatch, token-expired, token-not-yet-valid, issuer-untrusted, issuer-residency-mismatch (the issuer belongs to another plane), issuer-resolution-unknown, jwks-unavailable, jwks-key-not-found, staff-token-inactive (a platform operator's token), staff-not-operator, staff-directory-unavailable, identity-not-provisioned (a verified identity with no account on this plane), organization-not-found (a verified identity or key whose organization this plane does not know), internal-error (a fault while the credential is checked is refused too, fail closed), context-cancelled
- 402
The organization has to pay first: its standing is payment_required.
Read more
The request boundary applies this payment gate to every route except an organization's billing routes, the caller's own context, organization list, onboarding, session report and personal keys, the public routes, and the routes of platform operators, who administer an organization that has to pay. The operations the gate covers declare this response. On /v1 the refusal is rendered in the OpenAI error format. Code: payment-required
- 403
The credential is valid but may not do this here; the request is refused by the request boundary before the handler runs, and `error.code` says which refusal. An operation may add codes of its own.
Read more
Codes: policy-denied (no role of the caller holds the permission the operation needs, or the credential class may not call it: a personal API key outside inference and MCP, a route that needs a signed-in person, the one-shot installation credential outside its three steps), tenant-mismatch (the organization a path, query, body or X-Tenant header names is not the caller's own), organization-provisioning, organization-suspended, organization-disabled, organization-deleted (the state of the caller's organization), user-disabled, residency-mismatch (the organization lives on another plane), organization-selection-required (a caller with more than one organization, or a platform operator, must name one in X-Organization-Id), organization-not-selectable, no-active-organization-membership, team-not-selectable (the X-Organization-Id or X-Team-Id the caller sent does not name an organization or team it may act in), identity-managed-by-provider (a person who is not a platform operator changes an organization's status, which belongs to the identity provider), support-session-required (a platform operator reads an organization's data without an open support session)
- 404
The requested resource or path was not found.
- 405
The path is claimed but the request method is not allowed. Go ServeMux returns its standard plain-text response and lists permitted methods in Allow.
- 500
Platform failure, rendered without internal cause details.
- 503
A dependency the operation needs is not configured on this plane or could not be reached, and nothing was changed. `error.code` names it; an operation lists the ones it can answer besides the boundary's. Retry later. Codes: support-audit-unavailable (the request boundary: an operator's read under an open support session could not be recorded, so nothing was served)
/identity/v1/organizations/{org}/ssoCreate or update the organization's single sign-on connection
- Credential
- API key
- Permission
sso.manage- Request body
- Body up to 1 MiB
Requires sso.manage (a role permission held by root and organization_admin, assignable to custom roles).
Read more
Registers (or updates) an ORGANIZATION-level identity provider - Microsoft Entra ID through Zitadel's Azure AD template, or a generic OIDC provider - in the organization's linked Zitadel organization, makes sure its login policy allows external identity providers, and attaches or detaches the provider according to enabled. 201 when the connection was created, 200 when it was updated; the body is the connection (without the secret). client_secret is write-only: required to create and to change provider (400 sso-secret-required), optional on update. At most one connection per organization. Users who sign in through it are created automatically in the organization's Zitadel organization and, on their first sign-in, provisioned into the organization with the role user (no group or claim mapping). Refused for a deleted organization (409 organization-deleted); an organization without a linked Zitadel organization is 409 sso-organization-unlinked. An organization's alias exists only while its connection is enabled: an update that leaves the connection disabled releases the alias in the same transaction, and the organization.updated security event records it (details alias_from). Sensitive: audited, and records an sso.configured (201) or sso.updated (200) security event.
curl -X PUT "https://console.us.ccsio.ai/identity/v1/organizations/{org}/sso" \
-H "Authorization: Bearer $CCSIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"provider": "oidc",
"display_name": "Acme SSO",
"issuer": "https://idp.example.com",
"client_id": "ccsio-acme",
"client_secret": "replace-with-your-client-secret",
"scopes": [
"openid",
"profile",
"email"
],
"enabled": true
}'Parameters
| Name | In | Description |
|---|---|---|
X-Request-IDoptional | header | string Optional request ULID. A valid ULID (upper or lowercase) is honored; when the header is absent or invalid, the runtime mints the id. The response X-Request-ID header carries the canonical uppercase form. |
orgrequired | path | string The organization id (C7 identifier). A value outside the pattern is refused with 400 name-invalid. |
Request body application/json
providerstringrequiredThe identity-provider kind: entra (Microsoft Entra ID, registered in Zitadel through its Azure AD template) or oidc (a generic OIDC provider).
Allowed values:
"entra""oidc"display_namestringrequired1 to 64 characters
issuerstringoptionalRequired for oidc, refused for entra. An https URL without credentials, query or fragment.
at most 200 characters
tenant_idstringoptionalRequired for entra, refused for oidc: a tenant GUID or domain, or organizations | common | consumers.
at most 253 characters
client_idstringrequired1 to 200 characters
client_secretstringoptionalThe client secret issued by the identity provider. Never stored, logged or returned by core.
1 to 200 characters
scopesarray of stringoptionalOptional; defaults to openid, profile, email (and User.Read for entra, which Zitadel needs to read the user's name from Microsoft Graph).
at most 20 items
enabledbooleanoptionalOptional; true when the connection is created, unchanged on update.
Responses
- 200
The connection was updated.
Schema
SSOConnectionproviderstringrequiredThe identity-provider kind: entra (Microsoft Entra ID, registered in Zitadel through its Azure AD template) or oidc (a generic OIDC provider).
Allowed values:
"entra""oidc"display_namestringrequiredThe name the connection is shown under (also the provider's name in the identity provider's login page).
1 to 64 characters
issuerstringoptionaloidc connections only: the OIDC issuer URL (https). Absent for entra.
at most 200 characters
tenant_idstringoptionalentra connections only: the Entra tenant - a tenant GUID or domain, or one of organizations | common | consumers. Absent for oidc.
at most 253 characters
client_idstringrequired1 to 200 characters
scopesarray of stringrequiredThe scopes the identity provider is asked for.
at most 20 items
enabledbooleanrequiredWhether the provider is attached to the organization's login policy. A disabled connection is kept but nobody can sign in through it, and the public discovery answers 404.
zitadel_idp_idstringrequiredThe id of the organization-level identity provider inside the organization's linked Zitadel organization.
redirect_uristring (uri)requiredThe redirect URI the customer must register at their identity provider (for Entra: the app registration's Web redirect URI).
created_atstring (date-time)requiredupdated_atstring (date-time)required
- 201
The connection was created.
Schema
SSOConnectionproviderstringrequiredThe identity-provider kind: entra (Microsoft Entra ID, registered in Zitadel through its Azure AD template) or oidc (a generic OIDC provider).
Allowed values:
"entra""oidc"display_namestringrequiredThe name the connection is shown under (also the provider's name in the identity provider's login page).
1 to 64 characters
issuerstringoptionaloidc connections only: the OIDC issuer URL (https). Absent for entra.
at most 200 characters
tenant_idstringoptionalentra connections only: the Entra tenant - a tenant GUID or domain, or one of organizations | common | consumers. Absent for oidc.
at most 253 characters
client_idstringrequired1 to 200 characters
scopesarray of stringrequiredThe scopes the identity provider is asked for.
at most 20 items
enabledbooleanrequiredWhether the provider is attached to the organization's login policy. A disabled connection is kept but nobody can sign in through it, and the public discovery answers 404.
zitadel_idp_idstringrequiredThe id of the organization-level identity provider inside the organization's linked Zitadel organization.
redirect_uristring (uri)requiredThe redirect URI the customer must register at their identity provider (for Entra: the app registration's Web redirect URI).
created_atstring (date-time)requiredupdated_atstring (date-time)required
- 400
Client refusal unrelated to authentication, including malformed input, a denied or failed request.
- 401
The request carries no credential, more than one, or one the platform does not accept, and is refused before the operation runs; `error.code` says why. Supply exactly one credential. Codes: invalid-request (no credential, or both Authorization:
Read more
Bearer and x-api-key), api-key-invalid (a value that is not a platform API key, or a key of a kind the platform does not accept), api-key-unknown, api-key-revoked, api-key-expired, bootstrap-disabled (the one-shot installation credential, once a key exists), token-malformed (a bearer token that is not a JWT), token-algorithm-unsupported, token-signature-invalid, token-claim-invalid, token-issuer-mismatch, token-audience-mismatch, token-expired, token-not-yet-valid, issuer-untrusted, issuer-residency-mismatch (the issuer belongs to another plane), issuer-resolution-unknown, jwks-unavailable, jwks-key-not-found, staff-token-inactive (a platform operator's token), staff-not-operator, staff-directory-unavailable, identity-not-provisioned (a verified identity with no account on this plane), organization-not-found (a verified identity or key whose organization this plane does not know), internal-error (a fault while the credential is checked is refused too, fail closed), context-cancelled
- 402
The organization has to pay first: its standing is payment_required.
Read more
The request boundary applies this payment gate to every route except an organization's billing routes, the caller's own context, organization list, onboarding, session report and personal keys, the public routes, and the routes of platform operators, who administer an organization that has to pay. The operations the gate covers declare this response. On /v1 the refusal is rendered in the OpenAI error format. Code: payment-required
- 403
The credential is valid but may not do this here; the request is refused by the request boundary before the handler runs, and `error.code` says which refusal. An operation may add codes of its own.
Read more
Codes: policy-denied (no role of the caller holds the permission the operation needs, or the credential class may not call it: a personal API key outside inference and MCP, a route that needs a signed-in person, the one-shot installation credential outside its three steps), tenant-mismatch (the organization a path, query, body or X-Tenant header names is not the caller's own), organization-provisioning, organization-suspended, organization-disabled, organization-deleted (the state of the caller's organization), user-disabled, residency-mismatch (the organization lives on another plane), organization-selection-required (a caller with more than one organization, or a platform operator, must name one in X-Organization-Id), organization-not-selectable, no-active-organization-membership, team-not-selectable (the X-Organization-Id or X-Team-Id the caller sent does not name an organization or team it may act in), identity-managed-by-provider (a person who is not a platform operator changes an organization's status, which belongs to the identity provider), support-session-required (a platform operator reads an organization's data without an open support session)
- 404
The requested resource or path was not found.
- 405
The path is claimed but the request method is not allowed. Go ServeMux returns its standard plain-text response and lists permitted methods in Allow.
- 409
The request conflicts with the current state of the resource or with an invariant it protects (a taken name, a role still in use, an organization that must not be deleted, an archived team, the last active root, a seat or service account limit).
- 500
Platform failure, rendered without internal cause details.
- 503
A dependency the operation needs is not configured on this plane or could not be reached, and nothing was changed. `error.code` names it; an operation lists the ones it can answer besides the boundary's. Retry later. Codes: support-audit-unavailable (the request boundary: an operator's read under an open support session could not be recorded, so nothing was served)
{
"provider": "oidc",
"display_name": "Acme SSO",
"issuer": "https://idp.example.com",
"client_id": "ccsio-acme",
"scopes": [
"openid",
"profile",
"email"
],
"enabled": true,
"zitadel_idp_id": "298437125649084512",
"redirect_uri": "https://login.example.com/idps/callback",
"created_at": "2026-10-14T10:05:00Z",
"updated_at": "2026-10-14T10:05:00Z"
}/identity/v1/organizations/{org}/ssoRemove the organization's single sign-on connection
- Credential
- API key
- Permission
sso.manage
Requires sso.manage. Detaches the identity provider from the organization's login policy and deletes it at Zitadel (which also removes its users' links to it), then removes the metadata.
Read more
Members that were provisioned keep their membership; they can no longer sign in through the removed provider. 404 sso-not-configured when there is none. The organization's alias, which exists only with an enabled connection, is released in the same transaction (it reads null again) and an organization.updated security event records it (details alias_from). Sensitive: audited, and records an sso.removed security event.
curl -X DELETE "https://console.us.ccsio.ai/identity/v1/organizations/{org}/sso" \
-H "Authorization: Bearer $CCSIO_API_KEY"Parameters
| Name | In | Description |
|---|---|---|
X-Request-IDoptional | header | string Optional request ULID. A valid ULID (upper or lowercase) is honored; when the header is absent or invalid, the runtime mints the id. The response X-Request-ID header carries the canonical uppercase form. |
orgrequired | path | string The organization id (C7 identifier). A value outside the pattern is refused with 400 name-invalid. |
Responses
- 204
The connection was removed.
- 400
Client refusal unrelated to authentication, including malformed input, a denied or failed request.
- 401
The request carries no credential, more than one, or one the platform does not accept, and is refused before the operation runs; `error.code` says why. Supply exactly one credential. Codes: invalid-request (no credential, or both Authorization:
Read more
Bearer and x-api-key), api-key-invalid (a value that is not a platform API key, or a key of a kind the platform does not accept), api-key-unknown, api-key-revoked, api-key-expired, bootstrap-disabled (the one-shot installation credential, once a key exists), token-malformed (a bearer token that is not a JWT), token-algorithm-unsupported, token-signature-invalid, token-claim-invalid, token-issuer-mismatch, token-audience-mismatch, token-expired, token-not-yet-valid, issuer-untrusted, issuer-residency-mismatch (the issuer belongs to another plane), issuer-resolution-unknown, jwks-unavailable, jwks-key-not-found, staff-token-inactive (a platform operator's token), staff-not-operator, staff-directory-unavailable, identity-not-provisioned (a verified identity with no account on this plane), organization-not-found (a verified identity or key whose organization this plane does not know), internal-error (a fault while the credential is checked is refused too, fail closed), context-cancelled
- 402
The organization has to pay first: its standing is payment_required.
Read more
The request boundary applies this payment gate to every route except an organization's billing routes, the caller's own context, organization list, onboarding, session report and personal keys, the public routes, and the routes of platform operators, who administer an organization that has to pay. The operations the gate covers declare this response. On /v1 the refusal is rendered in the OpenAI error format. Code: payment-required
- 403
The credential is valid but may not do this here; the request is refused by the request boundary before the handler runs, and `error.code` says which refusal. An operation may add codes of its own.
Read more
Codes: policy-denied (no role of the caller holds the permission the operation needs, or the credential class may not call it: a personal API key outside inference and MCP, a route that needs a signed-in person, the one-shot installation credential outside its three steps), tenant-mismatch (the organization a path, query, body or X-Tenant header names is not the caller's own), organization-provisioning, organization-suspended, organization-disabled, organization-deleted (the state of the caller's organization), user-disabled, residency-mismatch (the organization lives on another plane), organization-selection-required (a caller with more than one organization, or a platform operator, must name one in X-Organization-Id), organization-not-selectable, no-active-organization-membership, team-not-selectable (the X-Organization-Id or X-Team-Id the caller sent does not name an organization or team it may act in), identity-managed-by-provider (a person who is not a platform operator changes an organization's status, which belongs to the identity provider), support-session-required (a platform operator reads an organization's data without an open support session)
- 404
The requested resource or path was not found.
- 405
The path is claimed but the request method is not allowed. Go ServeMux returns its standard plain-text response and lists permitted methods in Allow.
- 409
The request conflicts with the current state of the resource or with an invariant it protects (a taken name, a role still in use, an organization that must not be deleted, an archived team, the last active root, a seat or service account limit).
- 500
Platform failure, rendered without internal cause details.
- 503
A dependency the operation needs is not configured on this plane or could not be reached, and nothing was changed. `error.code` names it; an operation lists the ones it can answer besides the boundary's. Retry later. Codes: support-audit-unavailable (the request boundary: an operator's read under an open support session could not be recorded, so nothing was served)
/identity/v1/sso/discoveryDiscover an organization's single sign-on (public)
- Credential
- None: a public operation
- Permission
- None beyond the credential
PUBLIC: no credential, because the portal's "Continue with SSO" asks it before anyone has signed in.
Read more
Returns the identifiers the portal needs to start the normal OIDC login against Zitadel restricted to the organization: the organization's Zitadel id (scope urn:zitadel:iam:org:id:<zitadel_org_id>), its primary domain (scope urn:zitadel:iam:org:domain:primary:<zitadel_org_domain>, which Login V2 needs to find a custom-id organization) and its identity provider (scope urn:zitadel:iam:org:idp:id:<idp_id>). Answers 404 not-found for an unknown, deleted or suspended organization, for one without a connection, for one whose connection is disabled and for a malformed id - indistinguishably, so it reveals nothing about which. 400 invalid-request when organization is missing; 429 rate-limited past a per-client budget (Retry-After). Read-only, never cached (Cache-Control: no-store). The organization parameter is the organization id or its alias (an organization id wins when both would match); the answer's organization is always the organization id. Email-domain discovery is POST /identity/v1/login/discovery.
curl "https://console.us.ccsio.ai/identity/v1/sso/discovery?organization=<organization>"Parameters
| Name | In | Description |
|---|---|---|
X-Request-IDoptional | header | string Optional request ULID. A valid ULID (upper or lowercase) is honored; when the header is absent or invalid, the runtime mints the id. The response X-Request-ID header carries the canonical uppercase form. |
organizationrequired | query | string The organization id (slug) or the organization's alias, as the user typed it. |
Responses
- 200
An enabled connection exists.
Schema
SSODiscoveryorganizationstringrequiredThe id of the organization the lookup resolved to (an organization id, also when the lookup was by alias).
pattern ^[a-z][a-z0-9._-]{0,63}$
zitadel_org_idstringrequiredThe organization's linked Zitadel organization: send urn:zitadel:iam:org:id:<zitadel_org_id> as an OIDC scope.
at least 1 characters
zitadel_org_domainstringrequiredThe primary domain of that Zitadel organization: send urn:zitadel:iam:org:domain:primary:<zitadel_org_domain> as an OIDC scope too. Zitadel's Login V2 reads the org id scope only for numeric ids, while core's organizations carry custom ones (cp-<ulid>); the domain scope is what tells the login screen which organization is meant.
at least 1 characters
idp_idstringrequiredThe organization's identity provider in Zitadel: urn:zitadel:iam:org:idp:id:<idp_id> as an OIDC scope is the hint that skips the provider chooser.
at least 1 characters
- 400
Client refusal unrelated to authentication, including malformed input, a denied or failed request.
- 404
The requested resource or path was not found.
- 405
The path is claimed but the request method is not allowed. Go ServeMux returns its standard plain-text response and lists permitted methods in Allow.
- 429
The caller used up a budget. `Retry-After` says how many seconds to wait. `error.code` says which budget. Codes: rate-limited (the per-client budget of the public discovery routes), registration-rate-limited (sign-up), support-session-limit (an operator's open support sessions)
- 500
Platform failure, rendered without internal cause details.
/identity/v1/organizations/{org}/sso/domainsList the organization's single sign-on email domains
- Credential
- API key
- Permission
sso.manage
Requires sso.manage. The domains the organization has added, pending and verified, oldest first. A pending domain carries the TXT record to publish.
curl "https://console.us.ccsio.ai/identity/v1/organizations/{org}/sso/domains" \
-H "Authorization: Bearer $CCSIO_API_KEY"Parameters
| Name | In | Description |
|---|---|---|
X-Request-IDoptional | header | string Optional request ULID. A valid ULID (upper or lowercase) is honored; when the header is absent or invalid, the runtime mints the id. The response X-Request-ID header carries the canonical uppercase form. |
orgrequired | path | string The organization id (C7 identifier). A value outside the pattern is refused with 400 name-invalid. |
Responses
- 200
The domains.
Schema
ListSSODomainsResponsedomainsarray of SSODomainrequireddomainstringrequiredat most 253 characters
statusstringrequiredpending: added, the TXT record not yet confirmed. verified: the TXT record matched; the organization now owns the domain for email discovery.
Allowed values:
"pending""verified"txt_recordSSODomainTXTRecord or nullrequiredThe record to publish while the domain is pending; null once it is verified.
created_atstring (date-time)requiredverified_atstring (date-time) or nullrequirednull while pending.
- 400
Client refusal unrelated to authentication, including malformed input, a denied or failed request.
- 401
The request carries no credential, more than one, or one the platform does not accept, and is refused before the operation runs; `error.code` says why. Supply exactly one credential. Codes: invalid-request (no credential, or both Authorization:
Read more
Bearer and x-api-key), api-key-invalid (a value that is not a platform API key, or a key of a kind the platform does not accept), api-key-unknown, api-key-revoked, api-key-expired, bootstrap-disabled (the one-shot installation credential, once a key exists), token-malformed (a bearer token that is not a JWT), token-algorithm-unsupported, token-signature-invalid, token-claim-invalid, token-issuer-mismatch, token-audience-mismatch, token-expired, token-not-yet-valid, issuer-untrusted, issuer-residency-mismatch (the issuer belongs to another plane), issuer-resolution-unknown, jwks-unavailable, jwks-key-not-found, staff-token-inactive (a platform operator's token), staff-not-operator, staff-directory-unavailable, identity-not-provisioned (a verified identity with no account on this plane), organization-not-found (a verified identity or key whose organization this plane does not know), internal-error (a fault while the credential is checked is refused too, fail closed), context-cancelled
- 402
The organization has to pay first: its standing is payment_required.
Read more
The request boundary applies this payment gate to every route except an organization's billing routes, the caller's own context, organization list, onboarding, session report and personal keys, the public routes, and the routes of platform operators, who administer an organization that has to pay. The operations the gate covers declare this response. On /v1 the refusal is rendered in the OpenAI error format. Code: payment-required
- 403
The credential is valid but may not do this here; the request is refused by the request boundary before the handler runs, and `error.code` says which refusal. An operation may add codes of its own.
Read more
Codes: policy-denied (no role of the caller holds the permission the operation needs, or the credential class may not call it: a personal API key outside inference and MCP, a route that needs a signed-in person, the one-shot installation credential outside its three steps), tenant-mismatch (the organization a path, query, body or X-Tenant header names is not the caller's own), organization-provisioning, organization-suspended, organization-disabled, organization-deleted (the state of the caller's organization), user-disabled, residency-mismatch (the organization lives on another plane), organization-selection-required (a caller with more than one organization, or a platform operator, must name one in X-Organization-Id), organization-not-selectable, no-active-organization-membership, team-not-selectable (the X-Organization-Id or X-Team-Id the caller sent does not name an organization or team it may act in), identity-managed-by-provider (a person who is not a platform operator changes an organization's status, which belongs to the identity provider), support-session-required (a platform operator reads an organization's data without an open support session)
- 404
The requested resource or path was not found.
- 405
The path is claimed but the request method is not allowed. Go ServeMux returns its standard plain-text response and lists permitted methods in Allow.
- 409
The request conflicts with the current state of the resource or with an invariant it protects (a taken name, a role still in use, an organization that must not be deleted, an archived team, the last active root, a seat or service account limit).
- 500
Platform failure, rendered without internal cause details.
- 503
A dependency the operation needs is not configured on this plane or could not be reached, and nothing was changed. `error.code` names it; an operation lists the ones it can answer besides the boundary's. Retry later. Codes: support-audit-unavailable (the request boundary: an operator's read under an open support session could not be recorded, so nothing was served)
/identity/v1/organizations/{org}/sso/domainsAdd an email domain to the organization's single sign-on
- Credential
- API key
- Permission
sso.manage- Request body
- Body up to 1 MiB
Requires sso.manage. Adds the domain as pending and returns the DNS TXT record to publish (name _ccsio-verification.<domain>, value ccsio-verification=<token>, the token 32 random letters and digits). Nothing is checked yet: publish the record, then call verify.
Read more
Refused when the organization already holds the domain (409 sso-domain-exists), when another organization has already VERIFIED it (409 sso-domain-taken) and past 20 domains (409 sso-domain-limit); a domain outside the shape (lowercase ASCII hostname, at least one dot, at most 253 characters, no trailing dot, not an IP address) is 400 sso-domain-invalid. Sensitive: audited, and records an sso.domain_added security event.
curl -X POST "https://console.us.ccsio.ai/identity/v1/organizations/{org}/sso/domains" \
-H "Authorization: Bearer $CCSIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"domain": "string"
}'Parameters
| Name | In | Description |
|---|---|---|
X-Request-IDoptional | header | string Optional request ULID. A valid ULID (upper or lowercase) is honored; when the header is absent or invalid, the runtime mints the id. The response X-Request-ID header carries the canonical uppercase form. |
orgrequired | path | string The organization id (C7 identifier). A value outside the pattern is refused with 400 name-invalid. |
Request body application/json
domainstringrequiredThe email domain, e.g. acme.example (not an email address, not a URL).
3 to 253 characters
Responses
- 201
The domain was added, pending.
Schema
SSODomaindomainstringrequiredat most 253 characters
statusstringrequiredpending: added, the TXT record not yet confirmed. verified: the TXT record matched; the organization now owns the domain for email discovery.
Allowed values:
"pending""verified"txt_recordobject or nullrequiredThe record to publish while the domain is pending; null once it is verified.
namestringrequiredThe record name: _ccsio-verification.<domain>.
at most 253 characters
valuestringrequiredThe record value: ccsio-verification=<token>, the token being 32 random letters and digits.
pattern ^ccsio-verification=[A-Za-z0-9]{32}$
created_atstring (date-time)requiredverified_atstring (date-time) or nullrequirednull while pending.
- 400
Client refusal unrelated to authentication, including malformed input, a denied or failed request.
- 401
The request carries no credential, more than one, or one the platform does not accept, and is refused before the operation runs; `error.code` says why. Supply exactly one credential. Codes: invalid-request (no credential, or both Authorization:
Read more
Bearer and x-api-key), api-key-invalid (a value that is not a platform API key, or a key of a kind the platform does not accept), api-key-unknown, api-key-revoked, api-key-expired, bootstrap-disabled (the one-shot installation credential, once a key exists), token-malformed (a bearer token that is not a JWT), token-algorithm-unsupported, token-signature-invalid, token-claim-invalid, token-issuer-mismatch, token-audience-mismatch, token-expired, token-not-yet-valid, issuer-untrusted, issuer-residency-mismatch (the issuer belongs to another plane), issuer-resolution-unknown, jwks-unavailable, jwks-key-not-found, staff-token-inactive (a platform operator's token), staff-not-operator, staff-directory-unavailable, identity-not-provisioned (a verified identity with no account on this plane), organization-not-found (a verified identity or key whose organization this plane does not know), internal-error (a fault while the credential is checked is refused too, fail closed), context-cancelled
- 402
The organization has to pay first: its standing is payment_required.
Read more
The request boundary applies this payment gate to every route except an organization's billing routes, the caller's own context, organization list, onboarding, session report and personal keys, the public routes, and the routes of platform operators, who administer an organization that has to pay. The operations the gate covers declare this response. On /v1 the refusal is rendered in the OpenAI error format. Code: payment-required
- 403
The credential is valid but may not do this here; the request is refused by the request boundary before the handler runs, and `error.code` says which refusal. An operation may add codes of its own.
Read more
Codes: policy-denied (no role of the caller holds the permission the operation needs, or the credential class may not call it: a personal API key outside inference and MCP, a route that needs a signed-in person, the one-shot installation credential outside its three steps), tenant-mismatch (the organization a path, query, body or X-Tenant header names is not the caller's own), organization-provisioning, organization-suspended, organization-disabled, organization-deleted (the state of the caller's organization), user-disabled, residency-mismatch (the organization lives on another plane), organization-selection-required (a caller with more than one organization, or a platform operator, must name one in X-Organization-Id), organization-not-selectable, no-active-organization-membership, team-not-selectable (the X-Organization-Id or X-Team-Id the caller sent does not name an organization or team it may act in), identity-managed-by-provider (a person who is not a platform operator changes an organization's status, which belongs to the identity provider), support-session-required (a platform operator reads an organization's data without an open support session)
- 404
The requested resource or path was not found.
- 405
The path is claimed but the request method is not allowed. Go ServeMux returns its standard plain-text response and lists permitted methods in Allow.
- 409
The request conflicts with the current state of the resource or with an invariant it protects (a taken name, a role still in use, an organization that must not be deleted, an archived team, the last active root, a seat or service account limit).
- 500
Platform failure, rendered without internal cause details.
- 503
A dependency the operation needs is not configured on this plane or could not be reached, and nothing was changed. `error.code` names it; an operation lists the ones it can answer besides the boundary's. Retry later. Codes: support-audit-unavailable (the request boundary: an operator's read under an open support session could not be recorded, so nothing was served)
/identity/v1/organizations/{org}/sso/domains/{domain}/verifyVerify an email domain by its DNS TXT record
- Credential
- API key
- Permission
sso.manage
Requires sso.manage. Looks the domain's TXT record up in DNS (name _ccsio-verification.<domain>) and marks the domain verified only when one of the record's values is exactly ccsio-verification=<token>.
Read more
A verified domain makes email discovery send the domain's users to the organization's identity provider, when its connection is enabled. A domain can be verified by at most one organization (409 sso-domain-taken for the loser of a race, or when it was already verified elsewhere). When the record is missing, the domain does not exist in DNS or no value matches the answer is 409 sso-domain-verification-failed (publish the record and retry); when DNS could not be asked (timeout, server failure) it is 503 sso-domain-dns-unavailable (retry later). A domain this organization already verified is returned as is (200). Sensitive: audited, and records an sso.domain_verified security event.
curl -X POST "https://console.us.ccsio.ai/identity/v1/organizations/{org}/sso/domains/{domain}/verify" \
-H "Authorization: Bearer $CCSIO_API_KEY"Parameters
| Name | In | Description |
|---|---|---|
X-Request-IDoptional | header | string Optional request ULID. A valid ULID (upper or lowercase) is honored; when the header is absent or invalid, the runtime mints the id. The response X-Request-ID header carries the canonical uppercase form. |
orgrequired | path | string The organization id (C7 identifier). A value outside the pattern is refused with 400 name-invalid. |
domainrequired | path | string The email domain (lowercase ASCII hostname, at least one dot, at most 253 characters, no trailing dot, not an IP address). Upper case is folded to lower case; anything else outside the shape is refused with 400 sso-domain-invalid. |
Responses
- 200
The domain is verified.
Schema
SSODomaindomainstringrequiredat most 253 characters
statusstringrequiredpending: added, the TXT record not yet confirmed. verified: the TXT record matched; the organization now owns the domain for email discovery.
Allowed values:
"pending""verified"txt_recordobject or nullrequiredThe record to publish while the domain is pending; null once it is verified.
namestringrequiredThe record name: _ccsio-verification.<domain>.
at most 253 characters
valuestringrequiredThe record value: ccsio-verification=<token>, the token being 32 random letters and digits.
pattern ^ccsio-verification=[A-Za-z0-9]{32}$
created_atstring (date-time)requiredverified_atstring (date-time) or nullrequirednull while pending.
- 400
Client refusal unrelated to authentication, including malformed input, a denied or failed request.
- 401
The request carries no credential, more than one, or one the platform does not accept, and is refused before the operation runs; `error.code` says why. Supply exactly one credential. Codes: invalid-request (no credential, or both Authorization:
Read more
Bearer and x-api-key), api-key-invalid (a value that is not a platform API key, or a key of a kind the platform does not accept), api-key-unknown, api-key-revoked, api-key-expired, bootstrap-disabled (the one-shot installation credential, once a key exists), token-malformed (a bearer token that is not a JWT), token-algorithm-unsupported, token-signature-invalid, token-claim-invalid, token-issuer-mismatch, token-audience-mismatch, token-expired, token-not-yet-valid, issuer-untrusted, issuer-residency-mismatch (the issuer belongs to another plane), issuer-resolution-unknown, jwks-unavailable, jwks-key-not-found, staff-token-inactive (a platform operator's token), staff-not-operator, staff-directory-unavailable, identity-not-provisioned (a verified identity with no account on this plane), organization-not-found (a verified identity or key whose organization this plane does not know), internal-error (a fault while the credential is checked is refused too, fail closed), context-cancelled
- 402
The organization has to pay first: its standing is payment_required.
Read more
The request boundary applies this payment gate to every route except an organization's billing routes, the caller's own context, organization list, onboarding, session report and personal keys, the public routes, and the routes of platform operators, who administer an organization that has to pay. The operations the gate covers declare this response. On /v1 the refusal is rendered in the OpenAI error format. Code: payment-required
- 403
The credential is valid but may not do this here; the request is refused by the request boundary before the handler runs, and `error.code` says which refusal. An operation may add codes of its own.
Read more
Codes: policy-denied (no role of the caller holds the permission the operation needs, or the credential class may not call it: a personal API key outside inference and MCP, a route that needs a signed-in person, the one-shot installation credential outside its three steps), tenant-mismatch (the organization a path, query, body or X-Tenant header names is not the caller's own), organization-provisioning, organization-suspended, organization-disabled, organization-deleted (the state of the caller's organization), user-disabled, residency-mismatch (the organization lives on another plane), organization-selection-required (a caller with more than one organization, or a platform operator, must name one in X-Organization-Id), organization-not-selectable, no-active-organization-membership, team-not-selectable (the X-Organization-Id or X-Team-Id the caller sent does not name an organization or team it may act in), identity-managed-by-provider (a person who is not a platform operator changes an organization's status, which belongs to the identity provider), support-session-required (a platform operator reads an organization's data without an open support session)
- 404
The requested resource or path was not found.
- 405
The path is claimed but the request method is not allowed. Go ServeMux returns its standard plain-text response and lists permitted methods in Allow.
- 409
The request conflicts with the current state of the resource or with an invariant it protects (a taken name, a role still in use, an organization that must not be deleted, an archived team, the last active root, a seat or service account limit).
- 500
Platform failure, rendered without internal cause details.
- 503
A dependency the operation needs is not configured on this plane or could not be reached, and nothing was changed. `error.code` names it; an operation lists the ones it can answer besides the boundary's. Retry later. Codes: support-audit-unavailable (the request boundary: an operator's read under an open support session could not be recorded, so nothing was served)
/identity/v1/organizations/{org}/sso/domains/{domain}Remove an email domain from the organization's single sign-on
- Credential
- API key
- Permission
sso.manage
Requires sso.manage. Removes the domain, pending or verified; a verified domain stops routing its users to the organization's identity provider at once. 404 sso-domain-not-found when the organization does not hold it. Sensitive: audited, and records an sso.domain_removed security event.
curl -X DELETE "https://console.us.ccsio.ai/identity/v1/organizations/{org}/sso/domains/{domain}" \
-H "Authorization: Bearer $CCSIO_API_KEY"Parameters
| Name | In | Description |
|---|---|---|
X-Request-IDoptional | header | string Optional request ULID. A valid ULID (upper or lowercase) is honored; when the header is absent or invalid, the runtime mints the id. The response X-Request-ID header carries the canonical uppercase form. |
orgrequired | path | string The organization id (C7 identifier). A value outside the pattern is refused with 400 name-invalid. |
domainrequired | path | string The email domain (lowercase ASCII hostname, at least one dot, at most 253 characters, no trailing dot, not an IP address). Upper case is folded to lower case; anything else outside the shape is refused with 400 sso-domain-invalid. |
Responses
- 204
The domain was removed.
- 400
Client refusal unrelated to authentication, including malformed input, a denied or failed request.
- 401
The request carries no credential, more than one, or one the platform does not accept, and is refused before the operation runs; `error.code` says why. Supply exactly one credential. Codes: invalid-request (no credential, or both Authorization:
Read more
Bearer and x-api-key), api-key-invalid (a value that is not a platform API key, or a key of a kind the platform does not accept), api-key-unknown, api-key-revoked, api-key-expired, bootstrap-disabled (the one-shot installation credential, once a key exists), token-malformed (a bearer token that is not a JWT), token-algorithm-unsupported, token-signature-invalid, token-claim-invalid, token-issuer-mismatch, token-audience-mismatch, token-expired, token-not-yet-valid, issuer-untrusted, issuer-residency-mismatch (the issuer belongs to another plane), issuer-resolution-unknown, jwks-unavailable, jwks-key-not-found, staff-token-inactive (a platform operator's token), staff-not-operator, staff-directory-unavailable, identity-not-provisioned (a verified identity with no account on this plane), organization-not-found (a verified identity or key whose organization this plane does not know), internal-error (a fault while the credential is checked is refused too, fail closed), context-cancelled
- 402
The organization has to pay first: its standing is payment_required.
Read more
The request boundary applies this payment gate to every route except an organization's billing routes, the caller's own context, organization list, onboarding, session report and personal keys, the public routes, and the routes of platform operators, who administer an organization that has to pay. The operations the gate covers declare this response. On /v1 the refusal is rendered in the OpenAI error format. Code: payment-required
- 403
The credential is valid but may not do this here; the request is refused by the request boundary before the handler runs, and `error.code` says which refusal. An operation may add codes of its own.
Read more
Codes: policy-denied (no role of the caller holds the permission the operation needs, or the credential class may not call it: a personal API key outside inference and MCP, a route that needs a signed-in person, the one-shot installation credential outside its three steps), tenant-mismatch (the organization a path, query, body or X-Tenant header names is not the caller's own), organization-provisioning, organization-suspended, organization-disabled, organization-deleted (the state of the caller's organization), user-disabled, residency-mismatch (the organization lives on another plane), organization-selection-required (a caller with more than one organization, or a platform operator, must name one in X-Organization-Id), organization-not-selectable, no-active-organization-membership, team-not-selectable (the X-Organization-Id or X-Team-Id the caller sent does not name an organization or team it may act in), identity-managed-by-provider (a person who is not a platform operator changes an organization's status, which belongs to the identity provider), support-session-required (a platform operator reads an organization's data without an open support session)
- 404
The requested resource or path was not found.
- 405
The path is claimed but the request method is not allowed. Go ServeMux returns its standard plain-text response and lists permitted methods in Allow.
- 409
The request conflicts with the current state of the resource or with an invariant it protects (a taken name, a role still in use, an organization that must not be deleted, an archived team, the last active root, a seat or service account limit).
- 500
Platform failure, rendered without internal cause details.
- 503
A dependency the operation needs is not configured on this plane or could not be reached, and nothing was changed. `error.code` names it; an operation lists the ones it can answer besides the boundary's. Retry later. Codes: support-audit-unavailable (the request boundary: an operator's read under an open support session could not be recorded, so nothing was served)
/identity/v1/login/discoveryDiscover how a user signs in (public)
- Credential
- None: a public operation
- Permission
- None beyond the credential
- Request body
- Body up to 4096 bytes
PUBLIC: no credential, because the portal's login asks it before anyone has signed in. The body is exactly one of {"email"} and {"alias"}.
Read more
By alias: method sso (an active organization with an ENABLED connection) with the alias, zitadel_org_id, zitadel_org_domain and idp_id; every other alias - unknown, deleted or suspended, without a linked Zitadel organization, or belonging to an organization WITHOUT an enabled connection - is the same 404 not-found, indistinguishably. An alias exists only for an organization with single sign-on, and a plain login restricted to an organization cannot work (the identity provider appends the organization's primary domain to the login name), so there is no method password answer for an alias. By email: only the address's domain is read; when an organization has VERIFIED that domain and has an enabled connection the answer is method sso with the same fields, in every other case - unknown domain, pending or unverified domain, disabled or missing connection - it is exactly {"method":"password"}. No user is looked up, so the answer never depends on whether the account exists. 400 invalid-request when the body is not exactly one well-formed email or alias; 413 past 4096 bytes; 429 rate-limited past a per-client budget (Retry-After), shared with GET /identity/v1/sso/discovery. Never cached (Cache-Control: no-store).
curl -X POST "https://console.us.ccsio.ai/identity/v1/login/discovery" \
-H "Content-Type: application/json" \
-d 'null'Parameters
| Name | In | Description |
|---|---|---|
X-Request-IDoptional | header | string Optional request ULID. A valid ULID (upper or lowercase) is honored; when the header is absent or invalid, the runtime mints the id. The response X-Request-ID header carries the canonical uppercase form. |
Request body application/json
Exactly one of email and alias. The email travels in the body, never in a URL, and core never logs it. Naming both, neither, or an empty value is refused with 400 invalid-request.
One of
#1anyoptional#2anyoptional
Responses
- 200
How to sign in.
Schema
LoginDiscoverymethodstringrequiredsso: the organization's own identity provider. password: the normal Zitadel login (a lookup by email only).
Allowed values:
"sso""password"aliasstringoptionalAn organization's alias: the public name its sign-in link is built from (https://<console>/<alias>). 3 to 63 lowercase letters, digits and hyphens, starting and ending with a letter or digit. Unique across the plane and editable by the organization's Root and Admin; the organization's internal id never changes. An alias exists only for an organization with an ENABLED single sign-on connection: setting or changing one without it is refused with 409 alias-requires-sso, and removing or disabling the connection releases the alias (it reads null again). Reserved words (every top-level console path - api, auth, login, logout, register, onboarding, organizations, identity, audit, models, deployments, playground, settings, request-logs, metering, memory, registrars, usage - and common words such as admin, help, docs, static, assets, health, ready, www, console, app) and anything shaped like a generated organization id (org-<26 characters>) are refused with 400 alias-reserved; a malformed one with 400 alias-invalid; one already in use with 409 alias-taken.
3 to 63 characters · pattern ^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$
zitadel_org_idstringoptionalThe organization's linked Zitadel organization: send urn:zitadel:iam:org:id:<zitadel_org_id> as an OIDC scope.
at least 1 characters
zitadel_org_domainstringoptionalThe primary domain of that Zitadel organization: send urn:zitadel:iam:org:domain:primary:<zitadel_org_domain> as an OIDC scope too (Login V2 finds a custom-id organization by it).
at least 1 characters
idp_idstringoptionalmethod sso only: the organization's identity provider in Zitadel; urn:zitadel:iam:org:idp:id:<idp_id> is the hint that skips the provider chooser.
at least 1 characters
- 400
Client refusal unrelated to authentication, including malformed input, a denied or failed request.
- 404
The requested resource or path was not found.
- 405
The path is claimed but the request method is not allowed. Go ServeMux returns its standard plain-text response and lists permitted methods in Allow.
- 413
The body exceeds 4096 bytes. Codes: request-body-too-large
- 429
The caller used up a budget. `Retry-After` says how many seconds to wait. `error.code` says which budget. Codes: rate-limited (the per-client budget of the public discovery routes), registration-rate-limited (sign-up), support-session-limit (an operator's open support sessions)
- 500
Platform failure, rendered without internal cause details.
Generated from the platform contracts at commit 5f833620.