## Overview

All endpoints use JSON. Authentication is via the `x-api-key` header.

## Generate JWT Token

Generate a JWT token for avatar authentication.

**Endpoint:** `POST https://trulience.com/auth/generate-token`

### Request Headers

```
x-api-key: tru_live_***
Content-Type: application/json
```

### Request Body

```json
{
  "avatar_id": "4386676996480451678",
  "expire_at": 300
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `avatar_id` | string | Yes | Avatar ID to generate token for |
| `expire_at` | integer | No | Token expiry in seconds. Default: 120. Range: 60-3600 |

### Success Response (200 OK)

```json
{
  "jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires_in": 300,
  "avatar_id": "4386676996480451678"
}
```

| Field | Type | Description |
|-------|------|-------------|
| `jwt` | string | JWT token for avatar authentication |
| `expires_in` | integer | Token expiry in seconds |
| `avatar_id` | string | Avatar ID the token is valid for |

### Error Responses

**400 Bad Request - Invalid expire_at range**
```json
{
  "error": {
    "code": 19,
    "message": "expire_at must be between 60 and 3600 seconds"
  }
}
```

**401 Unauthorized - Invalid API Key**
```json
{
  "error": {
    "code": 16,
    "message": "Invalid API Key"
  }
}
```

**403 Forbidden - Missing Scope**
```json
{
  "error": {
    "code": 34,
    "message": "API key lacks required scope"
  }
}
```

**403 Forbidden - Avatar Not Allowed**
```json
{
  "error": {
    "code": 33,
    "message": "API key is not authorized for this avatar"
  }
}
```

**403 Forbidden - Origin Not Allowed**
```json
{
  "error": {
    "code": 35,
    "message": "Origin is not allowed for this API key"
  }
}
```

**429 Rate Limited**
```json
{
  "error": {
    "code": 36,
    "message": "Too many requests. Please slow down."
  }
}
```

**500 Internal Server Error**
```json
{
  "error": {
    "code": 45,
    "message": "Internal server error occurred"
  }
}
```

### Notes

- Tokens are single-use and cannot be refreshed
- Generate a new token for each user session
- Requires API key scope: `generate_tokens`

## List Avatars

Retrieve a paginated list of avatars for your account.

**Endpoint:** `GET /avatars`

### Request Headers

```
x-api-key: tru_live_***
```

### Query Parameters

```
GET /avatars?page=0&size=20
```

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `page` | integer | No | Page number (0-indexed). Default: 0 |
| `size` | integer | No | Results per page. Default: 20. Max: 100 |

### Success Response (200 OK)

```json
{
  "avatars": [
    {
      "id": "4386676996481151678",
      "name": "Amanda"
    },
    {
      "id": "993237220279768598",
      "name": "Amanda - Trulience"
    }
  ],
  "pagination": {
    "page": 0,
    "size": 20,
    "total": 82,
    "total_pages": 5,
    "has_next": true
  }
}
```

| Field | Type | Description |
|-------|------|-------------|
| `avatars` | array | Array of avatar objects |
| `avatars[].id` | string | Avatar unique identifier |
| `avatars[].name` | string | Avatar display name |
| `pagination.page` | integer | Current page number (0-indexed) |
| `pagination.size` | integer | Results per page |
| `pagination.total` | integer | Total number of avatars |
| `pagination.total_pages` | integer | Total number of pages |
| `pagination.has_next` | boolean | Whether more pages are available |

### Error Responses

**401 Unauthorized - Invalid API Key**
```json
{
  "error": {
    "code": 16,
    "message": "Invalid API Key"
  }
}
```

**403 Forbidden - Missing Scope**
```json
{
  "error": {
    "code": 34,
    "message": "API key lacks required scope"
  }
}
```

**429 Rate Limited**
```json
{
  "error": {
    "code": 36,
    "message": "Too many requests. Please slow down."
  }
}
```

**500 Internal Server Error**
```json
{
  "error": {
    "code": 45,
    "message": "Internal server error occurred"
  }
}
```

### Notes

- Avatars are sorted alphabetically by name
- Requires API key scope: `read_avatars`

## Query Session State

Look up the current state of a live avatar session — avatar status, avatar ID, user ID, and last-activity time — by `sessionId`. Use this from your backend to poll session status without needing your own client-side event plumbing.

**Endpoint:** `GET https://trulience.com/session/query`

### Creating a key with query permission

1. Sign in to the Trulience Dashboard with an account that has access to your organization's avatars
2. Open **Developer → API Keys**
3. Select **Create New Key** and give it a descriptive name (e.g. "Backend — session status polling")
4. Turn on the **Read Session State** permission. Keys without it are rejected by this endpoint
5. Save, then copy the key immediately — it is shown only once and cannot be retrieved afterwards
6. Store the key on your server only. Treat it like a password; never put it in client-side code

If the key is restricted to specific avatars (a per-avatar allowlist), it can only query sessions for those avatars — see error `33` below.

### Request Headers

```
x-api-key: tru_live_***
```

### Query Parameters

```
GET /session/query?sessionId=sess_9f2a7c1e&fields=avatarStatus,avatarId,userId,lastActivityAt
```

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `sessionId` | string | Yes | The session to query. Max 128 characters. |
| `fields` | string (comma-separated) | Yes | Which fields to return: one or more of `avatarStatus`, `avatarId`, `userId`, `lastActivityAt`. No default — there is no "return everything" shortcut, so name every field you need. |

### Success Response (200 OK)

```json
{
  "status": "OK",
  "failReason": 0,
  "sessionId": "sess_9f2a7c1e",
  "state": {
    "avatarStatus": "TALKING",
    "avatarId": 456,
    "userId": 789,
    "lastActivityAt": 1769784000123
  }
}
```

`state` contains only the fields you listed in `fields` — request a subset to get a smaller response. A field with no recorded value is still present, as `null`, rather than omitted.

| Field | Type | Description |
|-------|------|-------------|
| `state.avatarStatus` | string \| null | One of `IDLE`, `LOADED`, `UNLOADED`, `LISTENING`, `THINKING`, `TALKING` |
| `state.avatarId` | number | Avatar ID for this session |
| `state.userId` | number | User ID for this session |
| `state.lastActivityAt` | number \| null | Epoch milliseconds of the most recent activity (ping or message). `null` if none has been recorded yet |

### Failure Response

Failures use the same shape plus a `message`:

```json
{
  "status": "FAIL",
  "failReason": 34,
  "message": "API key lacks required scope"
}
```

Querying a session that has already ended is not a failure on your part — it simply returns error `65` below.

### Error Reference

| HTTP | Code | Message | What it means & what to do |
|------|------|---------|-----------------------------|
| 200 | 0 | OK | Success — no action needed |
| 401 | 30 | API key is required | Missing `x-api-key` header |
| 401 | 29 | Invalid API key format | Malformed key — check for copy/paste truncation |
| 401 | 31 | API key not found in database | Key doesn't exist — confirm account/deletion |
| 401 | 16 | Invalid API Key | Key doesn't validate — issue a new one if persistent |
| 403 | 32 | This API key has been disabled | Re-enable it or use a different key |
| 429 | 36 | Too many requests. Please slow down. | Back off and retry; avoid tight loops |
| 403 | 34 | API key lacks required scope | Enable "Read Session State" on the key |
| 400 | 7 | InvalidOrMissingParams | `sessionId` is missing/over 128 characters, or `fields` is missing/blank/contains an unrecognized name |
| 404 | 65 | Session not found | Already ended, or never existed — usually nothing to do |
| 403 | 24 | Avatar does not belong to your account | Session belongs to a different account |
| 403 | 33 | API key is not authorized for this avatar | Key's avatar allowlist doesn't include this one |
| 500 | 45 | Internal server error occurred | Retry; contact support if persistent |

### Rate Limiting

This endpoint enforces rate limits at two layers, each with different numbers:

- **Per API key** — 100 requests burst, refilling at 50 requests/second by default. Shared with your key's usage of the other endpoints on this page. Exceeding it returns the `429` JSON shown above (`failReason: 36`).
- **Per source IP address, at the network edge** — **~60 requests/second sustained, with a burst of 120**. It's set above the per-key ceiling so it won't bind for legitimate traffic — the per-key limit above is the actual source of truth for your quota. Because this is rejected before the request reaches the application, the response is a bare HTTP `429` **without** the usual `{"status":"FAIL",...}` JSON body — check the status code, not the response body, when handling throttling on this endpoint. This applies per source IP regardless of which key (or no key) is used, so if you send `/session/query` traffic for many different API keys through one shared outbound IP (e.g. a corporate proxy or NAT), that IP is throttled as a whole.

No `Retry-After` header is returned by either layer — back off and retry (e.g. with exponential backoff) rather than retrying in a tight loop.

Need a higher per-key limit? Contact support.

### Notes

- Requires API key permission: **Read Session State**
- `fields` is required — any single unrecognized field name fails the whole request with a 400 rather than silently returning a truncated response
- This is a point-in-time lookup, not a subscription — poll it if you need updates over time

## Disconnect a Session

End an in-flight avatar session on demand by `session_id`. Use this to terminate sessions from your backend — for example, call moderation — in addition to the natural exit paths (client hangup, transport failure, `max_session_duration`).

**Endpoint:** `POST https://trulience.com/sessions/disconnect`

### Creating a key with disconnect permission

1. Sign in to the Trulience Dashboard with an account that has access to your organization's avatars
2. Open **Developer → API Keys**
3. Select **Create New Key** and give it a descriptive name (e.g. "Backend — call moderation")
4. Turn on the **Disconnect Sessions** permission. Keys without it are rejected by this endpoint
5. Save, then copy the key immediately — it is shown only once and cannot be retrieved afterwards
6. Store the key on your server only. Treat it like a password; never put it in client-side code

If the key is restricted to specific avatars (a per-avatar allowlist), it can only disconnect sessions for those avatars — see error `33` below.

### Request Headers

```
x-api-key: your-api-key
Content-Type: application/json
```

### Request Body

```json
{
  "session_id": "sess_9f2a7c1e"
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `session_id` | string | Yes | The session to disconnect |

### Success Response (200 OK)

```json
{
  "status": "OK",
  "failReason": 0
}
```

### Failure Response

Failures use the same shape plus a `message`:

```json
{
  "status": "FAIL",
  "failReason": 34,
  "message": "API key lacks required scope"
}
```

The call is safe to retry — disconnecting an already-ended session simply returns error `65`, which is not a failure on your part.

### Error Reference

| HTTP | Code | Message | What it means & what to do |
|------|------|---------|-----------------------------|
| 200 | 0 | OK | Success — no action needed |
| 401 | 30 | API key is required | Missing `x-api-key` header |
| 401 | 29 | Invalid API key format | Malformed key — check for copy/paste truncation |
| 401 | 31 | API key not found in database | Key doesn't exist — confirm account/deletion |
| 401 | 16 | Invalid API Key | Key doesn't validate — issue a new one if persistent |
| 403 | 32 | This API key has been disabled | Re-enable it or use a different key |
| 429 | 36 | Too many requests. Please slow down. | Back off and retry; avoid tight loops |
| 403 | 34 | API key lacks required scope | Enable "Disconnect Sessions" on the key |
| 400 | 7 | InvalidOrMissingParams | Missing `session_id` in the body |
| 404 | 65 | Session not found | Already ended — usually nothing to do |
| 403 | 24 | Avatar does not belong to your account | Session belongs to a different account |
| 403 | 33 | API key is not authorized for this avatar | Key's avatar allowlist doesn't include this one |
| 500 | 45 | Internal server error occurred | Retry; contact support if persistent |

### Notes

- Requires API key permission: **Disconnect Sessions**
- Safe to call more than once — an already-ended session returns error `65`