> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.milliseconds.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.milliseconds.ai/_mcp/server.

# Limits and rate limits

The API enforces size limits on each request body. Rate limits depend on the key type and the organization's production tier, and every model shares them.

Test keys use the monthly test tokens. Production keys use purchased credits at \$0.04 per million input tokens. The Free plan cannot buy credits. See [Pricing](/reference/pricing).

Every request needs an API key. See [Authentication](/authentication). `decision-machine-1` takes `content-type: application/json` and a body. `rakeaudio` takes the audio file as the body: see [rakeaudio limits and errors](/rakeaudio/limits-and-errors).

## Field limits

These limits apply to `decision-machine-1`.

Every capability reads `text` or `texts`. The other fields depend on the capability.

| Field                     | Capability    | Limit                                                                             |
| ------------------------- | ------------- | --------------------------------------------------------------------------------- |
| `text`                    | all eight     | 1 to 20,000 characters                                                            |
| `texts`                   | all eight     | 1 to 32 items, each 1 to 20,000 characters                                        |
| `image`                   | all eight     | one image, JPEG, PNG or WebP, at most 5 MB decoded                                |
| `detail`                  | all eight     | `low`, `medium` or `high`; `medium` by default                                    |
| `statement`               | yes-no        | minimum 1 character                                                               |
| `statements`              | yes-no        | 1 to 32 items, each minimum 1 character                                           |
| `when_true`, `when_false` | yes-no        | no length limit                                                                   |
| `labels` (array form)     | classify      | 2 to 64 items, each minimum 1 character                                           |
| `labels` (object form)    | classify      | name to description map, each name minimum 1 character, no item count limit       |
| `tree`                    | classify-tree | 2 to 64 entries per level, each name minimum 1 character                          |
| `tree` depth              | classify-tree | at most 8 levels                                                                  |
| `scale`                   | rate          | 2 to 10 items, each minimum 1 character, ordered low to high                      |
| `question`                | answer        | minimum 1 character                                                               |
| `questions`               | answer        | 1 to 32 items, each minimum 1 character                                           |
| `types` (array form)      | entities      | 1 to 64 items, each minimum 1 character                                           |
| `types` (object form)     | entities      | type to description map, no item count limit                                      |
| `schema`                  | extract       | object with `properties`, no size limit on text; at most 5 fields with an `image` |
| `field`                   | verify        | object `{ name, description }`, `name` minimum 1 character                        |
| `value`                   | verify        | string or number, required                                                        |
| `state`                   | systemone     | string, object or array, at most 20,000 characters once rendered                  |
| `questions`               | systemone     | 1 to 32 named questions                                                           |
| `criteria` (`choice`)     | systemone     | 2 to 64 options                                                                   |
| `criteria` (`score`)      | systemone     | 2 to 10 levels                                                                    |

> **Note**
>
> Send exactly one of `text`, `texts` or `image`. All eight capabilities reject a request that carries `text` and `texts` together, and a request that carries none of the three. Both cases return the message `body: provide text, texts or image`. An `image` may travel with a `text`.

## What a breach returns

A limit breach returns `400` with the standard error envelope. The message names the field and the bound. These captures are real responses:

```json
{"error":{"code":"invalid_request","message":"text: Too big: expected string to have <=20000 characters"}}
{"error":{"code":"invalid_request","message":"texts: Too big: expected array to have <=32 items"}}
{"error":{"code":"invalid_request","message":"labels: Too small: expected array to have >=2 items"}}
{"error":{"code":"invalid_request","message":"labels: Too big: expected array to have <=64 items"}}
{"error":{"code":"invalid_request","message":"scale: Too big: expected array to have <=10 items"}}
{"error":{"code":"invalid_request","message":"tree.billing.labels: each level needs 2 to 64 labels"}}
{"error":{"code":"invalid_request","message":"tree.a.labels.a.labels.a.labels.a.labels.a.labels.a.labels.a.labels.a.labels: tree is deeper than 8 levels"}}
```

Validation reports up to three problems, joined with `; `. Each one reads `path: message`. See [Errors](/reference/errors) for every code.

## Rate limits

Test and production keys use separate rate-limit buckets within each organization. All keys of the same type share their buckets.

Buckets refill continuously, not on a fixed minute boundary. Creating more keys does not increase the limits.

| Key type          | Inference requests per minute (RPM) | Input tokens per minute |
| ----------------- | ----------------------------------- | ----------------------- |
| Test              | 30                                  | 500,000                 |
| Production Tier 1 | 1,000                               | 1,000,000               |
| Production Tier 2 | 5,000                               | 1,000,000               |
| Production Tier 3 | 5,000                               | Unlimited               |
| Production Tier 4 | 10,000                              | Unlimited               |

RPM and usage count admitted inference request units, not HTTP calls or successful results. One HTTP call can consume many units.

A `rakeaudio` call counts toward RPM. Its input tokens are billed after the transcript, so they do not count toward the input-tokens-per-minute limit.

| Call                                         | Inference request units              |
| -------------------------------------------- | ------------------------------------ |
| One text                                     | 1                                    |
| 24 `texts`                                   | 24                                   |
| One text with 3 `statements`                 | 3                                    |
| 2 `texts` with 3 `statements` or `questions` | 6                                    |
| `classify-tree`                              | 1 per text, regardless of tree depth |
| OpenAI-compatible chat completion            | 1                                    |
| TypeSafe-compatible `systemone`              | 1 per question                       |
| `rakeaudio` transcription                    | 1 per file                           |

Text counts multiply by statement or question counts. A missing array contributes one to the calculation.

Admitted calls still consume their units if processing later fails. Authenticated invalid or non-inference calls that reach admission consume at least one unit, including `GET` calls.

The API keeps existing `requests` and `rpm` field names. These names refer to inference request units. Input-token limits count each call's input tokens.

### Production tiers

Every production tier requires a paid credit bundle of at least \$10. Auto top-up and a saved credit card are optional.

| Tier   | Lifetime paid spend | Time since first payment |
| ------ | ------------------- | ------------------------ |
| Tier 1 | At least \$10       | Immediate                |
| Tier 2 | At least \$50       | At least 14 days         |
| Tier 3 | At least \$250      | At least 21 days         |
| Tier 4 | At least \$500      | At least 30 days         |

Your organization receives the highest tier for which it meets both thresholds. The monthly test tokens do not count toward paid spend.

Billing synchronization updates the saved access status and tier. Inference requests use that saved status without fetching billing records.

> **Note**
>
> Need immediate assistance with a higher tier? Reach out to our support team: [support@milliseconds.ai](mailto:support@milliseconds.ai).

> **Note**
>
> One call may be larger than the whole token budget. That call goes through. The next calls wait for the bucket to refill. The request bucket works differently: a batch needs its full count of request units, or it returns `429` with a `retry-after` that covers the shortfall.

### Rate-limit headers

The API checks and reserves request units before processing an admitted call. Successful admission returns the bucket state after that reservation.

A rate-limit refusal returns the current bucket state without spending units. Headers are snapshots; concurrent calls can change the remaining balance:

| Header                           | Meaning                                                       |
| -------------------------------- | ------------------------------------------------------------- |
| `x-ratelimit-limit-requests`     | The inference request-unit limit per minute.                  |
| `x-ratelimit-limit-tokens`       | The input-token limit per minute.                             |
| `x-ratelimit-remaining-requests` | The inference request units left in the bucket.               |
| `x-ratelimit-remaining-tokens`   | The input tokens left in the bucket.                          |
| `x-ratelimit-reset-requests`     | The time until the request bucket is full, for example `12s`. |
| `x-ratelimit-reset-tokens`       | The time until the token bucket is full, for example `6m0s`.  |

The reset values read as a duration: `12s`, `6m0s`, `1h2m3s`.

Tiers with unlimited input tokens omit the token limit, remaining, and reset headers. Request-limit headers still apply.

### What a breach returns

A breach returns `429` with the standard envelope and a `retry-after` header in seconds:

```json
{"error":{"code":"rate_limit_exceeded","message":"Rate limit reached for requests on this organization. Retry after 3s."}}
```

```json
{"error":{"code":"rate_limit_exceeded","message":"Rate limit reached for tokens on this organization. Retry after 12s."}}
```

> **Warning**
>
> Wait for `retry-after` before you send the call again. A retry inside that window fails again. The rejected call spends no request unit and no tokens, but it wastes a round trip.

Read `x-ratelimit-remaining-requests` and `x-ratelimit-remaining-tokens` on every response. Slow your concurrency down before you reach zero.

## Response headers per call

Every capability response carries the input size and the model processing time:

| Header           | Meaning                                                                                                       |
| ---------------- | ------------------------------------------------------------------------------------------------------------- |
| `x-input-chars`  | The number of input characters. For `texts`, the sum over all items.                                          |
| `x-input-tokens` | The input tokens billed for this call. This is the billing unit.                                              |
| `x-inference-ms` | The model processing time for this request, in milliseconds. It excludes queueing, network and the API layer. |

The OpenAI-compatible routes omit the two input headers. They report the same count inside `usage`. They do return `x-inference-ms`. The TypeSafe-compatible route returns all three headers.

## No credits left

Test requests consume only the monthly test tokens. Production requests consume only purchased credits. Neither key type can use the other credit pool.

When the applicable balance is exhausted, the API returns `429 insufficient_quota` without `retry-after`.

* For test keys, wait for the monthly allowance to replenish.
* For production keys, add purchased credits in [Billing](https://console.milliseconds.ai/billing).

Do not retry on a timer. A credit purchase does not restore an exhausted test allowance.

## Production access disabled

Production keys return `403 production_access_disabled` if the organization no longer meets the [paid-bundle requirement](/authentication#enable-production-access), or its saved billing status needs a refresh.

Open [Billing](https://console.milliseconds.ai/billing) to refresh your status. Buy a qualifying credit bundle only if you have no qualifying purchase. The console warns on every page when production keys exist without access.

## 529 is the backpressure signal

Inference runs on a fixed pool of slots. When every slot stays busy, the API returns `529`:

```json
{"error":{"code":"overloaded","message":"Every inference slot stayed busy. Retry with backoff."}}
```

> **Warning**
>
> Treat `529` as "retry later", never as "the request was wrong". The body is unchanged and correct. Retry with exponential backoff and jitter. The call goes through once a slot frees.

Retry `429` with `retry-after`, `529` and `502`. Never retry `400`, `401`, `403`, `404` or a `429` with the code `insufficient_quota`: the request or the balance must change first. The examples below retry `502` and `529`.

#### curl

```bash
curl -s --retry 5 --retry-delay 1 --retry-all-errors \
  https://api.milliseconds.ai/v1/decision-machine-1/yes-no \
  -H 'content-type: application/json' \
  -H 'authorization: Bearer test_sk-...' \
  -d '{
    "text": "The customer asks for a refund before Friday.",
    "statement": "This message is urgent"
  }'
```

#### Python

```python
import random
import time

import requests

URL = "https://api.milliseconds.ai/v1/decision-machine-1/yes-no"


def decide(body, attempts=5):
    for attempt in range(attempts):
        response = requests.post(URL, headers={"authorization": "Bearer test_sk-..."}, json=body, timeout=30)
        if response.status_code not in (502, 529):
            response.raise_for_status()
            return response.json()
        time.sleep(2**attempt * 0.5 + random.random() * 0.25)
    raise RuntimeError("decision-machine-1 stayed busy")


print(decide({"text": "The customer asks for a refund before Friday.",
              "statement": "This message is urgent"}))
```

#### TypeScript

```typescript
const URL = "https://api.milliseconds.ai/v1/decision-machine-1/yes-no";

async function decide(body: unknown, attempts = 5): Promise<unknown> {
  for (let attempt = 0; attempt < attempts; attempt++) {
    const response = await fetch(URL, {
      method: "POST",
      headers: { "content-type": "application/json", authorization: "Bearer test_sk-..." },
      body: JSON.stringify(body),
    });
    if (response.status !== 502 && response.status !== 529) {
      if (!response.ok) throw new Error(await response.text());
      return response.json();
    }
    const wait = 2 ** attempt * 500 + Math.random() * 250;
    await new Promise((resolve) => setTimeout(resolve, wait));
  }
  throw new Error("decision-machine-1 stayed busy");
}

console.log(
  await decide({
    text: "The customer asks for a refund before Friday.",
    statement: "This message is urgent",
  }),
);
```

A `429` carries `retry-after` in seconds. A `502` and a `529` carry no such header, so pick your own backoff there.

## Stay inside the limits

* Split documents above 20,000 characters yourself. Send each part as one item of `texts`.
* Keep a batch at 32 items or fewer. Chunk a longer queue into groups of 32 and send them in sequence.
* Batch `statements` and `questions` instead of `texts` where you can. They ride one model call, while each item of `texts` costs its own call. Every item still spends one request unit.
* Prefer the object form of `labels` and `types` when you need more than 64 entries. Descriptions also improve accuracy.
* Log `x-input-tokens` on every call. It records the input tokens billed for that call.
* Watch `x-ratelimit-remaining-requests` and `x-ratelimit-remaining-tokens`. Large batches spend both buckets fast: a batch of 32 texts is 32 inference request units.

## Next

* [Errors](/reference/errors) lists every code, status and cause.
* [Batching](/capabilities/batching) covers `texts`, result order and the 32 limit.
* [Long text and chunking](/concepts/long-text) explains what happens above 2,000 characters.