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

> Signed links for your users, API keys for your servers.

Verso Fetch has two credentials, for two audiences.

| Credential | Who uses it | Where |
| - | - | - |
| App secret | Your backend | `signLink()` only, to sign connect and manage links |
| API key (`vsk_…`) | Your backend | `Authorization: Bearer` on every API call |

Both come from onboarding. Keep both server-side; neither belongs in a browser or a mobile app.

## Signed links

The hosted pages (`/start` to connect, `/manage` to review and delete) are opened with a JWT your backend signs with the app secret.

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

const connect = await signLink(
  { appId: "app_yourapp", userRef: "user_123", scopes: ["conversations:read"], purpose: "connect" },
  process.env.VERSO_APP_SECRET!,
);
const manage = await signLink(
  { appId: "app_yourapp", userRef: "user_123", scopes: ["conversations:read"], purpose: "manage" },
  process.env.VERSO_APP_SECRET!,
);
```

| Option | Required | Description |
| - | - | - |
| `appId` | Yes | Your app id |
| `userRef` | Yes | Your identifier for the user. Any string; it is returned in every webhook and is the key of `GET /api/connections`. |
| `scopes` | Yes | `["conversations:read"]`, the only scope today |
| `purpose` | Yes | `connect` opens `/start`, `manage` opens `/manage` |
| `expiresInSeconds` | No | Default and maximum 900 (15 minutes) |
| `baseUrl` | No | Default `https://connect.tryverso.ai` |

Properties of a link:

* Signed with HS256 using your app secret; Verso verifies it against the secret it holds for your app.
* Expires after at most 15 minutes. An expired link shows an error page; sign a new one.
* Single use. A connect link is consumed when the page starts the login flow, not when the URL is fetched, so link previews in chat apps do not burn it. A manage link is consumed when the page loads.
* Scopes are checked against the scopes allowed for your app.

## API keys

API keys start with `vsk_` and are sent as a Bearer token.

```bash theme={null}
curl -s "https://connect.tryverso.ai/api/connections?userRef=user_123" \
  -H "Authorization: Bearer $VERSO_API_KEY"
```

Several keys can be active at the same time, so rotation has no downtime: ask for a new key, deploy it, then ask us to revoke the old one. Only a hash of each key is stored; a lost key cannot be recovered, only replaced.

<Warning>
  Using the app secret as a Bearer token still works for integrations built before API keys existed, but it is deprecated. New integrations must use an API key.
</Warning>

## Rate limits

`GET /api/connections` and `GET /api/conversations` are limited to 60 requests per minute per app. Every response carries the current state; a `429` also carries `Retry-After`.

| Header | Meaning |
| - | - |
| `RateLimit-Limit` | 60 |
| `RateLimit-Remaining` | Requests left in the current minute |
| `RateLimit-Reset` | Unix timestamp when the minute ends |
| `Retry-After` | Seconds to wait, on `429` only |

`POST /api/purge` and `POST /api/ingest-export` are not rate limited.
