Self SSO domains
Axowl has two kinds of SSO, configured and billed separately:
| Self SSO | External SSO | |
|---|---|---|
| Who handles sign-in | Axowl (passkey, magic link) | Your IdP (Okta, Entra ID, Google Workspace, SAML/OIDC) |
| What you register | Verified domains (this page) | IdP connections — IdP connections |
| Billing | Per verified domain beyond the plan allowance — $10 / domain / month | Per connection beyond the plan allowance |
| Dashboard | Organization settings → User management (left) · App group → Login methods → SSO | Organization settings → User management (right) |
Domain kinds
Section titled “Domain kinds”kind | Stored as | What it does |
|---|---|---|
email | Company email domain (for example acme.com) | “Sign in with SSO” on Axowl finds your organization from a work email. If an external IdP connection routes that domain, the person goes to that IdP; otherwise they continue with their Axowl account. External IdP connections can only route domains verified here. |
app | App domain of one app group (for example shop.acme.io) | Apps on different domains share one sign-in (the hosted portal hands the session over to the app’s callback origin). A verified domain also covers its subdomains. |
Personal and disposable email domains (gmail.com and the like) are refused for email. A domain verified by another organization can’t be registered or verified again — 409 domain_claimed.
Endpoints
Section titled “Endpoints”Org-scoped (/api/org/{slug}/sso-domains, session-authenticated). Writes need the org.idp_connection.update permission (the same one as IdP connections).
| Method · Route | Purpose |
|---|---|
GET ?kind=email|app&app_group_id= | List domains (both kinds when kind is omitted) and a billing summary: included (-1 = unlimited), verified, billable, unit_price_usd, monthly_usd. |
| POST “ | Register a domain. Body: domain, kind (email or app), app_group_id (required for app). Returns the row with the TXT record to publish. |
POST /{id}/verify | Look up the TXT record now. Returns verified and, when not verified, reason (txt_not_found, txt_mismatch, dns_lookup_failed) plus up to five found_records. 404 domain_not_found for an unknown id; 409 domain_claimed if another organization verified the domain first. |
DELETE /{id} | Remove a domain. An email domain still routed by an active external IdP connection returns 409 domain_bound_to_connection — turn that connection off first. |
POST /app-mode | Turn an app group’s app domains on or off without deleting them. Body: app_group_id, integrated (bool). Off domains keep their verification but are not used for sign-in sharing and are not billed. Returns integrated and the number of domains changed. |
Each row: id, domain, kind, app_group_id, verified, verified_at, active (false = turned off with /app-mode), txt_name, txt_value, last_checked_at, external_connection_type (email domains routed to an external IdP).
Verify with DNS
Section titled “Verify with DNS”Publish a TXT record, then call verify:
| Type | Name | Value |
|---|---|---|
| TXT | _axowl-challenge.acme.com (txt_name) | the txt_value from the row |
curl -X POST "https://api.axowl.com/api/org/acme/sso-domains" \ -H "Content-Type: application/json" --cookie "$AXOWL_SESSION" \ -d '{"domain":"acme.com","kind":"email"}'
# after the TXT record is livecurl -X POST "https://api.axowl.com/api/org/acme/sso-domains/$DOMAIN_ID/verify" --cookie "$AXOWL_SESSION"DNS changes can take a few minutes to show up; call verify again until verified is true.
If your organization already verified the same domain for the other kind (for example acme.com as an email domain, then
again as an app domain), the new row is verified right away — no second TXT record. A TXT value issued for either row of
the same domain in the same organization also verifies the other.
Employee sign-in policy
Section titled “Employee sign-in policy”/api/org/{slug}/sso-login-policy — how invited employees enter the organization.
| Method | Purpose |
|---|---|
| GET | Any member. Returns staff_invite_sso_only, staff_login_basis, has_enterprise_idp, has_email_domain. |
| PUT | Needs org.idp_connection.update. Body: staff_invite_sso_only (bool), staff_login_basis (idp or idp_or_domain). Returns the saved values and enforced — false while the organization has neither a company IdP nor a verified company email domain. 400 invalid_basis for any other basis. |
staff_invite_sso_only— applies to employee invitations (any role except Guest, Freelancer, outside director, project guest and meeting guest):- Company IdP connected: the invitation is accepted only after the invitee signs in with the company IdP and the
IdP’s email matches the invited address. Both accept routes return 400
sso_requireduntil then. Inviting a personal email address (Gmail and the like) to an employee role is refused up front. - Only a verified company email domain (no IdP): the invited address must be on that domain (subdomains count).
Other addresses are refused when the invitation is sent, and both accept routes return 400
staff_domain_required. Opening the invitation link from the email proves the mailbox. - Neither: not applied yet.
- Company IdP connected: the invitation is accepted only after the invitee signs in with the company IdP and the
IdP’s email matches the invited address. Both accept routes return 400
staff_login_basis— when the organization sign-in page (/o/{slug}/login) first asks “Registered employee or Guest”:idponly when a company IdP is connected;idp_or_domain(default) also when a company email domain is verified. Guests sign in with their own Axowl account and still need an invitation to enter the organization.
Find where a work email signs in
Section titled “Find where a work email signs in”POST /api/public/sso/discover — public, rate-limited. Body: { "email": "[email protected]" }.
| Response | Meaning |
|---|---|
{ "found": false } | No organization has verified this domain (also returned for malformed or personal addresses). |
{ "found": true, "mode": "external", "org_name", "redirect_url" } | Send the browser to redirect_url — the organization’s IdP sign-in. |
{ "found": true, "mode": "axowl", "org_name", "org_slug" } | Continue with the Axowl account (passkey or magic link). |
Check a registered employee
Section titled “Check a registered employee”POST /api/public/orgs/{slug}/staff-check — public, rate-limited. Body: { "email": "[email protected]" }.
Used by the organization sign-in page (/o/{slug}/login) when an employee picks “Registered employee” and the
organization has a verified company email domain but no external IdP.
| Response | Meaning |
|---|---|
{ "registered": true } | The address is on a domain this organization verified and belongs to an active (or pending) member. |
{ "registered": false } | Anything else — not a member, suspended or left, an unverified or personal domain, or an unknown organization. No reason is given. |
Billing
Section titled “Billing”Verified email domains and app domains are added together and compared with the plan allowance: Basic 0 · Pro 1 · Pro+ 3 · Business 10 · Enterprise unlimited. Each verified domain beyond that is $10 per month, charged with the monthly subscription and shown on the invoice under SSO. Domains waiting for DNS are not counted. Adding domains requires the Pro plan or higher — 403 UPGRADE_REQUIRED (feature: "sso").
Errors
Section titled “Errors”| Status · code | When |
|---|---|
400 invalid_kind | kind is not email or app |
400 invalid_domain | Not a domain name |
400 personal_email_domain | Personal or disposable email domain for kind: "email" |
400 app_group_required · 404 app_group_not_found | kind: "app" without an app group of this organization |
409 domain_claimed | Another organization already verified this domain |
409 domain_exists | Already registered here |
404 domain_not_found | Verify or delete with an id that is not a domain of this organization |
409 domain_bound_to_connection | Delete refused while an external IdP connection routes the domain |
403 UPGRADE_REQUIRED | Plan below Pro |