> ## Documentation Index
> Fetch the complete documentation index at: https://docs.knoxcall.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SSO Troubleshooting

> Every SAML error code KnoxCall can put on the login page, every refusal the SSO settings form can return, and what to do about each.

# SSO Troubleshooting

Two surfaces produce SSO errors, and they behave differently on purpose.

* **The sign-in path** — `/saml/<slug>/login`, `/saml/<slug>/acs`, `/saml/<slug>/slo` — is **public and unauthenticated**. Anyone who knows a workspace slug can reach it. So its answers are deliberately uninformative: the person is sent to the login page with a short `error` code, and the reason lives in your workspace's audit and security events, where only an administrator can read it. A public endpoint that explained itself would explain itself to anybody.
* **The settings form** — everything under `/admin/saml` — is behind the owner-or-admin role and a recent re-authentication, so it says exactly what is wrong with the value you typed. Those messages are quoted verbatim below.

<Note>
  **Start here for almost everything.** Most first-login failures are one of three things: the audience does not match (KnoxCall's SP entity ID *is* its metadata URL, `https://acme.knoxcall.com/saml/acme/metadata`, not the origin), the assertion is older than five minutes, or the asserted address is at a domain the workspace has not [proven](/sso/domains). Those `/saml/<workspace>/…` URLs are the `default` connection's — the `/saml/` alias **is** that connection. Any other connection has its own set at `https://acme.knoxcall.com/sso/<workspace>/<connection>/{metadata,acs,login,slo}`, so check the one you are debugging on its **Certificates** tab before comparing anything. Fetch your own metadata document and compare it against what your IdP has, before anything else.
</Note>

## Error codes on the login path

The code arrives as `?error=<code>` on the login page, or as a JSON `{ "error": "<code>" }` from the metadata and logout endpoints.

| Code                          | Where it comes from                          | What it means, and what to do                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| ----------------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `saml_not_configured`         | any `/saml/<slug>/…` route, 404              | No **active** SAML connection for that workspace slug. Either the slug is wrong, SSO was never enabled, or the connection is in `draft`, `testing` or `disabled`. Set it to active on the SSO settings page. The same 404 answers a workspace under an operator hold — it does not distinguish, and that is deliberate.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `saml_connection_ambiguous`   | any `/saml/<slug>/…` route, 409              | The alias resolves to the `default` connection; this workspace has two or more active connections and **none** of them carries that handle, so the shared URL cannot tell which identity provider an assertion is for. What the caller is told, in full: *"More than one SSO connection is active for this workspace and none of them is the one this shared sign-in URL belongs to, so it cannot tell which one you meant. Your administrator has a connection-specific URL for each of them; ask them for the one for your identity provider."* — and the logout endpoint's twin is the same sentence with *"…this shared logout URL belongs to…"*. Nothing needs disabling. Point each identity provider at its own connection's `/sso/<workspace>/<handle>/{metadata,acs,login,slo}` URLs — they name a connection and cannot be ambiguous — or keep the identity provider that was there first on the connection named `default`, which the alias belongs to. The response names no connection: an unauthenticated caller must not learn the shape of your identity estate. The SSO settings page names them, and each connection's **Certificates** tab carries its five URLs. |
| `sso_plan_required`           | `/login` (403) and the ACS                   | The workspace has no Enterprise entitlement **and no existing connection to grandfather**. A *lapse* on a workspace that already had Enterprise never produces this: existing logins keep working through a billing lapse by design.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `saml_metadata_unavailable`   | `/saml/<slug>/metadata`                      | The metadata document could not be generated. Retry; if it persists, the connection is missing something the document needs — check it has an IdP entry point. Note the endpoint is **not** plan-gated: IdP setup leaks nothing, so metadata stays fetchable.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `saml_login_failed`           | `/saml/<slug>/login`                         | KnoxCall could not build the AuthnRequest — nearly always a malformed IdP sign-in URL on the connection. Re-check it against your IdP.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `saml_idp_initiated_disabled` | the ACS                                      | An **unsolicited** assertion arrived (an app-launcher tile) and this connection has not opted in. Either point the tile at `https://acme.knoxcall.com/saml/acme/login` so it becomes an ordinary SP-initiated sign-in, or switch the setting on — see [IdP-initiated sign-in](/sso/overview#idp-initiated-sign-in).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `saml_replay`                 | the ACS                                      | This assertion's ID has already been used. Each is accepted exactly once. A person double-clicking a stale browser tab produces this; so does a replayed capture. Sign in again.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `saml_failed`                 | the ACS                                      | The catch-all: signature, audience, timing, or the account rules below. The person is given nothing more; the reason is in your workspace's **security events** as `auth.login_failed` with the detail `saml acs: assertion rejected`. The next section lists every underlying cause.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `saml_logout_message_missing` | the SLO endpoint                             | A request reached `/saml/<slug>/slo` carrying neither a `SAMLRequest` nor a `SAMLResponse`. Usually a health check or a bookmark, not a fault.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `saml_logout_unsigned`        | the SLO endpoint                             | A redirect-binding logout message arrived without a signature. KnoxCall refuses it — otherwise anyone who could reach the URL could sign out any user whose email address they could guess. Enable message signing on your IdP's logout endpoint.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `saml_logout_invalid`         | the SLO endpoint                             | The logout message did not verify, or was not the shape it claimed. Usually the signing certificate on the connection is not the one the IdP is signing logout messages with — the same rotation applies to logout as to sign-in.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `saml_logout_stale`           | the SLO endpoint                             | The message's `IssueInstant` is more than **five minutes** old — the same ceiling as an assertion. A queued or retried logout can arrive late; the session is ended by the next sign-out either way.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `saml_logout_replay`          | the SLO endpoint                             | That logout message has already been processed. Accepted once, exactly like an assertion.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `domain_not_verified`         | `POST /admin/saml/domains/<id>/verify` (400) | The DNS proof did not check out. The response carries the reason as `error_description` — the four possibilities are in [Verifying a domain](/sso/domains#when-verification-fails).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |

## What is really behind `saml_failed`

The assertion verified, and then an account rule refused it. The person sees `saml_failed`; you see one of these in the workspace's events. All six are quoted verbatim.

| Message                                                                                                                                                                                                | What happened                                                                                                                                                                                                                                                                                                                                                                                                                                 | What to do                                                                                                                     |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| *"This email already has a KnoxCall account that is not a member of this tenant. Ask an administrator to invite you, or have them add your email domain to the tenant's verified SAML domains."*       | The address already has a KnoxCall account somewhere, and it is not a member here. Linking it on the strength of an assertion alone would be account takeover — your IdP signs its own assertions, so it could claim any address.                                                                                                                                                                                                             | Either invite the person to the workspace directly, or [prove the domain](/sso/domains) and list it on the connection.         |
| *"This identity is known to this tenant but is not currently a member, and the KnoxCall account it is bound to is not within the tenant's verified SAML domains. Ask an administrator to invite you."* | The IdP's NameID is already bound to a KnoxCall account from an earlier sign-in, that account is no longer a member (an administrator removed it), and the account's **own** address is outside your verified domains. The address in the assertion does not decide this: the membership would land on the bound account, so that account is what your domains must cover. This is typically an invited outside collaborator who was removed. | Invite them again from the team page if they should have access. Changing the address your IdP sends will not help, by design. |
| *"No KnoxCall account exists for this email and JIT provisioning is disabled for this tenant."*                                                                                                        | New address, and **Create accounts automatically** is off.                                                                                                                                                                                                                                                                                                                                                                                    | Invite them, or turn just-in-time provisioning on.                                                                             |
| *"Just-in-time SAML provisioning requires this tenant to have verified email domains configured. Add them under SSO settings, or invite users directly…"*                                              | Just-in-time provisioning is on, but the connection lists no verified domain. An empty list means invite-only, never allow-everything — otherwise a workspace could seed accounts for addresses it does not control.                                                                                                                                                                                                                          | [Prove a domain](/sso/domains) and list it.                                                                                    |
| *"This email domain is not permitted for SAML sign-in to this tenant."*                                                                                                                                | The asserted address is at a domain outside the proven, listed set. Note that proving `acme.com` covers `eu.acme.com`; proving a subdomain never covers its parent.                                                                                                                                                                                                                                                                           | Prove and list the domain the address is actually at.                                                                          |
| *"This KnoxCall account is deactivated. Ask an administrator to reactivate it before signing in."*                                                                                                     | The account exists and is deactivated. A signed assertion does not re-admit a deactivated account — that would make deactivation a suggestion.                                                                                                                                                                                                                                                                                                | Reactivate them on the team page, or leave it: this is deactivation working.                                                   |

There is a seventh, for a misconfigured connection rather than a person: if the connection's default role is anything an IdP-asserted login may not confer, **every** login fails cleanly with a message naming the role and telling you to lower it. That state is unreachable through the settings form, which refuses the value at save time.

## Refusals from the SSO settings form

These come back as a `400` with the message shown to you directly. Fix the value and save again.

### The IdP sign-in URL and the single-logout URL

Both are **dereferenced** — a browser is sent to them — so both are held to the same rule.

| Message                                                                                                                                                                                                                                    | What to do                                                                                                                                   |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
| *"The IdP sign-in URL is required."*                                                                                                                                                                                                       | Every connection needs one. It is the URL your IdP's SAML setup screen calls the SSO URL, login URL or Identity Provider Single Sign-On URL. |
| *"…must be a full URL, for example [https://login.example.com/sso/saml](https://login.example.com/sso/saml)."*                                                                                                                             | You pasted a bare host or a path. Include the scheme.                                                                                        |
| The **sign-in URL** must use https, *"The AuthnRequest carries the login request ID that binds the assertion to this login, and over http it can be read and rewritten in transit."*                                                       | `http` is not a lesser version of https here.                                                                                                |
| The **single-logout URL** must too, *"A LogoutRequest carries the NameID — usually the person's email address — and the SessionIndex, and the browser is redirected to this URL, so over http both are readable by anything on the path."* | Over `http` the binding is decoration and the identifiers are public.                                                                        |
| *"…must not contain a username or password. A URL like [https://login.example.com@somewhere-else.example/](https://login.example.com@somewhere-else.example/) points at somewhere-else.example, not at your IdP."*                         | Almost always a mis-paste. No IdP endpoint has userinfo in it.                                                                               |
| *"…contains control characters."*                                                                                                                                                                                                          | A tab or newline came along with the paste. Retype it.                                                                                       |

The IdP **entity ID** is not held to any of this: it is a name, not an address, nothing dereferences it, and Okta's genuinely begins `http://www.okta.com/`. It is checked only for length and control characters.

### The signing certificate

| Message                                                                                                                                    | What to do                                                                                                                                                                             |
| ------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| *"That does not look like an X.509 certificate. Paste the IdP signing certificate, including the BEGIN/END lines if your IdP shows them."* | What you pasted is not base64 and is not PEM. Download the certificate from the IdP rather than copying it off a screen.                                                               |
| *"That is a private key, not a certificate. Paste the IdP's PUBLIC signing certificate."*                                                  | A `BEGIN PRIVATE KEY` block. KnoxCall only ever wants your IdP's **public** certificate — it never needs a private key of yours, and this field is rendered back on the settings page. |
| *"That certificate could not be parsed as X.509. Copy the IdP signing certificate exactly as your identity provider shows it."*            | It is base64, but not a decodable certificate — usually a truncated copy.                                                                                                              |

A bare base64 body is fine; so is full PEM. KnoxCall normalises what you paste and stores the PEM form. **Expiry is deliberately not refused** — refusing to save a config whose certificate expires next week would block the very edit that fixes it.

### Roles, domains and the rest

| Message                                                                                                                                                                                                                                                 | What to do                                                                                                                                                                                                          |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| *"Default role must be one of `member, readonly`. Just-in-time provisioning may never create an owner or an admin — elevate members on the team page."*                                                                                                 | The bound is absolute. Owner and admin are granted by a human, on the team page, and by nothing else.                                                                                                               |
| *"Group `'Engineering'` maps to `'owner'`; an IdP group may only map to `member or readonly` — an IdP-asserted login may never mint an administrator (elevate members on the team page)."*                                                              | Same bound, applied to the group map.                                                                                                                                                                               |
| *"At least one verified email domain is required. Without it, SAML sign-in cannot create or link any account for this tenant — just-in-time provisioning is refused outright, and a pre-existing account that is not already a member is refused too."* | The list is empty. [Prove a domain](/sso/domains) and list it.                                                                                                                                                      |
| *"…is not covered by a domain you have verified for single sign-on…"*                                                                                                                                                                                   | The list names a domain whose SSO proof does not cover it. Prove that domain, or list one that is covered. A domain proven for **sending mail** is a different proof and does not count here.                       |
| *"Allow IdP-initiated sign-in must be true or false."*                                                                                                                                                                                                  | You sent the string `"true"` rather than the boolean. `"false"`, `"0"` and `"no"` are all truthy in JavaScript, and this is the one field whose default is *deny* — so the shape is refused rather than guessed at. |
| *"An SSO connection cannot be activated without at least one active IdP certificate."*                                                                                                                                                                  | You set the status to active before adding a certificate. Add one first.                                                                                                                                            |
| *"An SSO connection cannot be activated without an IdP entry point."*                                                                                                                                                                                   | Same, for the sign-in URL.                                                                                                                                                                                          |
| *"This is the connection's only active IdP certificate. Add the new certificate first, then retire this one."*                                                                                                                                          | Rotation is **add-first**: add the new certificate, switch the IdP over, then retire the old one.                                                                                                                   |
| *"Single sign-on configuration requires a current Enterprise plan. Existing SSO connections keep authenticating your members; adding or changing one needs the subscription brought up to date."*                                                       | Bring the subscription up to date. Nobody is locked out in the meantime — this refuses **configuration**, never login.                                                                                              |
| *"Managing the SAML connection requires the owner or admin role — this row is the tenant's authentication policy…"*                                                                                                                                     | You are signed in as a member or a read-only user. Ask an owner or an admin. KnoxCall staff cannot write it either — the same surface refuses them.                                                                 |

<Note>
  Saving also needs a re-authentication in the **last five minutes**. A `403` asking you to re-authenticate is that, not a permissions problem — it is the same bar as changing a password, because this row names the identity provider allowed to assert who your users are.
</Note>

## Refusals from the IdP metadata import

`POST /admin/saml/connections/<id>/metadata` reads your identity provider's federation metadata; the flow is [importing your IdP's metadata](/sso/overview#importing-your-idps-metadata). Every refusal below is decided **before anything is written**, and each carries a machine-readable code alongside the message.

| Message                                                                                                                                                                                                                                                                                                                                      | Code                                |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- |
| *"Send either { xml } — the document itself — or { url }, where to fetch it from — but not both."*                                                                                                                                                                                                                                           | `metadata_source_ambiguous`         |
| *"That document carries a DOCTYPE or entity declaration. KnoxCall will not process one: an XML entity can read files off our servers or expand into gigabytes of text, and no identity provider needs one to publish its metadata. Export the metadata again — Entra, Okta, Google and JumpCloud all produce a document without a DOCTYPE."* | `metadata_doctype_forbidden`        |
| *"That document is `312` KiB; the limit is `256` KiB. Federation metadata for one application is a few kilobytes — a document this size is usually a whole federation."*                                                                                                                                                                     | `metadata_too_large`                |
| *"That document is an EntitiesDescriptor — a federation describing several identity providers — and KnoxCall will not guess which one you meant. Export the metadata for the single application you are connecting, or paste just its `<EntityDescriptor>` element."*                                                                        | `metadata_not_entity_descriptor`    |
| *"That document describes an entity but not an identity provider — it carries no `<IDPSSODescriptor>`…"* — you exported the SP half, which is the document KnoxCall gives *you* to upload to your IdP.                                                                                                                                       | `metadata_no_idp_descriptor`        |
| *"That document publishes no HTTP-Redirect or HTTP-POST `<SingleSignOnService>` endpoint, so there is nowhere to send a sign-in request."*                                                                                                                                                                                                   | `metadata_no_sso_endpoint`          |
| *"That document carries no signing certificate. Without one KnoxCall cannot tell a real assertion from a forged one, so the connection cannot be activated from it."*                                                                                                                                                                        | `metadata_no_signing_certificate`   |
| The metadata URL must use **https**, and an `http:` one is refused before anything is fetched: *"KnoxCall fetches this document to read your IdP's signing certificate; over http that fetch — and the certificate it returns — can be rewritten in transit."*                                                                               | `metadata_url_rejected`             |
| *"That URL answered `302` — a redirect. KnoxCall does not follow redirects when fetching metadata, because the address we checked would not be the address we read. Use the URL the redirect points at, if it is one you recognise."*                                                                                                        | `metadata_fetch_redirect`           |
| *"KnoxCall could not fetch that metadata URL. It must be reachable from the public internet over https, and it may not resolve to a private, loopback or link-local address."*                                                                                                                                                               | `metadata_fetch_failed`             |
| *"Every signing certificate in that document has expired, and this connection has no unexpired certificate of its own — importing it would leave every sign-in failing signature validation. Re-export the metadata once your identity provider has issued its new certificate."*                                                            | `metadata_all_certificates_expired` |

<Note>
  An import **never clears** what the document does not carry. A metadata export made before you switched Single Logout on has no `SingleLogoutService`, and KnoxCall leaves your stored SLO URL exactly where it was rather than reading the absence as "turn it off". It never retires a certificate either — rotation stays add-first — and it does not touch the domain list.
</Note>

## Symptoms with no error code

**Everything worked, then every login started failing at once.** The IdP rotated its signing certificate. Add the new one to the connection — [rotation, with overlap](/sso/overview#certificate-rotation-with-overlap).

**Logins fail only for people who have never signed in before.** The account rules, not the assertion: check the connection's verified email domains and whether just-in-time provisioning is on.

**Everyone lands with the wrong role.** Group mapping decides the role a person gets when they are **first** added. Moving someone between groups in your IdP later does not change their KnoxCall role — change it on the team page.

**Sign-in works from KnoxCall but not from the IdP's app tile.** That tile is an IdP-initiated login. Point it at `https://acme.knoxcall.com/saml/acme/login` instead, which is the better answer anyway.

**Repeated attempts start failing.** The ACS and the SLO endpoint each accept **30 requests per minute** per workspace and source address, with separate budgets so a logout storm cannot spend your sign-in allowance. Wait a minute.

**Assertion encryption is not offered by the IdP at all.** The app was configured from SP metadata that predates our encryption key. Re-upload the metadata — this is the [8 September 2026 call-out](/sso/overview) on every guide.

## Still stuck

Collect these before contacting support — they are what makes a report actionable, and none of them is a secret:

* your workspace slug and the URL you signed in from;
* the `error=` code on the login page, and roughly when;
* your IdP's SAML tracer output, if you can capture one (Entra, Okta and JumpCloud all have one; browser extensions work everywhere);
* the SP metadata document your IdP currently holds, so it can be diffed against `https://acme.knoxcall.com/saml/acme/metadata`.

Do not send a private key. KnoxCall never needs one from you.
