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

# API Key Management

> A complete management system for user API tokens, supporting create, update, delete, batch operations, and quota & permission control

<Info>
  All API endpoints use the prefix `https://api.tennda.ai`. Use HTTPS in production to protect authentication tokens; HTTP is recommended for development only.

  A complete management system for user API tokens, supporting create, update, delete, batch operations, and quota & permission control.
</Info>

<Note>
  Note: API keys are tokens you generate under **Personal Center → Access Tokens** for accessing `/api` routes.
</Note>

## User Authentication

The following endpoints require user login authentication. Include `Authorization` in the request headers.

<ParamField header="Authorization" type="string" required>
  User login token, format: `Bearer your_user_token`
</ParamField>

### Common Response Structure

All endpoints return a unified JSON envelope:

| Field     | Type                           | Description                                                         |
| --------- | ------------------------------ | ------------------------------------------------------------------- |
| `success` | boolean                        | Whether the request succeeded                                       |
| `message` | string                         | Message; usually an empty string on success                         |
| `data`    | object / array / number / null | Business data; some endpoints (e.g. create, delete) omit this field |

### Token Object Fields

Token objects returned by list, search, detail, and update endpoints include the following fields (`key` is masked in list/detail/update responses):

| Field                  | Type    | Description                                                                                                                      |
| ---------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `id`                   | integer | Token ID                                                                                                                         |
| `user_id`              | integer | Owner user ID                                                                                                                    |
| `name`                 | string  | Token name                                                                                                                       |
| `key`                  | string  | API Key; list/detail/update endpoints return a masked value (e.g. `abcd**********efgh`); call **Get Token Key** for the full key |
| `status`               | integer | Status: `1` enabled, `2` disabled, `3` expired, `4` quota exhausted                                                              |
| `remain_quota`         | integer | Remaining quota (internal units; 500,000 = \$1 USD)                                                                              |
| `used_quota`           | integer | Used quota (internal units)                                                                                                      |
| `unlimited_quota`      | boolean | Whether quota is unlimited                                                                                                       |
| `model_limits_enabled` | boolean | Whether model limits are enabled                                                                                                 |
| `model_limits`         | string  | Allowed models, comma-separated                                                                                                  |
| `allow_ips`            | string  | IP allowlist, newline- or comma-separated; CIDR supported                                                                        |
| `group`                | string  | Billing group; `auto` means intelligent failover                                                                                 |
| `cross_group_retry`    | boolean | Cross-group retry; only effective when `group` is `auto`                                                                         |
| `expired_time`         | integer | Expiration Unix timestamp in seconds; `-1` means never expires                                                                   |
| `created_time`         | integer | Creation Unix timestamp in seconds                                                                                               |
| `accessed_time`        | integer | Last access Unix timestamp in seconds                                                                                            |

***

### List All Tokens

* **HTTP Method**: GET
* **Path**: `/api/token/`
* **Description**: Paginated list of all tokens for the current user

**Query Parameters**

<ParamField query="p" type="integer" default="1">
  Page number
</ParamField>

<ParamField query="size" type="integer" default="20">
  Page size
</ParamField>

**Request Example**

```javascript theme={null}
const response = await fetch('/api/token/?p=1&size=20', {
  method: 'GET',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_user_token'
  }
});
const data = await response.json();
```

**Success Response**

```json theme={null}
{
  "success": true,
  "message": "",
  "data": {
    "items": [
      {
        "id": 1,
        "user_id": 1001,
        "name": "API Token",
        "key": "abcd**********efgh",
        "status": 1,
        "remain_quota": 1000000,
        "used_quota": 500000,
        "unlimited_quota": false,
        "model_limits_enabled": true,
        "model_limits": "gpt-3.5-turbo,gpt-4",
        "allow_ips": "192.168.1.1\n10.0.0.1",
        "group": "default",
        "cross_group_retry": false,
        "expired_time": 1640995200,
        "created_time": 1640908800,
        "accessed_time": 1640995000
      }
    ],
    "total": 5,
    "page": 1,
    "page_size": 20
  }
}
```

**Error Response**

```json theme={null}
{
  "success": false,
  "message": "Failed to get token list"
}
```

**Response Fields**

| Field            | Type    | Description                                              |
| ---------------- | ------- | -------------------------------------------------------- |
| `data`           | object  | Paginated data                                           |
| `data.items`     | array   | List of Token objects; see **Token Object Fields** above |
| `data.total`     | integer | Total number of tokens                                   |
| `data.page`      | integer | Current page number                                      |
| `data.page_size` | integer | Page size                                                |

***

### Search Tokens

* **HTTP Method**: GET
* **Path**: `/api/token/search`
* **Description**: Search the user's tokens by keyword and token value

**Query Parameters**

<ParamField query="keyword" type="string">
  Search keyword; matches token name
</ParamField>

<ParamField query="token" type="string">
  Token value search; supports partial match (`sk-` prefix optional); supports `%` wildcards, up to 2
</ParamField>

<ParamField query="p" type="integer" default="1">
  Page number
</ParamField>

<ParamField query="size" type="integer" default="20">
  Page size; max 100
</ParamField>

**Request Example**

```javascript theme={null}
const response = await fetch('/api/token/search?keyword=api&token=sk-123', {
  method: 'GET',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_user_token'
  }
});
const data = await response.json();
```

**Success Response**

```json theme={null}
{
  "success": true,
  "message": "",
  "data": {
    "items": [
      {
        "id": 1,
        "user_id": 1001,
        "name": "API Token",
        "key": "abcd**********efgh",
        "status": 1,
        "remain_quota": 1000000,
        "used_quota": 0,
        "unlimited_quota": false,
        "model_limits_enabled": false,
        "model_limits": "",
        "allow_ips": "",
        "group": "default",
        "cross_group_retry": false,
        "expired_time": -1,
        "created_time": 1640908800,
        "accessed_time": 1640995000
      }
    ],
    "total": 1,
    "page": 1,
    "page_size": 20
  }
}
```

**Error Response**

```json theme={null}
{
  "success": false,
  "message": "Failed to search tokens"
}
```

**Response Fields**

| Field            | Type    | Description                                           |
| ---------------- | ------- | ----------------------------------------------------- |
| `data`           | object  | Paginated data; same structure as **List All Tokens** |
| `data.items`     | array   | Matching Token objects                                |
| `data.total`     | integer | Total matching results                                |
| `data.page`      | integer | Current page number                                   |
| `data.page_size` | integer | Page size                                             |

***

### Get Single Token

* **HTTP Method**: GET
* **Path**: `/api/token/:id`
* **Description**: Get detailed information for a specific token

**Path Parameters**

<ParamField path="id" type="integer" required>
  Token ID
</ParamField>

**Request Example**

```javascript theme={null}
const response = await fetch('/api/token/123', {
  method: 'GET',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_user_token'
  }
});
const data = await response.json();
```

**Success Response**

```json theme={null}
{
  "success": true,
  "message": "",
  "data": {
    "id": 123,
    "user_id": 1001,
    "name": "API Token",
    "key": "abcd**********efgh",
    "status": 1,
    "remain_quota": 1000000,
    "used_quota": 200000,
    "unlimited_quota": false,
    "model_limits_enabled": true,
    "model_limits": "gpt-3.5-turbo,gpt-4",
    "allow_ips": "192.168.1.1,10.0.0.1",
    "group": "default",
    "cross_group_retry": false,
    "expired_time": 1640995200,
    "created_time": 1640908800,
    "accessed_time": 1640995000
  }
}
```

**Error Response**

```json theme={null}
{
  "success": false,
  "message": "Token does not exist"
}
```

**Response Fields**

| Field  | Type   | Description                                      |
| ------ | ------ | ------------------------------------------------ |
| `data` | object | Single Token object; see **Token Object Fields** |

***

### Create Token

* **HTTP Method**: POST
* **Path**: `/api/token/`
* **Description**: Create a new API token; batch creation is supported

**Request Body**

<ParamField body="name" type="string" required>
  Token name; max length 30 characters
</ParamField>

<ParamField body="expired_time" type="integer">
  Expiration timestamp; -1 means never expires
</ParamField>

<ParamField body="remain_quota" type="integer">
  Remaining quota
</ParamField>

<ParamField body="unlimited_quota" type="boolean">
  Whether quota is unlimited
</ParamField>

<ParamField body="model_limits_enabled" type="boolean">
  Whether model limits are enabled
</ParamField>

<ParamField body="model_limits" type="array">
  Allowed model list
</ParamField>

<ParamField body="allow_ips" type="string">
  Allowed IP addresses, comma-separated
</ParamField>

<ParamField body="group" type="string">
  Billing group
</ParamField>

**Request Example**

```javascript theme={null}
const response = await fetch('/api/token/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_user_token'
  },
  body: JSON.stringify({
    name: "My API Token",
    expired_time: 1640995200,
    remain_quota: 1000000,
    unlimited_quota: false,
    model_limits_enabled: true,
    model_limits: ["gpt-3.5-turbo", "gpt-4"],
    allow_ips: "192.168.1.1,10.0.0.1",
    group: "default"
  })
});
const data = await response.json();
```

**Success Response**

```json theme={null}
{
  "success": true,
  "message": ""
}
```

**Error Response**

```json theme={null}
{
  "success": false,
  "message": "Token name is too long"
}
```

**Response Fields**

| Field     | Type    | Description                            |
| --------- | ------- | -------------------------------------- |
| `success` | boolean | Whether creation succeeded             |
| `message` | string  | Error message; empty string on success |

<Note>
  On success, the response body does not include a `data` field and does not return the full `key`. Use **Get Token Key** to retrieve the full key when needed.
</Note>

***

### Update Token

* **HTTP Method**: PUT
* **Path**: `/api/token/`
* **Description**: Update token configuration; supports status-only updates and full updates

**Request Body**

<ParamField body="id" type="integer" required>
  Token ID
</ParamField>

<ParamField query="status_only" type="boolean">
  Whether to update status only
</ParamField>

Other fields are the same as **Create Token** and are all optional.

**Request Example (full update)**

```javascript theme={null}
const response = await fetch('/api/token/', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_user_token'
  },
  body: JSON.stringify({
    id: 123,
    name: "Updated Token",
    expired_time: 1640995200,
    remain_quota: 2000000,
    unlimited_quota: false,
    model_limits_enabled: true,
    model_limits: ["gpt-3.5-turbo", "gpt-4"],
    allow_ips: "192.168.1.1",
    group: "vip"
  })
});
const data = await response.json();
```

**Request Example (status only)**

```javascript theme={null}
const response = await fetch('/api/token/?status_only=true', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_user_token'
  },
  body: JSON.stringify({
    id: 123,
    status: 1
  })
});
const data = await response.json();
```

**Success Response**

```json theme={null}
{
  "success": true,
  "message": "",
  "data": {
    "id": 123,
    "user_id": 1001,
    "name": "Updated Token",
    "key": "abcd**********efgh",
    "status": 1,
    "remain_quota": 2000000,
    "used_quota": 100000,
    "unlimited_quota": false,
    "model_limits_enabled": true,
    "model_limits": "gpt-3.5-turbo,gpt-4",
    "allow_ips": "192.168.1.1",
    "group": "vip",
    "cross_group_retry": false,
    "expired_time": 1640995200,
    "created_time": 1640908800,
    "accessed_time": 1640995000
  }
}
```

**Error Response**

```json theme={null}
{
  "success": false,
  "message": "Token has expired and cannot be enabled. Please update the expiration time first, or set it to never expire"
}
```

**Response Fields**

| Field  | Type   | Description                                       |
| ------ | ------ | ------------------------------------------------- |
| `data` | object | Updated Token object; see **Token Object Fields** |

***

### Delete Token

* **HTTP Method**: DELETE
* **Path**: `/api/token/:id`
* **Description**: Delete a specific token

**Path Parameters**

<ParamField path="id" type="integer" required>
  Token ID
</ParamField>

**Request Example**

```javascript theme={null}
const response = await fetch('/api/token/123', {
  method: 'DELETE',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_user_token'
  }
});
const data = await response.json();
```

**Success Response**

```json theme={null}
{
  "success": true,
  "message": ""
}
```

**Error Response**

```json theme={null}
{
  "success": false,
  "message": "Token does not exist"
}
```

**Response Fields**

| Field     | Type    | Description                            |
| --------- | ------- | -------------------------------------- |
| `success` | boolean | Whether deletion succeeded             |
| `message` | string  | Error message; empty string on success |

***

### Batch Delete Tokens

* **HTTP Method**: POST
* **Path**: `/api/token/batch`
* **Description**: Batch delete multiple tokens

**Request Body**

<ParamField body="ids" type="array" required>
  List of Token IDs to delete; required and must not be empty
</ParamField>

**Request Example**

```javascript theme={null}
const response = await fetch('/api/token/batch', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_user_token'
  },
  body: JSON.stringify({
    ids: [1, 2, 3, 4, 5]
  })
});
const data = await response.json();
```

**Success Response**

```json theme={null}
{
  "success": true,
  "message": "",
  "data": 5
}
```

**Error Response**

```json theme={null}
{
  "success": false,
  "message": "Invalid parameters"
}
```

**Response Fields**

| Field     | Type    | Description                            |
| --------- | ------- | -------------------------------------- |
| `success` | boolean | Whether deletion succeeded             |
| `message` | string  | Error message; empty string on success |
| `data`    | integer | Number of tokens successfully deleted  |

***

### Get Token Key

* **HTTP Method**: POST
* **Path**: `/api/token/:id/key`
* **Description**: On-demand retrieval of the full (unmasked) key for a specific token; rate-limited

**Path Parameters**

<ParamField path="id" type="integer" required>
  Token ID
</ParamField>

**Request Example**

```javascript theme={null}
const response = await fetch('/api/token/123/key', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_user_token'
  }
});
const data = await response.json();
```

**Success Response**

```json theme={null}
{
  "success": true,
  "message": "",
  "data": {
    "key": "XyLy1234567890abcdefghijklmnop"
  }
}
```

**Error Response**

```json theme={null}
{
  "success": false,
  "message": "Token does not exist"
}
```

**Response Fields**

| Field      | Type   | Description                                                                    |
| ---------- | ------ | ------------------------------------------------------------------------------ |
| `data`     | object | Key data                                                                       |
| `data.key` | string | Full API Key (without `sk-` prefix; prepend `sk-` when using, i.e. `sk-{key}`) |

<Note>
  This endpoint returns the full key. Store it securely and avoid displaying it in plaintext in logs or frontend pages.
</Note>

***

### Batch Get Token Keys

* **HTTP Method**: POST
* **Path**: `/api/token/batch/keys`
* **Description**: Batch retrieve full keys for multiple tokens; max 100 per request

**Request Body**

<ParamField body="ids" type="array" required>
  List of Token IDs to retrieve keys for; required and must not be empty; max 100
</ParamField>

**Request Example**

```javascript theme={null}
const response = await fetch('/api/token/batch/keys', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_user_token'
  },
  body: JSON.stringify({
    ids: [1, 2, 3]
  })
});
const data = await response.json();
```

**Success Response**

```json theme={null}
{
  "success": true,
  "message": "",
  "data": {
    "keys": {
      "1": "abcd1234efgh5678ijkl9012mnop3456",
      "2": "qrst1234uvwx5678yzab9012cdef3456",
      "3": "hijk1234lmno5678pqrs9012tuvw3456"
    }
  }
}
```

**Error Response**

```json theme={null}
{
  "success": false,
  "message": "Invalid parameters"
}
```

**Response Fields**

| Field       | Type   | Description                                                                                                      |
| ----------- | ------ | ---------------------------------------------------------------------------------------------------------------- |
| `data`      | object | Batch key data                                                                                                   |
| `data.keys` | object | Map from Token ID to full key; keys are Token IDs (strings), values are full `key` values (without `sk-` prefix) |
