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

# Single sign-on (SAML)

> Exchange your institution API key plus a SAML assertion for a doctor session

With SAML single sign-on your institution holds **one** API key instead of one key per doctor. Your identity provider (Okta, Azure AD, ADFS…) authenticates the doctor, and your backend exchanges the institution key plus a signed SAML assertion for a **doctor session token**. Every business endpoint stays the same — only the `Authorization` value changes.

## How it works

1. Your IdP authenticates the doctor and signs a SAML assertion.
2. Your backend calls `POST /v1/sso/exchange` with your institution secret key and the assertion.
3. Telepatia verifies the signature, issuer, audience and validity against the SAML configuration of your institution, and returns a doctor access token plus a refresh token.
4. Your backend calls the API with the doctor token, and renews it through `POST /v1/sso/refresh` — no new IdP round-trip until the refresh chain ends.

## Prerequisites

* An institution **secret key** (`sk_…`) created with the **SAML SSO exchange** permission (the `saml:exchange` checkbox in the platform's API Keys page).
* The SAML configuration saved in the platform's **Developers → SSO** tab: your IdP's entity ID (issuer) and its signing certificate.
* The doctor must already exist in your institution. SSO authenticates doctors; it does not create them.

## Service Provider details

Register these fixed Telepatia values in your identity provider. They are the same for every institution.

| Value                           | URL                                               |
| ------------------------------- | ------------------------------------------------- |
| SP entity ID (audience)         | `https://scribe-api.telepatia.ai/saml`            |
| ACS URL (assertion destination) | `https://scribe-api.telepatia.ai/v1/sso/exchange` |
| SP metadata URL                 | `https://scribe-api.telepatia.ai/v1/sso/metadata` |

## Exchanging an assertion

Send the base64-encoded SAML response with your institution key:

```bash theme={null}
curl -X POST https://scribe-api.telepatia.ai/v1/sso/exchange \
  -H "Authorization: Bearer YOUR_INSTITUTION_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "samlResponse": "PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46..."
  }'
```

Response `200`:

```json theme={null}
{
  "documentId": "acc_1234567890",
  "token": {
    "accessToken": "eyJhbGciOiJIUzI1NiIs...",
    "expiresIn": 3600,
    "refreshToken": "4167f20c-d620-40c4-b0f3-8f2a1c9e7d55"
  }
}
```

Cache the session and reuse it until it expires. Each assertion is **single use** — a second exchange with the same assertion returns `403`.

## Assertion requirements

* The **assertion is signed** (RSA-SHA256, enveloped signature) with the certificate configured for your institution.
* The `Issuer` equals the IdP entity ID configured for your institution.
* The `Audience` equals the SP entity ID above.
* The `Destination` equals the ACS URL above.
* The validity window (`NotBefore` / `NotOnOrAfter`) covers the moment of the exchange.
* The assertion carries an **`AttributeStatement`** — standard IdPs always include one.
* The doctor's email arrives as the subject `NameID` (format `emailAddress`), or in the attribute named by the optional **email attribute** setting.

## Refreshing the session

The refresh token is the credential — no institution key and no assertion:

```bash theme={null}
curl -X POST https://scribe-api.telepatia.ai/v1/sso/refresh \
  -H "Content-Type: application/json" \
  -d '{
    "refreshToken": "4167f20c-d620-40c4-b0f3-8f2a1c9e7d55"
  }'
```

The response carries the same session shape as the exchange. Refresh tokens **rotate**: the old one is revoked when the new pair is issued, and reusing it returns `401`.

## Calling the API as the doctor

Use the access token exactly where the API key would go:

```bash theme={null}
curl https://scribe-api.telepatia.ai/v1/scribe-session-configurations \
  -H "Authorization: Bearer DOCTOR_ACCESS_TOKEN"
```

## Error responses

| Status | Meaning                                                                                                                  |
| ------ | ------------------------------------------------------------------------------------------------------------------------ |
| `401`  | Invalid or expired institution key, or a rotated refresh token                                                           |
| `403`  | SSO disabled, key without the `saml:exchange` permission, replayed assertion, or doctor not linked to the institution    |
| `422`  | The assertion failed verification: signature, issuer, audience, destination, validity, or a missing `AttributeStatement` |

### Example: replayed assertion

```json theme={null}
{
  "error": {
    "type": "invalid_request_error",
    "code": "permission_denied",
    "message": "SAML assertion could not be exchanged for a session."
  }
}
```

### Example: invalid assertion

```json theme={null}
{
  "error": {
    "type": "invalid_request_error",
    "code": "parameter_invalid",
    "message": "SAML assertion could not be exchanged for a session."
  }
}
```
