Create SSO Configuration

POST /v1/sso/config

Create a new SSO connection for the caller's active account. Once a matching config is enforced and (for custom IdPs) domain-verified, account members whose email addresses match the configured email_domain are forced through your IdP at login instead of using password auth.

Plan-gated: requires the account's sso entitlement (else 403). Permission-gated: requires the settings:write account permission — members without it get 403.

Each email_domain can have at most one SSO config per account. Adding a second one for the same domain returns 409.

After creation, run POST /v1/sso/config/{id}/test to sanity-check the config. For oidc / saml providers you must also complete domain verification before the config applies at login (see below).

Request body

email_domain string Req
Domain to route via this SSO. Lowercased server-side. Users with email addresses ending in @<email_domain> hit this config. Must be unique within the account.
provider_type string Req
Exactly one of google, github, oidc, saml. For google / github the OAuth endpoints are fixed (see below); oidc / saml are custom IdPs that require their own endpoint URLs.
client_id string Req
OAuth/OIDC client ID from your IdP.
client_secret string Req
OAuth/OIDC client secret. Encrypted at rest; never returned by any GET endpoint. On update, sending a blank client_secret keeps the existing secret.
auth_url, token_url, userinfo_url string
Required for provider_type: "oidc" and "saml". Ignored for google / github — the platform force-locks those to Google's / GitHub's canonical endpoints, overriding any values you send (a fake-IdP guard). Must be https and resolve to a public host; internal/private hosts (link-local, RFC1918, cluster-internal) are rejected with 400.
scopes string
Space-separated OAuth scopes. Defaults to openid profile email (user:email for github).
enforced boolean
When true, account members whose email domain matches are forced through SSO — their password login is rejected. Requires the account's plan to include the sso entitlement or the request is refused with 403. Default false (opt-in SSO).

Domain verification (oidc / saml)

Before a custom oidc / saml config applies at login, the account must prove it owns email_domain. On create (and whenever email_domain changes) the config is returned with domain_verified: false plus a DNS-TXT challenge in domain_verify_record_name / domain_verify_record_value. Publish that TXT record, then call POST /v1/sso/config/{id}/verify-domain — on an exact match domain_verified flips to true.

google / github configs are auto-verified (identity is asserted by the provider and the endpoints are locked), so they carry no TXT challenge and apply immediately.

Response (200 OK)

The newly-created SSO config (secret never included). Fields: id, account_id, email_domain, provider_type, client_id, auth_url, token_url, userinfo_url, scopes, status (active | error), enforced, domain_verified, domain_verify_record_name (omitted once verified / for google+github), domain_verify_record_value (same), last_error_message, last_tested_at, created_at, updated_at.

Errors

400 Bad Request
Missing required field, invalid provider_type, missing auth_url/token_url/userinfo_url for oidc/saml, or a non-https / internal endpoint URL.
401 Unauthorized
Missing auth.
403 Forbidden
No active account, caller lacks settings:write, the account's plan doesn't include SSO, or enforced: true was sent for a plan without the SSO entitlement.
409 Conflict
An SSO config for this email_domain already exists in the account.
Request — Custom OIDC
curl -X POST "https://api.ngris.com/v1/sso/config" \ -H "X-API-KEY: <your_api_key>" \ -H "Content-Type: application/json" \ -d '{ "email_domain": "acme.com", "provider_type": "oidc", "client_id": "0oa1234abc", "client_secret": "<idp-client-secret>", "auth_url": "https://acme.okta.com/oauth2/default/v1/authorize", "token_url": "https://acme.okta.com/oauth2/default/v1/token", "userinfo_url": "https://acme.okta.com/oauth2/default/v1/userinfo", "scopes": "openid profile email", "enforced": true }'
Python
from ngris import Ngris client = Ngris(api_key="<your_api_key>") sso = client.sso.create({ "email_domain": "acme.com", "provider_type": "oidc", "client_id": "0oa1234abc", "client_secret": "<idp-client-secret>", "auth_url": "https://acme.okta.com/oauth2/default/v1/authorize", "token_url": "https://acme.okta.com/oauth2/default/v1/token", "userinfo_url": "https://acme.okta.com/oauth2/default/v1/userinfo", "enforced": True, }) # oidc/saml: publish the returned TXT record, then verify: client.sso.verify_domain(sso.id)
Response — 200 OK
{ "id": 1, "account_id": 42, "email_domain": "acme.com", "provider_type": "oidc", "client_id": "0oa1234abc", "auth_url": "https://acme.okta.com/oauth2/default/v1/authorize", "token_url": "https://acme.okta.com/oauth2/default/v1/token", "userinfo_url": "https://acme.okta.com/oauth2/default/v1/userinfo", "scopes": "openid profile email", "status": "active", "enforced": true, "domain_verified": false, "domain_verify_record_name": "_ngris-sso-verify.acme.com", "domain_verify_record_value": "ngris-sso-verify=3f9c...a1", "last_error_message": "", "last_tested_at": null, "created_at": "2026-07-02T10:00:00Z", "updated_at": "2026-07-02T10:00:00Z" }
Iris