Skip to main content
DocumentationIdentity and access · console.latam.ccsio.ai

Single sign-on

An organization's own identity provider connection and the email domains that send its people to it, and the discoveries the login page calls before anyone has signed in.

Documentation menu

The descriptions of the operations and fields are the contract’s own, in English.

GET/identity/v1/organizations/{org}/sso

Read 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
curl "https://console.latam.ccsio.ai/identity/v1/organizations/{org}/sso" \
  -H "Authorization: Bearer $CCSIO_API_KEY"

Parameters

NameInDescription
X-Request-ID
optional
headerstring

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.

org
required
pathstring

The organization id (C7 identifier). A value outside the pattern is refused with 400 name-invalid.

Responses

  • 200

    The connection.

    Schema
    SSOConnection
    • providerstringrequired

      The 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_namestringrequired

      The name the connection is shown under (also the provider's name in the identity provider's login page).

      1 to 64 characters

    • issuerstringoptional

      oidc connections only: the OIDC issuer URL (https). Absent for entra.

      at most 200 characters

    • tenant_idstringoptional

      entra connections only: the Entra tenant - a tenant GUID or domain, or one of organizations | common | consumers. Absent for oidc.

      at most 253 characters

    • client_idstringrequired

      1 to 200 characters

    • scopesarray of stringrequired

      The scopes the identity provider is asked for.

      at most 20 items

    • enabledbooleanrequired

      Whether 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_idstringrequired

      The id of the organization-level identity provider inside the organization's linked Zitadel organization.

    • redirect_uristring (uri)required

      The redirect URI the customer must register at their identity provider (for Entra: the app registration's Web redirect URI).

    • created_atstring (date-time)required
    • updated_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)

PUT/identity/v1/organizations/{org}/sso

Create 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
curl -X PUT "https://console.latam.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

NameInDescription
X-Request-ID
optional
headerstring

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.

org
required
pathstring

The organization id (C7 identifier). A value outside the pattern is refused with 400 name-invalid.

Request body application/json

PutSSORequest
  • providerstringrequired

    The 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_namestringrequired

    1 to 64 characters

  • issuerstringoptional

    Required for oidc, refused for entra. An https URL without credentials, query or fragment.

    at most 200 characters

  • tenant_idstringoptional

    Required for entra, refused for oidc: a tenant GUID or domain, or organizations | common | consumers.

    at most 253 characters

  • client_idstringrequired

    1 to 200 characters

  • client_secretstringoptional

    The client secret issued by the identity provider. Never stored, logged or returned by core.

    1 to 200 characters

  • scopesarray of stringoptional

    Optional; 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

  • enabledbooleanoptional

    Optional; true when the connection is created, unchanged on update.

Responses

  • 200

    The connection was updated.

    Schema
    SSOConnection
    • providerstringrequired

      The 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_namestringrequired

      The name the connection is shown under (also the provider's name in the identity provider's login page).

      1 to 64 characters

    • issuerstringoptional

      oidc connections only: the OIDC issuer URL (https). Absent for entra.

      at most 200 characters

    • tenant_idstringoptional

      entra connections only: the Entra tenant - a tenant GUID or domain, or one of organizations | common | consumers. Absent for oidc.

      at most 253 characters

    • client_idstringrequired

      1 to 200 characters

    • scopesarray of stringrequired

      The scopes the identity provider is asked for.

      at most 20 items

    • enabledbooleanrequired

      Whether 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_idstringrequired

      The id of the organization-level identity provider inside the organization's linked Zitadel organization.

    • redirect_uristring (uri)required

      The redirect URI the customer must register at their identity provider (for Entra: the app registration's Web redirect URI).

    • created_atstring (date-time)required
    • updated_atstring (date-time)required
  • 201

    The connection was created.

    Schema
    SSOConnection
    • providerstringrequired

      The 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_namestringrequired

      The name the connection is shown under (also the provider's name in the identity provider's login page).

      1 to 64 characters

    • issuerstringoptional

      oidc connections only: the OIDC issuer URL (https). Absent for entra.

      at most 200 characters

    • tenant_idstringoptional

      entra connections only: the Entra tenant - a tenant GUID or domain, or one of organizations | common | consumers. Absent for oidc.

      at most 253 characters

    • client_idstringrequired

      1 to 200 characters

    • scopesarray of stringrequired

      The scopes the identity provider is asked for.

      at most 20 items

    • enabledbooleanrequired

      Whether 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_idstringrequired

      The id of the organization-level identity provider inside the organization's linked Zitadel organization.

    • redirect_uristring (uri)required

      The redirect URI the customer must register at their identity provider (for Entra: the app registration's Web redirect URI).

    • created_atstring (date-time)required
    • updated_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)

Example response
{
  "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"
}
DELETE/identity/v1/organizations/{org}/sso

Remove 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
curl -X DELETE "https://console.latam.ccsio.ai/identity/v1/organizations/{org}/sso" \
  -H "Authorization: Bearer $CCSIO_API_KEY"

Parameters

NameInDescription
X-Request-ID
optional
headerstring

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.

org
required
pathstring

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)

GET/identity/v1/sso/discovery

Discover 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
curl "https://console.latam.ccsio.ai/identity/v1/sso/discovery?organization=<organization>"

Parameters

NameInDescription
X-Request-ID
optional
headerstring

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.

organization
required
querystring

The organization id (slug) or the organization's alias, as the user typed it.

Responses

  • 200

    An enabled connection exists.

    Schema
    SSODiscovery
    • organizationstringrequired

      The 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_idstringrequired

      The organization's linked Zitadel organization: send urn:zitadel:iam:org:id:<zitadel_org_id> as an OIDC scope.

      at least 1 characters

    • zitadel_org_domainstringrequired

      The 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_idstringrequired

      The 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.

GET/identity/v1/organizations/{org}/sso/domains

List 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
curl "https://console.latam.ccsio.ai/identity/v1/organizations/{org}/sso/domains" \
  -H "Authorization: Bearer $CCSIO_API_KEY"

Parameters

NameInDescription
X-Request-ID
optional
headerstring

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.

org
required
pathstring

The organization id (C7 identifier). A value outside the pattern is refused with 400 name-invalid.

Responses

  • 200

    The domains.

    Schema
    ListSSODomainsResponse
    • domainsarray of SSODomainrequired
      • domainstringrequired

        at most 253 characters

      • statusstringrequired

        pending: 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 nullrequired

        The record to publish while the domain is pending; null once it is verified.

      • created_atstring (date-time)required
      • verified_atstring (date-time) or nullrequired

        null 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)

POST/identity/v1/organizations/{org}/sso/domains

Add 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
curl -X POST "https://console.latam.ccsio.ai/identity/v1/organizations/{org}/sso/domains" \
  -H "Authorization: Bearer $CCSIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "domain": "string"
}'
Built from the schema’s required members: replace the values.

Parameters

NameInDescription
X-Request-ID
optional
headerstring

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.

org
required
pathstring

The organization id (C7 identifier). A value outside the pattern is refused with 400 name-invalid.

Request body application/json

AddSSODomainRequest
  • domainstringrequired

    The 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
    SSODomain
    • domainstringrequired

      at most 253 characters

    • statusstringrequired

      pending: 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 nullrequired

      The record to publish while the domain is pending; null once it is verified.

      • namestringrequired

        The record name: _ccsio-verification.<domain>.

        at most 253 characters

      • valuestringrequired

        The 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)required
    • verified_atstring (date-time) or nullrequired

      null 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)

POST/identity/v1/organizations/{org}/sso/domains/{domain}/verify

Verify 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
curl -X POST "https://console.latam.ccsio.ai/identity/v1/organizations/{org}/sso/domains/{domain}/verify" \
  -H "Authorization: Bearer $CCSIO_API_KEY"

Parameters

NameInDescription
X-Request-ID
optional
headerstring

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.

org
required
pathstring

The organization id (C7 identifier). A value outside the pattern is refused with 400 name-invalid.

domain
required
pathstring

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
    SSODomain
    • domainstringrequired

      at most 253 characters

    • statusstringrequired

      pending: 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 nullrequired

      The record to publish while the domain is pending; null once it is verified.

      • namestringrequired

        The record name: _ccsio-verification.<domain>.

        at most 253 characters

      • valuestringrequired

        The 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)required
    • verified_atstring (date-time) or nullrequired

      null 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)

DELETE/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
curl -X DELETE "https://console.latam.ccsio.ai/identity/v1/organizations/{org}/sso/domains/{domain}" \
  -H "Authorization: Bearer $CCSIO_API_KEY"

Parameters

NameInDescription
X-Request-ID
optional
headerstring

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.

org
required
pathstring

The organization id (C7 identifier). A value outside the pattern is refused with 400 name-invalid.

domain
required
pathstring

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)

POST/identity/v1/login/discovery

Discover 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
curl -X POST "https://console.latam.ccsio.ai/identity/v1/login/discovery" \
  -H "Content-Type: application/json" \
  -d 'null'
Built from the schema’s required members: replace the values.

Parameters

NameInDescription
X-Request-ID
optional
headerstring

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

LoginDiscoveryRequest

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
    LoginDiscovery
    • methodstringrequired

      sso: the organization's own identity provider. password: the normal Zitadel login (a lookup by email only).

      Allowed values: "sso""password"

    • aliasstringoptional

      An 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_idstringoptional

      The organization's linked Zitadel organization: send urn:zitadel:iam:org:id:<zitadel_org_id> as an OIDC scope.

      at least 1 characters

    • zitadel_org_domainstringoptional

      The 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_idstringoptional

      method 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.

Explore the platform

Get your first API key

Registration is open. Four steps to your first call: create your account, create your organization, create your API key, and start using it.

  1. 01Create your account
  2. 02Create your organization
  3. 03Create your API key
  4. 04Start using it