---
title: "Read the verdict of a configuration test (owner)"
method: POST
path: "/v1/organizations/{organization_id}/sso/test/result"
tags: ["organizations"]
---

# Read the verdict of a configuration test (owner)

`POST /v1/organizations/{organization_id}/sso/test/result`

## 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/test/result`
- Auth: none declared

Redeem the ceremony's intent and report what a member's login would hit.

``POST`` on a sub-resource despite being a read: the intent token is a
bearer credential, and Zitadel already exposes it in a browser URL -- it
must not travel in this service's request line as well.

Authenticates nobody -- no session, no account, no IdP link. Writes one
thing, and only on a pass: ``verified_at`` / ``verified_template_id``, which
is what ``require_sso`` is gated on. An intent naming a template other than
this connection's is refused rather than reported, so a redeemable intent
obtained elsewhere cannot make a broken connection look tested.

Args:
    organization_id (int): The parent organization.
    payload (SsoTestResultRequest): The intent id and token from the return
        URL.
    db (AsyncSession): Request-scoped session.
    context (ActiveContext): Resolved caller.

Returns:
    SsoTestVerdictResponse: The verdict and the values it compared.

## Path parameters

- `organization_id` integer, required

## Request body

- SsoTestResultRequest: The intent the ceremony's return URL handed back. Both values arrive in the browser's query string, so they are already exposed to history and access logs by Zitadel's own design. Sent in a body rather than as query parameters here so this service does not add a second copy of the token to its own request logs. The bounds match the federated login's (``FederatedAccountRequest``): the token's length is Zitadel's to choose, and v4.19 grew it from 46 characters to a 257-character signed token, which a 200 cap refused with a 422.
  - `intent_id` string, required: The `id` query parameter from the return URL.
  - `intent_token` string, required: The `token` query parameter from the return URL. Never logged or stored server-side.

## Response `200`

Successful Response

- SsoTestVerdictResponse: What the ceremony found, in the terms the customer has to act on. Specific where a federated *login* is deliberately opaque, and the difference is the caller: a login answers an unauthenticated stranger, this answers the organization's own owner about their own connection. Naming the fault is the entire point -- "SSO did not work" is what they already knew. **Three outcomes report no detail at all.** On `wrong_template`, `no_template_on_intent` and `ceremony_expired`, every value below that is read off the ceremony is withheld and comes back null -- the ceremony was not this connection's, so its contents are not the caller's to read. Only `expected_issuer`, taken from the caller's own connection, is carried. Read a null on those outcomes as "withheld", never as "the provider sent nothing".
  - `passed` boolean, required: Whether a member's login would have been accepted. The one field a wizard should gate on; `outcome` says why when it is false.
  - `outcome` 'passed' | 'ceremony_expired' | 'no_template_on_intent' | 'wrong_template' | 'no_expected_issuer' | 'no_assertion' | 'unreadable_assertion' | 'mismatched' | 'no_asserted_email', required: What a completed configuration test found. Attributes: PASSED: The ceremony would have been accepted by the login path. NO_TEMPLATE_ON_INTENT: The redeemed intent names no IdP template. A broken ceremony rather than a configuration fault. WRONG_TEMPLATE: The intent names a template that is not this connection's. Refused rather than reported against this connection: an intent naming a template is not evidence it belongs to whoever redeems it. NO_EXPECTED_ISSUER: SAML answered, but no EntityID is stored to check it against, so nothing verified which provider signed. An administrator re-sending the customer's metadata records one. NO_ASSERTION: The connection expects an EntityID but the ceremony carried no assertion. UNREADABLE_ASSERTION: An assertion arrived whose ``Issuer`` could not be read. MISMATCHED: The assertion was signed by a different EntityID than the one stored -- usually the customer re-created their IdP. Repaired by an administrator re-sending their metadata, which re-derives the stored EntityID. NO_ASSERTED_EMAIL: Every tenancy check passed but no address could be read: none in Zitadel's claims (always the case for SAML) and none in the assertion -- no ``emailAddress`` NameID and no single email attribute (see :mod:`._saml_email`). Invisible without a test: a login refuses such an identity with nothing to tell the customer. CEREMONY_EXPIRED: The ceremony was never completed, or was completed too long ago. **The one outcome that says nothing about the configuration** -- the customer left the wizard open, which is the most predictable thing to happen in a wizard. Tell them to start again, not to look at their IdP.
  - `template_id` string, nullable: The Zitadel IdP template the ceremony ran against. Null on the three withholding outcomes above -- including `wrong_template`, where the intent did name one and it is deliberately not returned.
  - `expected_issuer` string, nullable: The SAML EntityID stored for this connection. Null for an OIDC connection, and null for a SAML one that stores none -- reported as `no_expected_issuer` only when the ceremony carried an assertion to compare it against.
  - `asserted_issuer` string, nullable: The EntityID that actually signed the assertion, when one could be read. Compared against `expected_issuer`; when they differ, re-send the customer's metadata through the admin IdP update so the stored EntityID tracks it again.
  - `asserted_email` string, nullable: The address the provider released. Null on the three withholding outcomes above; on any other outcome it means none could be read -- for SAML, neither an `emailAddress` NameID nor an email attribute (`email`, `emailaddress`, `mail`, or the Entra claim URI). Logins refuse such an identity, and the customer has to release the address in one of those.
  - `tester_link` 'linked' | 'already_linked' | 'other_address' | 'held_by_another' | 'not_linked': What a passing test did about the tester's own account. A passing ceremony is the strongest evidence this system ever gets that the person in front of it controls an identity at the organization's provider: they are signed in to Ethiack, and they have just completed a real sign-in at the provider in the same browser. So the tester's own account is linked to that identity -- keyed on the identifier the provider actually sent, not on an address, which also makes it work for a template asserting opaque persistent ids. Only ever the tester, and only when the provider asserted the tester's own address. Anything else stays with ``POST .../sso/members/link``. Attributes: LINKED: This call linked the tester's account to the identity. ALREADY_LINKED: The identity already resolves to the tester's account. OTHER_ADDRESS: The provider asserted an address that is not the tester's -- typically an IT administrator testing with their own account. Linking it would let that person sign in as the tester, so nothing is linked. HELD_BY_ANOTHER: The identity already belongs to a different account. NOT_LINKED: Anything else: the tester has no Zitadel identity, is already linked to this provider under another identifier, may not link members (or that could not be checked), or Zitadel refused or was unreachable. The verdict stands either way; linking is a convenience on top of it.

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