Update SSO Configuration

PUT /v1/sso/config/{uuid}

Edit an SSO connection on the caller's active account. Commonly used to rotate the IdP client secret, switch the OIDC endpoints, or toggle enforced.

This is a full replacement: send the same fields as create, and all of the same Req fields must be present. The one exception is client_secret — because GET endpoints never return the secret, leave it empty/omitted to keep the stored one, or pass a new value to rotate it.

Plan-gated: requires the sso entitlement on the account's plan. Permission-gated: the caller needs the settings:update account permission — members without it get 403.

Saving resets status to active and clears the last test result (last_error_message, last_tested_at); re-run test afterwards. Changing email_domain on an oidc config resets domain verification — domain_verified returns to false, a fresh DNS-TXT record is issued in the response, and the config stops applying at login until you re-run verify-domain. google/github stay auto-verified.

Path parameters

uuid string (uuid) Req
The SSO config's uuid. Must belong to the caller's active account (else 404).

Deprecated: the legacy integer id is still accepted in the path until the sunset, answered with Deprecation and Sunset headers. Use the uuid.

Request body

Same fields as create. Notable behaviors on update:

client_secret string
Pass a new value to rotate; leave empty/omitted to keep the existing secret. Never returned by GET endpoints.
email_domain string Req
Lowercased server-side; unique per account (409 on collision). Changing it resets domain verification for oidc configs.
provider_type string Req
google | github | oidc (saml returns 400: SAML is not supported). For google/github the auth_url/token_url/userinfo_url are force-locked to canonical endpoints; any supplied values are ignored.
auth_url, token_url, userinfo_url string
Required for oidc. Must be HTTPS and public (SSRF-guarded).
issuer, jwks_url string
Required for oidc (same as create): issuer is matched against the id_token iss claim and jwks_url supplies the signature keys at login. Cleared automatically for google/github. Switching a config to oidc without both is rejected with 400. Use Discover to auto-fill.
enforced boolean
When true, matching users are forced through SSO and password login is rejected. Requires the account's plan to include the sso entitlement (else 403).

Response (200 OK)

The updated SSO config (SSOConfigResponse, secret never included). For an oidc config whose email_domain changed, domain_verified is false and the response carries domain_verify_record_name / domain_verify_record_value — the TXT record to publish before calling verify-domain.

Deprecated: until the sunset this response also carries the legacy integer id and *_id fields. Don't use them; read uuid and the *_uuid fields.

Errors

400 Bad Request
Missing required field, invalid provider_type, missing issuer/jwks_url for oidc, or non-HTTPS/private endpoint URL.
401 Unauthorized
Missing auth.
403 Forbidden
No active account, missing settings:update permission, plan lacks the sso entitlement, or enforced: true requested on a plan without SSO.
404 Not Found
Config doesn't exist or belongs to another account.
409 Conflict
Another config on this account already uses the new email_domain.
Request — full body, keep the stored secret
curl -X PUT "https://api.ngris.com/v1/sso/config/019af662-7814-75fe-ac3c-b767d84325a9" \ -H "X-API-KEY: <your_api_key>" \ -H "Content-Type: application/json" \ -d '{ "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", "issuer": "https://acme.okta.com/oauth2/default", "jwks_url": "https://acme.okta.com/oauth2/default/v1/keys", "scopes": "openid profile email", "enforced": false }'
Python
from ngris import Ngris client = Ngris(api_key="<your_api_key>") # Full replacement. client_secret is left out, so the stored one is kept; # add "client_secret": "your-new-idp-client-secret" to rotate it. client.sso.update("019af662-7814-75fe-ac3c-b767d84325a9", { "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", "issuer": "https://acme.okta.com/oauth2/default", "jwks_url": "https://acme.okta.com/oauth2/default/v1/keys", })
Response — 200 OK
{ "uuid": "019af662-7814-75fe-ac3c-b767d84325a9", "account_uuid": "019a68b9-1a79-7820-a884-41f554aba6bd", "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", "issuer": "https://acme.okta.com/oauth2/default", "jwks_url": "https://acme.okta.com/oauth2/default/v1/keys", "scopes": "openid profile email", "status": "active", "enforced": false, "domain_verified": true, "last_error_message": "", "last_tested_at": null, "created_at": "2026-01-15T00:00:00Z", "updated_at": "2026-01-15T09:30:00Z" }
Was this helpful?
Was this helpful?