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

# Authentication

> JWT link signing for user flows and Bearer tokens for server-to-server calls.

Verso Fetch uses two authentication mechanisms depending on the context.

## JWT links (user-facing flows)

The connect and manage pages are accessed via **signed JWT links**. Your backend generates a short-lived token using `signLink()` from `@verso/core`.

### SignLinkOptions

| Field              | Type                    | Required | Description                                                                |
| ------------------ | ----------------------- | -------- | -------------------------------------------------------------------------- |
| `appId`            | `string`                | Yes      | Your app identifier (e.g. `app_yourapp`)                                   |
| `userRef`          | `string`                | Yes      | Your internal user ID                                                      |
| `scopes`           | `string[]`              | Yes      | Requested scopes (e.g. `["conversations:read"]`)                           |
| `purpose`          | `"connect" \| "manage"` | Yes      | Flow type                                                                  |
| `expiresInSeconds` | `number`                | No       | TTL in seconds (default & max: 900 = 15 min)                               |
| `baseUrl`          | `string`                | No       | Base URL for connect/manage pages (default: `https://connect.tryverso.ai`) |

### SignedLink return type

| Field   | Type     | Description                                      |
| ------- | -------- | ------------------------------------------------ |
| `token` | `string` | The raw JWT string                               |
| `url`   | `string` | Full URL: `${baseUrl}/${purpose}?token=${token}` |

### Example

```typescript theme={null}
import { signLink } from "@verso/core";

// Connect link — user grants access to their LLM data
const connect = await signLink({
  appId:    "app_yourapp",
  userRef:  "user_123",
  scopes:   ["conversations:read"],
  purpose:  "connect",
}, process.env.VERSO_SECRET);

// Manage link — user views/deletes their data
const manage = await signLink({
  appId:    "app_yourapp",
  userRef:  "user_123",
  scopes:   ["conversations:read"],
  purpose:  "manage",
}, process.env.VERSO_SECRET);
```

### Security properties

* **Algorithm**: HS256 (HMAC-SHA256)
* **Max TTL**: 15 minutes. Tokens with longer expiration are rejected.
* **Single-use**: Each token contains a unique `jti` (JWT ID). The server inserts it into a `used_jti` table — duplicate usage is rejected with `403`.
* **Scope validation**: Requested scopes are checked against your app's `allowed_scopes` configuration.

### Available scopes

| Scope                  | Description                     |
| ---------------------- | ------------------------------- |
| `conversations:read`   | Read imported conversation data |
| `conversations:write`  | Write/update conversation data  |
| `conversations:delete` | Delete conversation data        |
| `profile:read`         | Read user profile information   |

## Bearer tokens (server-to-server)

The API endpoints (`/api/conversations`, `/api/ingest-export`, `/api/purge`) authenticate via your app secret as a Bearer token.

```bash theme={null}
curl https://connect.tryverso.ai/api/conversations \
  -H "Authorization: Bearer YOUR_APP_SECRET" \
  -G -d "connectionId=uuid"
```

Authentication uses timing-safe comparison against your app's secret stored in the vault.

### Rate limiting

API endpoints are rate-limited to **60 requests per minute per app**. The rate limiter is database-backed (atomic counter per app per minute bucket).

Rate limit headers are included in every response:

| Header                | Description                             |
| --------------------- | --------------------------------------- |
| `RateLimit-Limit`     | Max requests per minute (60)            |
| `RateLimit-Remaining` | Remaining requests in current window    |
| `RateLimit-Reset`     | Unix timestamp when the window resets   |
| `Retry-After`         | Seconds to wait (only on 429 responses) |

<Warning>
  Never expose your app secret in client-side code. All API calls should be
  made from your backend.
</Warning>
