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

# Limits and quotas

> Every ceiling, and the error you get when you hit it

```sh theme={null}
curl https://hollerith.monarcha.ai/v1/model-limits
```

This page is the source of truth for Hollerith's limits. `GET /v1/model-limits` needs no API
key and returns the published limits your deployment is serving.

## Per job

| Limit                    | Value               | Applies to                          |
| ------------------------ | ------------------- | ----------------------------------- |
| Rows in the fitted table | 1,000,000           | `fit`, inline `predict`, `evaluate` |
| Columns                  | 2,000               | every job                           |
| Cells (rows × columns)   | 100,000,000         | the fitted table                    |
| Rows scored per predict  | 200,000             | `predict`, `predict_proba`          |
| Classes                  | 160                 | classification                      |
| Compressed upload        | 5,000,000,000 bytes | every upload                        |

The upload ceiling is a default. A deployment can be configured with a different one, and that
override is not reflected in `/v1/model-limits` — so if an upload is rejected below
5,000,000,000 bytes, ask your administrator what the deployment is set to.

One stale number sits elsewhere in the control plane.

* **The org usage summary returns `maxColsPerJob: 500`.** Nothing enforces it and no job is
  measured against it. The column ceiling is 2,000 — what the SDK checks locally, what the
  worker checks on arrival, and what this page publishes.

## The cell budget

Rows and columns each have a ceiling, and their product has a third. A table has to be under
all three, so the wider it is the shorter it has to be.

Three tables that all sit exactly on the 100,000,000-cell budget:

* **1,000,000 rows × 100 columns** — at the row ceiling.
* **500,000 rows × 200 columns**.
* **50,000 rows × 2,000 columns** — at the column ceiling.

At 2,000 columns you get 50,000 rows, not 1,000,000. Scored rows are counted on their own and
do not draw on the cell budget.

<img className="block dark:hidden" src="https://mintcdn.com/monarcha-53b27419/8XBoJyUBLB_e0wrZ/images/limits-light.svg?fit=max&auto=format&n=8XBoJyUBLB_e0wrZ&q=85&s=cfc08cc7da3f1a473542fdb99485f0c5" alt="Rows against columns on logarithmic axes. The allowed region is bounded by the 1,000,000-row ceiling, the 2,000-column ceiling and the 100,000,000-cell budget. The budget line cuts off the corner where both ceilings would meet, so a table can sit at one ceiling or the other but never at both." width="700" height="440" data-path="images/limits-light.svg" />

<img className="hidden dark:block" src="https://mintcdn.com/monarcha-53b27419/8XBoJyUBLB_e0wrZ/images/limits-dark.svg?fit=max&auto=format&n=8XBoJyUBLB_e0wrZ&q=85&s=b640ee6d89d3f227abb5f81cdc49ba2f" alt="Rows against columns on logarithmic axes. The allowed region is bounded by the 1,000,000-row ceiling, the 2,000-column ceiling and the 100,000,000-cell budget. The budget line cuts off the corner where both ceilings would meet, so a table can sit at one ceiling or the other but never at both." width="700" height="440" data-path="images/limits-dark.svg" />

## Per day

| Quota      | Value     |
| ---------- | --------- |
| Calls      | 10,000    |
| Input rows | 1,000,000 |
| Reset      | 00:00 UTC |

The daily row quota counts input rows only. Output rows are billed but do not draw on it, so a
context-backed predict spends `outputRowCount` of quota and bills twice that.

Quota is reserved when a job is submitted and released when it completes. A burst of concurrent
submits can therefore hit `quota_exceeded` before any of them has run.

## Contexts and waiting

A fitted context lives 7 days from the fit that created it. Predicting against an expired one
raises `fitted_context_expired`; the fix is to fit again.

Blocking calls stop waiting after 900 seconds by default, and raise a Python `TimeoutError`.
The timeout ends the wait, not the job — the job keeps running server-side. Pass `timeout=` to
change it, or `wait=False` to take a handle instead.

## Forecast

| Limit             | Value               |
| ----------------- | ------------------- |
| Context rows      | 1,000,000           |
| Context cells     | 100,000,000         |
| Output rows       | 200,000             |
| Covariate columns | 2,000               |
| Compressed upload | 5,000,000,000 bytes |

Forecast cells are counted as context rows × (covariate columns + 1), because the target column
counts too. A forecast never uses a fitted context, so every call ships the whole context
window and every call is measured against these limits in full.

## What happens at each ceiling

| Ceiling                                    | Code                | HTTP | Retryable |
| ------------------------------------------ | ------------------- | ---- | --------- |
| Rows, columns, cells, scored rows, classes | `dataset_too_large` | 422  | no        |
| Compressed upload bytes                    | `payload_too_large` | 413  | no        |
| Calls or input rows per day                | `quota_exceeded`    | 429  | no        |

`quota_exceeded` is a 429 that is not retryable. A client that retries every 429 will loop
against it; reduce what you send, or wait for the 00:00 UTC reset.

The `dataset_too_large` envelope names the dimension you breached, the value you sent and the
limit. If you breach several at once it names the first. Envelope fields are in
[Errors](/reference/errors).

## Where each limit is checked

* **Rows, columns, cells, scored rows and classes** are checked in the SDK before your table is
  serialized. They fail in under a second and upload nothing.
* **Upload bytes are not checked in the SDK.** The payload is serialized and gzipped in full,
  then rejected at presign with `payload_too_large`. You pay the compression pass, not the
  transfer.
* **The SDK checks against a snapshot bundled in the wheel.** It does not read
  `/v1/model-limits`. Raising a limit server-side takes effect for direct REST callers at once
  and for the SDK only after you install a newer wheel.
* **Daily quota is enforced server-side only.** The SDK has no local view of what you have
  spent today.

## Next

* [Errors](/reference/errors) — all 26 codes, and which four are retryable
* [Usage and billing](/account/usage-and-billing) — what counts as a billable row
* [The fitted context](/concepts/fitted-context) — expiry, and resuming from an id
