> ## 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.

# Get SAML SP Metadata

> Return the Service Provider metadata XML for a tenant's SAML SSO connection.

Returns the Service Provider (SP) metadata document for a tenant, as
`application/samlmetadata+xml`. Hand this document to the tenant's identity
provider when configuring the connection: it carries the SP entity id, the
Assertion Consumer Service URLs
([`POST /auth/saml/{tenantID}/acs`](/api-reference/auth/saml-acs)), the SP
certificate (published for `encryption` use), and the SP's requirements:
`WantAssertionsSigned="true"` and `AuthnRequestsSigned="false"` — Recurso does
not sign its AuthnRequests, so the document carries no signing key.

The endpoint is public and unauthenticated so an IdP can fetch it directly,
but it only answers for tenants that have an SSO connection row. The row does
not need to be enabled or fully configured yet — you can hand the metadata to
the IdP before the IdP details are stored — but users cannot start an
SP-initiated login with
[`GET /auth/saml/{tenantID}/login`](/api-reference/auth/saml-login) until the
connection is both configured and enabled.

## Path Parameters

| Parameter  | Type          | Required | Description                             |
| ---------- | ------------- | -------- | --------------------------------------- |
| `tenantID` | string (uuid) | Yes      | The tenant whose SP metadata to return. |

## Example Request

```bash theme={null}
curl https://api.recurso.dev/auth/saml/d4c3b2a1-0f9e-4765-8321-abcdef012345/metadata
```

## Example Response

The body is XML, not JSON:

```xml theme={null}
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
  validUntil="2026-09-06T10:15:00Z"
  entityID="https://api.recurso.dev/auth/saml/d4c3b2a1-0f9e-4765-8321-abcdef012345/metadata">
  <SPSSODescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
    validUntil="2026-09-06T10:15:00Z"
    protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"
    AuthnRequestsSigned="false" WantAssertionsSigned="true">
    <KeyDescriptor use="encryption">
      <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
        <X509Data xmlns="http://www.w3.org/2000/09/xmldsig#">
          <X509Certificate xmlns="http://www.w3.org/2000/09/xmldsig#">MIIC5TCCAc2gAwIBAgIQ...</X509Certificate>
        </X509Data>
      </KeyInfo>
      <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"></EncryptionMethod>
      <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes192-cbc"></EncryptionMethod>
      <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc"></EncryptionMethod>
      <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"></EncryptionMethod>
    </KeyDescriptor>
    <NameIDFormat></NameIDFormat>
    <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://api.recurso.dev/auth/saml/d4c3b2a1-0f9e-4765-8321-abcdef012345/acs" index="1"></AssertionConsumerService>
    <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://api.recurso.dev/auth/saml/d4c3b2a1-0f9e-4765-8321-abcdef012345/acs" index="2"></AssertionConsumerService>
  </SPSSODescriptor>
</EntityDescriptor>
```

The `validUntil` attributes are set 48 hours from the time of the request, so
an IdP that caches metadata should re-fetch it periodically. The
`NameIDFormat` element is present but empty — the SP does not require a
particular NameID format; it reads the user's email from the assertion. Only
the HTTP-POST ACS (index 1) is used by Recurso's login flow; the HTTP-Artifact
entry is emitted by the SAML library and is not backed by an artifact
resolution step.

## Errors

| Status | Code        | When                                                                            | Fix                                                                                                                       |
| ------ | ----------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `404`  | `not_found` | `tenantID` is not a valid UUID, or the tenant has no SSO connection row at all. | Create the tenant's SAML connection in the dashboard first (it does not need to be enabled yet), then fetch the metadata. |

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


## OpenAPI

````yaml GET /auth/saml/{tenantID}/metadata
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:
  /auth/saml/{tenantID}/metadata:
    get:
      tags:
        - Auth
      summary: SP metadata XML for a tenant
      description: >-
        Returns the Service Provider metadata document
        (application/samlmetadata+xml). 404 when the tenant has no SSO
        connection.
      operationId: getSAMLMetadata
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: SP metadata XML.
          content:
            application/samlmetadata+xml:
              schema:
                type: string
        '404':
          $ref: '#/components/responses/NotFound'
      security: []
components:
  responses:
    NotFound:
      description: The requested resource does not exist.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  schemas:
    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.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API key
      description: >-
        Tenant API key obtained from `POST /auth/register` or `POST
        /v1/developer/keys`.

````