Single Sign-On via SAML 2.0
Connect enterprise identity providers using SAML 2.0. Management endpoints require Authorization: Bearer <admin-jwt>.
Endpoint Summary
| Method | Path | Purpose |
|---|---|---|
| POST | /api/admin/organizations/{org}/saml-connections | Create SAML connection |
| GET | /api/admin/organizations/{org}/saml-connections | List connections |
| DELETE | /api/admin/organizations/{org}/saml-connections/{uuid} | Delete connection |
| GET | /api/auth/saml/{connection_uuid}/authorize | Initiate SAML login |
| POST | /api/auth/saml/callback | Handle IdP callback |
| PATCH | /api/admin/organizations/{org}/sso/{uuid}/saml-cert | Rotate SAML cert |
Connection Management
POST /api/admin/organizations/:org_uuid/saml-connections
Creates a new SAML connection for an organization.
Input: ~~~json { "name": "Acme Okta", "idp_metadata_url": "https://acme.okta.com/app/exk.../sso/saml/metadata", "allowed_domains": ["acme.com"] } ~~~
Output: ~~~json { "connection_uuid": "saml-conn-uuid-123", "sp_entity_id": "https://api.buttrbase.com/saml/saml-conn-uuid-123", "acs_url": "https://api.buttrbase.com/saml/saml-conn-uuid-123/acs" } ~~~
Configure your IdP with the returned sp_entity_id and acs_url.
GET /api/admin/organizations/:org_uuid/saml-connections
Lists all SAML connections for an organization.
Output: ~~~json { "data": [{ "connection_uuid": "saml-conn-uuid-123", "name": "Acme Okta", "provider": "okta", "allowed_domains": ["acme.com"], "created_at": "2026-01-15T00:00:00Z" }] } ~~~
DELETE /api/admin/organizations/:org_uuid/saml-connections/:connection_uuid
Deletes a SAML connection. Returns 204.
Login Flow
GET /api/auth/saml/:connection_uuid/authorize
Initiates the SAML login flow. Redirect the user's browser to this URL.
Query Parameters:
redirect_uri— where to send the user after login.state— opaque value for CSRF protection (recommended).- Enterprise customers requiring SAML (many large orgs mandate it)
- IdPs that only support SAML (older ADFS, some on-prem identity systems)
- Compliance environments where SAML is the approved standard
Response: 302 redirect to the IdP's SAML login page.
POST /api/auth/saml/callback
The Assertion Consumer Service (ACS) endpoint. Receives the SAML response from the IdP after the user authenticates.
Input: URL-encoded form with SAMLResponse and RelayState fields.
On success: Redirects to your application's redirect_uri with a session_token query parameter.
Certificate Management
PATCH /api/admin/organizations/:org_uuid/sso/:connection_uuid/saml-cert
Rotates the SAML signing certificate for a connection. Used when the IdP rotates its certificate.
Input: ~~~json { "cert": "-----BEGIN CERTIFICATE-----\nMIIC..." } ~~~
Output: ~~~json { "message": "SAML certificate updated", "connection_uuid": "saml-conn-uuid-123" } ~~~
SDK Examples
Python
~~~python
Create a SAML connection
conn = client.create_sso_connection("org-uuid", { "provider": "okta", "name": "Okta SSO", "idp_metadata_url": "https://acme.okta.com/app/.../metadata", "allowed_domains": ["acme.com"] })Get the authorize URL
url = client.saml_authorize_url(conn["connection_uuid"])Handle callback (on your server)
resp = client.saml_callback({"SAMLResponse": "base64-encoded-response"})Rotate SAML certificate
client.saml_cert_rollover("org-uuid", conn["connection_uuid"], {"cert": "-----BEGIN CERTIFICATE-----..."}) ~~~Node.js / TypeScript
~~~typescript // Create a SAML connection const conn = await client.createSsoConnection('org-uuid', { provider: 'okta', name: 'Okta SSO', idp_metadata_url: 'https://acme.okta.com/app/.../metadata', allowed_domains: ['acme.com'] });
// Get the authorize URL const url = await client.samlAuthorizeUrl(conn.connection_uuid);
// Handle callback (on your server) const resp = await client.samlCallback({ SAMLResponse: 'base64-encoded-response' }); ~~~
Go
~~~go // Create a SAML connection conn, err := client.CreateSsoConnection(ctx, "org-uuid", map[string]any{ "provider": "okta", "name": "Okta SSO", "idp_metadata_url": "https://acme.okta.com/app/.../metadata", "allowed_domains": []string{"acme.com"}, })
// Get the authorize URL url, err := client.SamlAuthorizeURL(ctx, conn.ConnectionUUID)
// Handle callback (on your server) resp, err := client.SamlCallback(ctx, map[string]any{"SAMLResponse": "base64-encoded-response"}) ~~~
Rust
~~~rust // Create a SAML connection let conn = client.create_sso_connection("org-uuid", &serde_json::json!({ "provider": "okta", "name": "Okta SSO", "idp_metadata_url": "https://acme.okta.com/app/.../metadata", "allowed_domains": ["acme.com"] })).await?;
// Get the authorize URL let url = client.saml_authorize_url(&conn["connection_uuid"].as_str().unwrap()).await?;
// Handle callback (on your server) let resp = client.saml_callback(&serde_json::json!({"SAMLResponse": "base64-encoded-response"})).await?;
// Rotate SAML certificate client.saml_cert_rollover("org-uuid", "conn-uuid", &serde_json::json!({"cert": "..."})).await?; ~~~
When to Use
Error Reference
| Status | Code | Meaning |
|--------|------|---------|
| 400 | invalid_connection | SAML connection UUID does not exist |
| 400 | invalid_saml_response | SAML assertion is malformed or unsigned |
| 400 | signature_invalid | SAML response signature verification failed |
| 401 | assertion_expired | SAML assertion is past its validity window |
| 403 | domain_mismatch | Email domain not allowed for this connection |
| 404 | org_not_found | org_uuid does not exist |
| 502 | provider_error | IdP returned an error or was unreachable |