> ## Documentation Index
> Fetch the complete documentation index at: https://docs.recurso.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Create or Update SSO Connection

> Create or replace the tenant's SAML identity provider configuration and toggle whether SSO login is enabled.

Creates the tenant's SAML SSO connection, or replaces it if one already
exists. Each tenant has exactly one connection, so a `PUT` is idempotent: send
the full IdP configuration every time, not a partial patch. Configure the
identity provider either by pasting its full metadata document in
`idp_metadata_xml`, or by supplying all three discrete fields —
`idp_entity_id`, `idp_sso_url` and `idp_certificate`. When both are present,
the metadata XML wins.

Set `enabled: true` to switch on the public
[login](/api-reference/auth/saml-login) and
[ACS](/api-reference/auth/saml-acs) endpoints for the tenant. Enabling a
connection that is not fully configured is rejected, so you can save a
draft with `enabled: false` and flip it on once the IdP side is done. The
response includes the SP URLs to register with the IdP; read them back later
with [`GET /v1/sso/connection`](/api-reference/sso/get).

Only owners and admins can call this endpoint from a dashboard session;
members receive `403`. API-key callers act with full tenant access.

## Request Body

| Parameter          | Type    | Required | Description                                                                                                                                                                                                                                                                   |
| ------------------ | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `idp_metadata_xml` | string  | No       | The IdP's full SAML metadata document. Sufficient on its own; takes precedence over the discrete fields below.                                                                                                                                                                |
| `idp_entity_id`    | string  | No       | The IdP's SAML entity id (issuer). Only checked when `enabled` is `true`: enabling requires either `idp_metadata_xml`, or all of `idp_entity_id`, `idp_sso_url` and `idp_certificate`. With `enabled: false` any subset — including none — is accepted and stored as a draft. |
| `idp_sso_url`      | string  | No       | The IdP's HTTP-Redirect SingleSignOnService URL.                                                                                                                                                                                                                              |
| `idp_certificate`  | string  | No       | The IdP's base64/PEM X.509 signing certificate.                                                                                                                                                                                                                               |
| `enabled`          | boolean | No       | Whether SSO login is live for the tenant. Defaults to `false`. Can only be `true` when the connection is configured.                                                                                                                                                          |

Whitespace around every string field is trimmed before it is stored.

## Example Request

```bash theme={null}
curl -X PUT https://api.recurso.dev/v1/sso/connection \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "idp_entity_id": "http://www.okta.com/exk1a2b3c4d5e6f7g8h9",
    "idp_sso_url": "https://acme.okta.com/app/acme_recurso_1/exk1a2b3c4d5e6f7g8h9/sso/saml",
    "idp_certificate": "-----BEGIN CERTIFICATE-----\nMIIDpDCCAoygAwIBAgIGAYt...\n-----END CERTIFICATE-----",
    "enabled": true
  }'
```

## Response

```json theme={null}
{
  "data": {
    "tenant_id": "d4c3b2a1-0f9e-4765-8321-abcdef012345",
    "idp_entity_id": "http://www.okta.com/exk1a2b3c4d5e6f7g8h9",
    "idp_sso_url": "https://acme.okta.com/app/acme_recurso_1/exk1a2b3c4d5e6f7g8h9/sso/saml",
    "idp_certificate": "-----BEGIN CERTIFICATE-----\nMIIDpDCCAoygAwIBAgIGAYt...\n-----END CERTIFICATE-----",
    "idp_metadata_xml": "",
    "enabled": true,
    "configured": true,
    "sp_metadata_url": "https://api.recurso.dev/auth/saml/d4c3b2a1-0f9e-4765-8321-abcdef012345/metadata",
    "sp_acs_url": "https://api.recurso.dev/auth/saml/d4c3b2a1-0f9e-4765-8321-abcdef012345/acs"
  }
}
```

## Fields

| Field              | Type          | Description                                                                                                                                                                   |
| ------------------ | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tenant_id`        | string (uuid) | The tenant that owns the connection.                                                                                                                                          |
| `idp_entity_id`    | string        | The stored IdP entity id, or empty when only `idp_metadata_xml` was submitted.                                                                                                |
| `idp_sso_url`      | string        | The stored IdP SingleSignOnService URL.                                                                                                                                       |
| `idp_certificate`  | string        | The stored IdP signing certificate.                                                                                                                                           |
| `idp_metadata_xml` | string        | The stored IdP metadata document, or empty when only the discrete fields were submitted. Fields are stored as sent (trimmed); nothing is blanked when both forms are present. |
| `enabled`          | boolean       | Whether the public SP endpoints answer for this tenant.                                                                                                                       |
| `configured`       | boolean       | Whether the stored detail is enough to attempt a login.                                                                                                                       |
| `sp_metadata_url`  | string        | Public URL of the tenant's [SP metadata](/api-reference/auth/saml-metadata). Register it with the IdP.                                                                        |
| `sp_acs_url`       | string        | Public URL the IdP posts assertions to.                                                                                                                                       |

## Errors

| Status | Code                | When                                                                                                                                                                                                                                   | Fix                                                                                                                                 |
| ------ | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `validation_failed` | The body is not valid JSON, or `enabled` is `true` but the connection is not configured (no `idp_metadata_xml` and at least one of the three discrete fields missing). The same code is returned if the connection could not be saved. | Send a well-formed body; provide `idp_metadata_xml` or all of `idp_entity_id`, `idp_sso_url` and `idp_certificate` before enabling. |
| `401`  | `unauthorized`      | No API key or session cookie was sent. An invalid API key returns `invalid_api_key`; a key for the wrong mode (live vs test) returns `key_mode_mismatch`.                                                                              | Send `Authorization: Bearer <api_key>` with a key for the right mode, or sign in to the dashboard.                                  |
| `403`  | `forbidden`         | The dashboard session belongs to a member, not an owner or admin.                                                                                                                                                                      | Ask an owner or admin to configure SSO, or use an API key.                                                                          |

Errors use the standard envelope — see [Errors](/api-reference/errors).


## OpenAPI

````yaml PUT /v1/sso/connection
openapi: 3.1.0
info:
  title: Recurso API
  version: 1.0.0
  description: |
    The Recurso billing engine REST API.

    Authenticate by passing your API key as a bearer token:

        Authorization: Bearer <api_key>

    Obtain an API key by registering a tenant via `POST /auth/register`.
    All authenticated endpoints live under the `/v1` prefix. Mutating
    endpoints support idempotency via the `Idempotency-Key` header.
  license:
    name: MIT
    identifier: MIT
servers:
  - url: https://billing.example.com
    description: >-
      Example deployment — substitute the base URL of your own Recurso
      deployment.
security:
  - bearerAuth: []
tags:
  - name: System
    description: Health, version, and API metadata
  - name: Auth
    description: Tenant registration
  - name: Plans
    description: Product catalog plans
  - name: Customers
    description: Customer management
  - name: Subscriptions
    description: Subscription lifecycle
  - name: Invoices
    description: Invoices, PDFs, and Indian GST e-invoicing
  - name: Coupons
    description: Discounts
  - name: Usage
    description: Metered usage events
  - name: Credit Notes
    description: Customer credits
  - name: Quotes
    description: Quote-to-invoice lifecycle
  - name: Webhooks
    description: Webhook endpoint management and event feed
  - name: Analytics
    description: Revenue analytics
  - name: Checkout
    description: Public hosted checkout for invoices
  - name: Payments
    description: Payment order creation
  - name: Inbound Webhooks
    description: Receivers for payment-gateway callbacks (Razorpay, Stripe)
  - name: Customer Portal
    description: Customer-facing portal — magic-link auth and session-scoped data
  - name: Developer
    description: API key management
  - name: Account
    description: Tenant account settings
  - name: Finance
    description: Ledger accounts, entries, reconciliation, and revenue recognition
  - name: Settings
    description: Tax (GST) and e-invoicing (IRP) configuration
  - name: Consents
    description: Consent records for RBI-compliant recurring billing
  - name: Referrals
    description: Customer referral program
  - name: Gifts
    description: Gift subscriptions
  - name: Mandates
    description: UPI Autopay mandates
  - name: Offline Payments
    description: Virtual accounts and manually recorded payments
  - name: Organizations
    description: Multi-entity organizations grouping several tenants
  - name: Accounting
    description: QuickBooks / Xero accounting integrations
  - name: Churn
    description: Churn risk scoring and alerts
  - name: Cancel Flows
    description: Configurable retention flows shown at cancellation time
  - name: Dunning
    description: Dunning analytics and multi-channel dunning campaigns
paths:
  /v1/sso/connection:
    put:
      tags:
        - Auth
      summary: Create or update the tenant's SAML IdP configuration (owner/admin only)
      description: >
        Upserts the tenant's IdP config. Provide either `idp_metadata_xml`, or
        all of `idp_entity_id` + `idp_sso_url` + `idp_certificate`. Enabling a
        connection that is not fully configured is rejected. Members get 403.
      operationId: upsertSSOConnection
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SSOConnectionUpsertRequest'
      responses:
        '200':
          description: The upserted connection.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/SSOConnection'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
      security:
        - sessionCookie: []
        - bearerAuth: []
components:
  schemas:
    SSOConnectionUpsertRequest:
      type: object
      properties:
        idp_entity_id:
          type: string
        idp_sso_url:
          type: string
        idp_certificate:
          type: string
        idp_metadata_xml:
          type: string
        enabled:
          type: boolean
    SSOConnection:
      type: object
      properties:
        tenant_id:
          type: string
          format: uuid
        idp_entity_id:
          type: string
        idp_sso_url:
          type: string
        idp_certificate:
          type: string
          description: Base64/PEM X.509 signing certificate.
        idp_metadata_xml:
          type: string
          description: >-
            Optional full IdP metadata; takes precedence over the discrete
            fields.
        enabled:
          type: boolean
        configured:
          type: boolean
          description: Whether the connection has enough IdP detail to attempt a login.
        sp_metadata_url:
          type: string
          description: Public SP metadata URL to hand to the IdP.
        sp_acs_url:
          type: string
          description: Public SP Assertion Consumer Service URL.
      required:
        - tenant_id
        - enabled
        - configured
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          description: Structured error detail.
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: Stable machine-readable error code.
              examples:
                - validation_failed
                - unauthorized
                - forbidden
                - not_found
                - conflict
                - rate_limited
                - internal_error
                - invalid_api_key
                - key_mode_mismatch
                - over_refund
                - invoice_not_paid
                - invoice_already_paid
            message:
              type: string
              description: Human-readable explanation.
  responses:
    BadRequest:
      description: The request body or parameters are invalid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: Missing or invalid credentials (API key or session cookie).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Forbidden:
      description: Authenticated but not permitted (insufficient role).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API key
      description: >-
        Tenant API key obtained from `POST /auth/register` or `POST
        /v1/developer/keys`.
    sessionCookie:
      type: apiKey
      in: cookie
      name: recurso_session
      description: >
        Dashboard user session cookie (httpOnly) issued by `POST /auth/register`
        and `POST /auth/login`. v1 endpoints accept EITHER this cookie or the
        tenant API key (`bearerAuth`).

````