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

# Sub-accounts: keys, limits and usage per end customer

> Give each of your customers its own API keys, credit limit and concurrency limit, keep their results apart, and read what each one used. You get one bill.

If you run querying.ai inside your own product, create one sub-account per customer. A
sub-account has its own API keys, limits, tasks, batches and monitors. Its credits come from
your balance, and the usage endpoints tell you what to charge each customer.

## How a sub-account works

* **Its own keys and results.** A sub-account key submits and reads tasks, batches and
  monitors like any other key, and sees only its own work. A task created with Acme's key
  returns `404` to Globex's key and to your own keys.
* **Your credits.** Its tasks are charged to your balance. A sub-account has no balance, so its
  keys get `403 SUB_ACCOUNT_FORBIDDEN` from `GET /v1/credits` and `GET /v1/usage`.
* **Your capacity.** Your plan's or contract's concurrency limit covers your own tasks and
  every sub-account's together, so sub-accounts never add capacity. `GET /v1/async/status` counts
  that shared queue for you, and a sub-account's key gets `403 SUB_ACCOUNT_FORBIDDEN` from it.
* **Your monitor limits.** The `monitors` limit in `GET /v1/monitors/capabilities` counts
  your monitors and every sub-account's together, and the `prompts` and `tasksPerRun` limits
  apply to a sub-account's monitors as they do to yours.
* **Its own limits.** `monthlyCreditLimit` caps what it can be charged in a UTC calendar
  month. `concurrencyLimit` caps its queued and running tasks inside your limit. Both are
  optional.
* **One level.** A sub-account cannot have sub-accounts. Alert emails for its monitors go to
  your account's admins.

## Who can manage sub-accounts

Call the `/v1/sub-accounts` endpoints with one of your own API keys that has no engine scope
and no daily credit limit. Any other key gets `403 SUB_ACCOUNT_FORBIDDEN`, because a
restricted key could otherwise issue keys without its own restrictions. A sub-account's own
keys get the same error.

```json theme={"theme":"css-variables"}
{
  "success": false,
  "error": {
    "code": "SUB_ACCOUNT_FORBIDDEN",
    "message": "Managing sub-accounts needs an API key without an engine scope or a daily credit limit",
    "timestamp": "2026-10-19T09:00:00.000Z"
  }
}
```

## Create a sub-account and a key

```bash theme={"theme":"css-variables"}
curl -X POST https://api.querying.ai/v1/sub-accounts \
  -H "Authorization: Bearer $QUERYING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Acme", "externalId": "cust_acme", "monthlyCreditLimit": 200000, "concurrencyLimit": 50}'
```

The response is `201`:

```json theme={"theme":"css-variables"}
{
  "success": true,
  "data": {
    "id": "0199d2a4-5b10-7000-8000-000000000001",
    "name": "Acme",
    "externalId": "cust_acme",
    "status": "active",
    "monthlyCreditLimit": 200000,
    "concurrencyLimit": 50,
    "createdAt": "2026-10-09T09:00:00.000Z",
    "suspendedAt": null,
    "usage": { "month": "2026-10", "credits": 0, "tasks": 0, "reservedCredits": 0 }
  }
}
```

| Field | Required | Description |
| - | - | - |
| `name` | Yes | 1–128 characters. |
| `externalId` | No | Your id for this customer, 1–128 characters, unique among your sub-accounts. A second create with the same value returns `409 SUB_ACCOUNT_EXISTS`, so a retry never makes a duplicate. Find the existing one with `GET /v1/sub-accounts?externalId=cust_acme`. |
| `monthlyCreditLimit` | No | 0.01 to 20,000,000 credits, at most two decimals. Omit it or send `null` for no cap. |
| `concurrencyLimit` | No | Integer 1 to 100,000. Omit it or send `null` for no cap. |

Any other field returns `400 VALIDATION_ERROR`, so a misspelled limit never leaves a
customer uncapped.

Then issue the sub-account a key:

```bash theme={"theme":"css-variables"}
curl -X POST https://api.querying.ai/v1/sub-accounts/$SUB_ACCOUNT_ID/keys \
  -H "Authorization: Bearer $QUERYING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "production", "allowedTaskTypes": ["CHATGPT", "GEMINI"]}'
```

```json theme={"theme":"css-variables"}
{
  "success": true,
  "data": {
    "id": "b7e4c1a2-...",
    "name": "production",
    "keyPrefix": "vt_3f9a1c2b",
    "createdAt": "2026-10-09T09:01:00.000Z",
    "allowedTaskTypes": ["CHATGPT", "GEMINI"],
    "dailyCreditLimit": null,
    "expiresAt": null,
    "key": "vt_3f9a1c2b..."
  }
}
```

> [!WARNING]
>
> `key` appears in this response only. Store it now; it is never shown again.

A key takes the same optional settings as a dashboard key: `name` (1–64 characters, default
`default`), `allowedTaskTypes`, `dailyCreditLimit` and `expiresAt` (5 minutes to 2 years
ahead). Send no body for a key with no restrictions.

A sub-account can hold up to 5 active keys; revoked and expired keys do not count. A sixth
returns `409 KEY_LIMIT`. Revoke one first.

Your customer's key works on every endpoint like any other key:

```bash theme={"theme":"css-variables"}
curl -X POST https://api.querying.ai/v1/async/task \
  -H "Authorization: Bearer $ACME_KEY" \
  -H "Content-Type: application/json" \
  -d '{"taskType": "CHATGPT", "payload": {"prompt": "best project management software", "country": "US"}}'
```

## Manage keys

`GET /v1/sub-accounts/{id}/keys` lists every key the sub-account has had, newest first,
including revoked and expired ones. Check `revokedAt` and `expiresAt` to find the active
keys.

```json theme={"theme":"css-variables"}
{
  "success": true,
  "data": {
    "keys": [
      {
        "id": "b7e4c1a2-...",
        "name": "production",
        "keyPrefix": "vt_3f9a1c2b",
        "createdAt": "2026-10-09T09:01:00.000Z",
        "revokedAt": null,
        "lastUsedAt": "2026-10-19T08:58:12.000Z",
        "allowedTaskTypes": ["CHATGPT", "GEMINI"],
        "dailyCreditLimit": null,
        "expiresAt": null
      },
      {
        "id": "4d2a9e10-...",
        "name": "staging",
        "keyPrefix": "vt_8c01d5e7",
        "createdAt": "2026-10-01T12:00:00.000Z",
        "revokedAt": "2026-10-09T09:02:00.000Z",
        "lastUsedAt": "2026-10-08T17:40:03.000Z",
        "allowedTaskTypes": [],
        "dailyCreditLimit": null,
        "expiresAt": null
      }
    ]
  }
}
```

`DELETE /v1/sub-accounts/{id}/keys/{keyId}` revokes a key and returns
`{"success": true, "data": {"revoked": "<keyId>"}}`. A key that is already revoked, or not
the sub-account's, returns `404 NOT_FOUND`.

## Limits

| Limit | Counts | When reached |
| - | - | - |
| `monthlyCreditLimit` | The sub-account's charges this UTC month, plus the credits held by its tasks still running | `429 SUB_ACCOUNT_CREDIT_LIMIT` |
| `concurrencyLimit` | The sub-account's queued and running tasks | `429 PLAN_CONCURRENCY_LIMIT`, `details.source: "sub_account"` |
| Your plan or contract limit | Your tasks and every sub-account's | `429 PLAN_CONCURRENCY_LIMIT`, `details.source: "plan"` or `"contract"` |
| Your balance | Everyone | `402 INSUFFICIENT_CREDITS` |

Only the sub-account that reached its own limit is refused; your other customers keep
running. A raised limit applies to the next request.

```json theme={"theme":"css-variables"}
{
  "success": false,
  "error": {
    "code": "SUB_ACCOUNT_CREDIT_LIMIT",
    "message": "Sub-account monthly credit limit 200000 would be exceeded (199998 used, 3 needed)",
    "details": { "limit": 200000, "used": 199998, "needed": 3, "retryAfterSec": 1814400 },
    "timestamp": "2026-10-09T09:00:00.000Z"
  }
}
```

`retryAfterSec` counts to the start of the next UTC month, when the monthly count starts
over. See [Credits and limits](/concepts/credits-and-limits.md) for the account-wide limits.

## Check a sub-account now

`GET /v1/sub-accounts/{id}` returns the sub-account with this month's usage and
`activeTasks`, the number of its tasks queued or running right now. Compare it with
`concurrencyLimit` to see how close the customer is to its cap.

```json theme={"theme":"css-variables"}
{
  "success": true,
  "data": {
    "id": "0199d2a4-5b10-7000-8000-000000000001",
    "name": "Acme",
    "externalId": "cust_acme",
    "status": "active",
    "monthlyCreditLimit": 200000,
    "concurrencyLimit": 50,
    "createdAt": "2026-10-09T09:00:00.000Z",
    "suspendedAt": null,
    "usage": { "month": "2026-10", "credits": 48211.5, "tasks": 39870, "reservedCredits": 37.5 },
    "activeTasks": 31
  }
}
```

The values above are an example. In `usage`, `credits` and `tasks` are what was charged this
UTC month, and `reservedCredits` is held by tasks still running.

## Usage and billing

Every charge also appears on your own billing ledger; these endpoints split it by customer.
Failed tasks are never charged and never count. Credit amounts carry up to two decimals.

* `GET /v1/sub-accounts` lists your sub-accounts, oldest first, each with this month's
  `usage`. Page with `limit` (default 100, at most 1,000) and pass `nextCursor` back as
  `cursor`.
* `GET /v1/sub-accounts/usage` totals each sub-account over a range of UTC dates.
* `GET /v1/sub-accounts/{id}/usage` gives one sub-account's charges per day and engine.

Both usage endpoints take `from` and `to` as `YYYY-MM-DD`, inclusive. `from` defaults to the
first of the current UTC month and `to` to today; a range spans at most 366 days.

### All sub-accounts

```bash theme={"theme":"css-variables"}
curl "https://api.querying.ai/v1/sub-accounts/usage?from=2026-10-01&to=2026-10-31" \
  -H "Authorization: Bearer $QUERYING_API_KEY"
```

For example:

```json theme={"theme":"css-variables"}
{
  "success": true,
  "data": {
    "from": "2026-10-01",
    "to": "2026-10-31",
    "credits": 184200,
    "tasks": 61400,
    "subAccounts": [
      { "id": "0199d2a4-5b10-7000-8000-000000000001", "name": "Acme", "externalId": "cust_acme", "credits": 150000, "tasks": 50000 },
      { "id": "0199d2a4-6c20-7000-8000-000000000002", "name": "Globex", "externalId": "cust_globex", "credits": 34200, "tasks": 11400 }
    ]
  }
}
```

Sub-accounts are listed largest first. A sub-account with no charge in the range is left
out.

### One sub-account

```bash theme={"theme":"css-variables"}
curl "https://api.querying.ai/v1/sub-accounts/$SUB_ACCOUNT_ID/usage?from=2026-10-08&to=2026-10-09" \
  -H "Authorization: Bearer $QUERYING_API_KEY"
```

For example:

```json theme={"theme":"css-variables"}
{
  "success": true,
  "data": {
    "id": "0199d2a4-5b10-7000-8000-000000000001",
    "from": "2026-10-08",
    "to": "2026-10-09",
    "credits": 3062.5,
    "tasks": 2950,
    "days": [
      { "date": "2026-10-08", "taskType": "CHATGPT", "credits": 1500, "tasks": 1500 },
      { "date": "2026-10-08", "taskType": "GEMINI", "credits": 312.5, "tasks": 250 },
      { "date": "2026-10-08", "taskType": "OTHER", "credits": 50, "tasks": 0 },
      { "date": "2026-10-09", "taskType": "CHATGPT", "credits": 1200, "tasks": 1200 }
    ]
  }
}
```

* `credits` and `tasks` at the top are the totals of `days`.
* `days` has one row per UTC date and engine that had a charge, ordered by date, then
  `taskType`. Days without charges have no row.
* `taskType: "OTHER"` is a charge that is not a task, such as a monitor's
  [competitor read](/monitors.md#cost-and-scheduling). It adds credits but no `tasks`.

## Suspend and resume

```bash theme={"theme":"css-variables"}
curl -X PATCH https://api.querying.ai/v1/sub-accounts/$SUB_ACCOUNT_ID \
  -H "Authorization: Bearer $QUERYING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "suspended"}'
```

* Its keys return `401` within five seconds.
* Its monitors stop running, and batch items that have not started end as
  `API_KEY_INACTIVE`.
* Tasks already queued or running finish and are charged.

`{"status": "active"}` restores its keys and monitors. Each monitor runs once when it comes
back rather than catching up on missed runs. The same `PATCH` changes `name`, `externalId`,
`monthlyCreditLimit` and `concurrencyLimit`; `null` removes a limit or the `externalId`. A
sub-account cannot be deleted; suspend it instead.

## Errors

| Status | Code | Cause |
| - | - | - |
| `400` | `VALIDATION_ERROR` | Invalid JSON, an unknown field, a value out of range, or a bad usage range. |
| `403` | `SUB_ACCOUNT_FORBIDDEN` | The key has an engine scope or a daily credit limit, or belongs to a sub-account. |
| `404` | `NOT_FOUND` | No sub-account with that id among yours, or no active key with that id. |
| `409` | `SUB_ACCOUNT_EXISTS` | Another sub-account already has that `externalId`. |
| `409` | `KEY_LIMIT` | The sub-account already has 5 active keys. |

## Endpoints

| Method and path | Purpose |
| - | - |
| [`POST /v1/sub-accounts`](/api-reference/sub-accounts/create-sub-account) | Create |
| [`GET /v1/sub-accounts`](/api-reference/sub-accounts/list-sub-accounts) | List, with `externalId`, `limit` and `cursor` |
| [`GET /v1/sub-accounts/{id}`](/api-reference/sub-accounts/get-sub-account) | One sub-account, with `activeTasks` |
| [`PATCH /v1/sub-accounts/{id}`](/api-reference/sub-accounts/update-sub-account) | Change settings, suspend or resume |
| [`GET /v1/sub-accounts/usage`](/api-reference/sub-accounts/list-usage) | Totals per sub-account |
| [`GET /v1/sub-accounts/{id}/usage`](/api-reference/sub-accounts/get-usage) | One sub-account per day and engine |
| [`GET /v1/sub-accounts/{id}/keys`](/api-reference/sub-accounts/list-keys) | List its keys, including revoked and expired ones |
| [`POST /v1/sub-accounts/{id}/keys`](/api-reference/sub-accounts/issue-key) | Issue a key |
| [`DELETE /v1/sub-accounts/{id}/keys/{keyId}`](/api-reference/sub-accounts/revoke-key) | Revoke a key |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.