//SSO via SAML

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).
  • 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

  • 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

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 |