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

# Authentication

> Server-side token exchange, per-document signatures, and share tokens.

Qirtaas separates credentials by where they live and what they can do:

| Credential                      | Lives                 | Grants                                 |
| ------------------------------- | --------------------- | -------------------------------------- |
| **Secret API key** (`qrt_sk_…`) | Your backend only     | Minting embed tokens                   |
| **Embed token** (JWT, \~1 h)    | Browser (short-lived) | Read/write the end user's documents    |
| **Signing secret**              | Your backend only     | Computing per-document read signatures |
| **Signature** (`sig` + `exp`)   | Browser (short-lived) | Read one document until `exp`          |
| **Share token**                 | Public                | Read one explicitly shared document    |

Your backend is the broker: the browser never sees the secret key or signing
secret — only the short-lived artifacts derived from them.

<Note>
  Keys are provisioned per organization. Request them via the [key request
  form](https://qirtaas.io/developers?signup=1). Self-hosting? You issue your
  own — see [Self-hosting](/backend/self-hosting).
</Note>

## Embed tokens (authoring)

The editor authenticates every request with an embed token supplied by the
SDK's `getToken` callback. Your backend mints one by exchanging its API key:

### `POST /v1/embed/tokens/`

<ParamField header="Authorization" type="string" required>
  `Bearer qrt_sk_…` — your secret API key.
</ParamField>

<ParamField body="external_user_id" type="string" required>
  A stable identifier for the end user **in your system** (a user id, UUID, or
  even a fixed value if all authors share one identity). Qirtaas auto-provisions
  an identity under your organization per distinct id.
</ParamField>

Response `200`:

```json theme={null}
{
  "token": "eyJhbGciOiJIUzI1NiIs…",
  "expires_at": "2026-07-04T13:22:05.104517"
}
```

Errors: `400 { "error": "external_user_id_required" }`, `401` invalid or
revoked key, `403 { "error": "organization_suspended" }`.

Tokens expire after **1 hour**. The SDK calls `getToken` on init, proactively
before expiry, and once more after a `401` — your endpoint should simply mint a
fresh token on every call.

### Example endpoint

Authenticate the request with your own session auth, then exchange:

<CodeGroup>
  ```python Python theme={null}
  import os
  import requests

  QIRTAAS_API_KEY = os.environ["QIRTAAS_API_KEY"]

  def mint_embed_token(external_user_id: str) -> dict:
  resp = requests.post(
  "https://api.qirtaas.io/v1/embed/tokens/",
  headers={"Authorization": f"Bearer {QIRTAAS_API_KEY}"},
  json={"external_user_id": external_user_id},
  timeout=10,
  )
  resp.raise_for_status()
  return resp.json() # {"token": "…", "expires_at": "…"}

  ```

  ```ts Node theme={null}
  async function mintEmbedToken(externalUserId: string) {
    const res = await fetch("https://api.qirtaas.io/v1/embed/tokens/", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.QIRTAAS_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ external_user_id: externalUserId }),
    });
    if (!res.ok) throw new Error(`token exchange failed (${res.status})`);
    return res.json(); // { token, expires_at }
  }
  ```
</CodeGroup>

<Warning>
  Whoever can call your token endpoint can edit that identity's documents. Gate
  it with your app's own authentication, and scope `external_user_id` to the
  authenticated user.
</Warning>

## Signatures (cross-user reads)

Embed tokens are scoped to one identity's documents. To let *other* users
**read** a document (ex: student reading teacher's document) the renderer
uses a per-document, expiring **HMAC signature** instead. Your backend computes
it after running its own access check:

```
signature = hex(HMAC_SHA256(signing_secret, "<document_id>|<exp>"))
```

`exp` is a unix timestamp. The renderer sends the pair as `?sig=&exp=` query
parameters, and Qirtaas recomputes the same HMAC under the document owner's
secret to verify.

```python Python theme={null}
import hashlib, hmac, time

def sign_read(doc_id: str, ttl: int = 3600) -> dict:
    exp = int(time.time()) + ttl
    sig = hmac.new(
        SIGNING_SECRET.encode(),
        f"{doc_id}|{exp}".encode(),
        hashlib.sha256,
    ).hexdigest()
    # Matches the SDK renderer's getSignature() contract directly.
    return {"signature": sig, "exp": exp}
```

Feed it to the renderer via `getSignature`:

```ts theme={null}
qirtaas.mountRenderer("#viewer", {
  documentId,
  getSignature: async () => {
    const res = await fetch(`/api/lessons/${lessonId}/signature`);
    return res.json(); // { signature, exp }
  },
});
```

An invalid or expired signature is a `403 { "error": "invalid_signature" }` —
signatures are not refreshable the way tokens are; the renderer surfaces the
error via `onError`.

## Share tokens (public reads)

A document explicitly shared by its author gets an opaque **share token** that
resolves it publicly via `GET /v1/documents/shared/{token}/` — no key, no
signature. Pass it straight to the renderer as `shareToken`. See
[Renderer](/sdk/renderer#auth-modes).

The token is **minted by the backend when sharing is turned on**, not by the
SDK: call the [client](/sdk/client)'s `setSharing(documentId, true)` (which
wraps `PATCH /v1/documents/{id}/share/` over the embed-token channel) and
store or link the returned `share_token`. Turning sharing off revokes the
token. Self-hosted backends implement the same
[share endpoints](/backend/documents#patch-v1documentsidshare).

## Which read auth should I use?

| Situation                              | Use                                         |
| -------------------------------------- | ------------------------------------------- |
| Author viewing their own document      | `getToken` (same embed token as the editor) |
| Another user, access controlled by you | `getSignature`                              |
| Anyone with the link                   | `shareToken`                                |
