---
title: "Point an organization at its Zitadel IdP template (admin)"
method: PUT
path: "/v1/organizations/{organization_id}/sso/template"
tags: ["organizations"]
---

# Point an organization at its Zitadel IdP template (admin)

`PUT /v1/organizations/{organization_id}/sso/template`

## Request

- Base URL: `https://api.ethiack.com` (the document declares no server; this is the origin it was published from)
- URL: `https://api.ethiack.com/v1/organizations/{organization_id}/sso/template`
- Auth: none declared

Create the connection, or re-point an existing one.

Idempotent by organization: there is at most one connection per
organization, so a repeat call points the existing row rather than inserting
a duplicate. The branch is chosen from the caller's *scoped* read, so a
caller who cannot see the existing row falls to the create arm and is
refused there -- by their own lack of the verb, or by the conflict on the
organization slot.

Through the workflow rather than the connection service: naming a template
id means reading that template back from Zitadel to derive the EntityID
stored alongside it, which an entity service may not do. See
:meth:`~phoenix.services.workflows.users.sso_idp_template.SsoIdpTemplateWorkflow.point_at_template`
for what a connection pointed at a SAML template without one does.

Args:
    organization_id (int): The parent organization.
    payload (SsoTemplateRequest): Slug and template id.
    db (AsyncSession): Request-scoped session.
    context (ActiveContext): Resolved caller.

Returns:
    SsoConnectionResponse: The connection as it now stands.

## Path parameters

- `organization_id` integer, required

## Request body

- SsoTemplateRequest: Admin onboarding body: which Zitadel template this org logs in through.
  - `login_slug` string, required: Org-scoped entry token in the login URL: a lower-case DNS label, globally unique. It ends up in the customer's IdP configuration, so changing it invalidates entry links already handed out.
  - `zitadel_idp_id` string, nullable, required: The Zitadel IdP template this organization's federated logins run against. Required rather than optional so omitting it cannot quietly unpoint a working connection; send an explicit null to unpoint one, which is refused while it is still enabled.

## Response `200`

Successful Response

- SsoConnectionResponse: An organization's connection, as an admin or its owner sees it. Wider than ``GET /auth/sso/{login_slug}``, which is unauthenticated and deliberately omits everything about what happens to *accounts*. **Carries the customer-facing SAML endpoints, which the write responses also carry.** They used to come back only from ``POST|PUT .../sso/idp``, both admin-only, so the organization owner -- who can already read this resource -- had no way to see the ACS and metadata URLs they have to paste into their own IdP. Every one of them is derived from ``zitadel_idp_id`` by ``TemplateEndpoints`` and is published, non-secret metadata that Zitadel itself serves unauthenticated, so returning them here grants no authority the reader did not already hold. The descriptions are copied verbatim from ``IdpTemplateResponse`` so the two surfaces cannot drift into describing the same URL differently. Nothing about the *provider* is echoed: no metadata document, no OIDC client id and no secret. Those are inputs, admin-only, and this is a read every owner holds.
  - `login_slug` string, required: Org-scoped entry token in the login URL.
  - `zitadel_idp_id` string, nullable: The Zitadel IdP template, or null if unpointed.
  - `enabled` boolean, required: Whether federated login is served.
  - `require_sso` boolean, required: Whether members must authenticate through the IdP.
  - `provision_on_first_login` boolean, required: Whether a first federated login may mint an account. Armed only through the provisioning workflow, which requires a DNS-proven domain; not settable here.
  - `provisioned_role` 'owner' | 'admin' | 'analyst' | 'viewer', required: Sub-role within a :data:`ActorKind.ORGANIZATION` context. Snake_case wire values are used in the DB, in JWT claims synced from Zitadel, and as components of Cerbos role strings (e.g. ``"org_admin"``). Attributes: OWNER: Full authority over the organization. ADMIN: Administrative authority below the owner. ANALYST: Read and write on engagements, findings, assets. VIEWER: Read-only.
  - `verified_at` string, date-time, nullable: When a configuration test last passed against the template now in service, or null if none has. `require_sso` cannot be set true while this is null: mandating SSO on a connection nobody has proved works locks the organization out, since its members hold no password. Re-pointing the template clears it, so a verification never outlives the thing it verified.
  - `protocol` 'saml' | 'unknown', nullable: Which protocol family the template configures, as far as Phoenix can tell. Null when the connection points at no template. `saml` when `expected_issuer` is stored -- the mint workflow refuses to record a SAML template without one, so its presence is a positive signal, and it is the same discriminator the login path keys on (`sso_jit_login._issuer_verdict`). `unknown` for a connection that is pointed but stores no issuer: that is an OIDC template, or a SAML one pointed at by `PUT .../sso/template` alone, which records no issuer -- and nothing on the row separates the two. Answering `oidc` there would be a guess, and the guess would hide the ACS and metadata URLs a SAML customer cannot finish onboarding without.
  - `acs_url` string, nullable: SAML only. The Assertion Consumer Service URL the customer must configure in their IdP. Unique to this template, which is what keeps one customer's assertions off another's connection.
  - `sp_metadata_url` string, nullable: SAML only. Zitadel's **SP** metadata for this template, for customers who prefer importing it over manual entry. Named for the direction: the request bodies' `metadata_url` is the opposite one, the customer's IdP metadata that Zitadel fetches.
  - `certificate_url` string, nullable: SAML only. Zitadel's signing certificate for this template.
  - `slo_url` string, nullable: SAML only. Single-logout endpoint for this template.

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/ethiack/apis/ethiack-api.md) - [All operations](https://skmtc.dev/ethiack/apis/ethiack-api/llms.txt) - [OpenAPI document](https://skmtc.dev/ethiack/apis/ethiack-api/revisions/4eaba89bfebf?raw)
