# API Authentication

Viresso has two separate authentication flows:

- **API keys** are created in Viresso and are used to call the API from websites, apps, and servers.
- **Hosted authentication access tokens** are created by the hosted auth endpoints and identify an authenticated app user.

Do not use hosted auth access tokens as API keys.

## API keys

API keys are workspace-scoped credentials. Create them from **Settings → API Keys** in Viresso.

### Sending the API key

Pass the key via the `Authorization` header:

```
Authorization: Bearer {api_key}
```

API keys are not accepted in query parameters.

Each key can have full access or a limited set of abilities. Its effective access is also capped by the workspace permissions of the member who created it. See [API Access](/docs/platform/api-access) for the complete ability list.

### Origin restrictions

API keys can restrict allowed origins (e.g. `example.com`, `*.example.com`, `localhost:5173`). Origin restrictions are enforced against the `Origin` request header.

- For **browser-based clients**, create keys with origin restrictions matching your application domain.
- For **server-side integrations**, create keys without origin restrictions. A restricted key rejects requests without an `Origin` header.

Reading file details and refreshing temporary URLs require `files:read`; file uploads require `files:write`; file deletions require `files:delete`. Each is also limited by the key creator's workspace permissions. Tokenized and signed temporary file downloads do not use API keys. Use `temporary_url` for an expiring link (10 minutes by default; file-details requests accept `expires_in=30` through `600`), or `download_url` for the existing permanent link. See [Files API](/docs/api/files).

An origin mismatch returns `403 Forbidden`:

```json
{
  "message": "Forbidden"
}
```

## Hosted authentication

Hosted authentication adds passwordless app login on top of your Viresso content. It works with a configured content type in your workspace that contains an email field. When a visitor signs in, Viresso finds the matching entry and returns an access token for that app user.

These access tokens are strictly for hosted app authentication. They are not API keys and should not be used for server-to-server API access.

The hosted auth endpoints are available under `POST /api/v1/{workspace}/auth`. Each operation has its own rate-limit bucket so login attempts cannot consume the capacity used by real-time session validation:

| Operation | Limit and scope |
|---|---|
| Request a code | 10 requests per minute per email and 60 per IP |
| Verify a code | 10 requests per minute per OTP and 120 per IP |
| Exchange an auth code | 60 requests per minute per IP |
| Authenticate an access token | 600 requests per minute per access token |

If an allowed origin is configured for hosted login, hosted-auth requests that include an `Origin` header must normalize to that origin (host casing and default ports are treated equivalently). Server-side requests without an `Origin` header are not blocked by this browser-origin check.

### Request a verification code

```
POST /api/v1/{workspace}/auth
```

**Request body:**

| Field | Type | Description |
|---|---|---|
| `email` | string (email) | The user's email address |
| `locale` | string | Locale for the verification email (optional, default `en-US`) |

**Response** (always 200, even if the email is not found — prevents enumeration):

```json
{
  "message": "If the email matches an account, a verification code has been sent.",
  "otp_id": "uuid-string"
}
```

The `otp_id` is always returned so callers can follow the same flow without learning whether the email matched an entry. A verification email is only sent when the email matches a published entry.

### Verify the code

```
POST /api/v1/{workspace}/auth/verify
```

**Request body:**

| Field | Type | Description |
|---|---|---|
| `otp_id` | string (UUID) | The OTP ID from the login response |
| `secret` | string (6 digits) | The verification code sent via email |
| `locale` | string | Locale for the response (optional, default `en-US`) |

**Response:**

```json
{
  "access_token": "random-80-char-string",
  "expires_at": "2026-06-24T09:30:00.000000Z",
  "user_data": { }
}
```

Each OTP can be attempted up to 3 times. After that, a new verification code must be requested.

### Authenticate with an existing token

```
POST /api/v1/{workspace}/auth/authenticate
```

**Request body:**

| Field | Type | Description |
|---|---|---|
| `access_token` | string | An existing hosted auth access token |
| `locale` | string | Locale for the response (optional, default `en-US`) |

**Response:** Same shape as `verify`. Returns the same token (touches it, extends TTL).

### Exchange an authorization code

```
POST /api/v1/{workspace}/auth/exchange
```

**Request body:**

| Field | Type | Description |
|---|---|---|
| `code` | string | An authorization code (generated server-side, 2-minute TTL) |

**Response:** Same shape as `verify`. Issues a new token.

### Hosted auth token response

```json
{
  "access_token": "gH7sK...80-chars...9mQ2",
  "expires_at": "2026-06-24T09:30:00.000000Z",
  "user_data": {
    "id": "01HY8M7M7D4RT5N6P2Q3R4S5T6",
    "name": "Jane Doe",
    "status": "published",
    "fields": {
      "full_name": "Jane Doe",
      "email": "jane@example.com"
    }
  }
}
```

The `user_data` object is the rendered entry for the authenticated app user.

### Token expiration

Hosted auth access tokens expire after 30 days. The `authenticate` endpoint resets the expiration. If a token expires, request a new verification code.

## Related pages

- [API Overview](/docs/api/overview) — Base URL, headers, and error handling.
- [API Entries](/docs/api/entries) — Entry CRUD reference.
- [API Access](/docs/platform/api-access) — API-key abilities, origin restrictions, and rotation.
