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

# Limits

> What an account may hold and do: the catalog of limits, defaults, overrides, holdings and errors

Limits decide how much a user, an organization or a blob may hold, how far a single run may go, which blob types an
organization may create, and how long finished work is kept. Every limit is one entry in one catalog. Each entry has
a default, and an override can change it for one user, organization or blob. The API serves the values in force,
and every refusal names the limit behind it.

## Groups and ids

The catalog is divided into groups. `platform`, labelled **Global**, holds the limits that apply whatever a blob's
type. Every blob type has a group of its own, keyed by its type string, holding everything about that type —
including whether an organization may create it at all.

A limit's id is `<family>.<name>`. The family is `platform` for a global limit, and the last segment of the type
string for a type's limit, so the limits of `blobhub.compute.workflow` are named `workflow.*`.

| Group | Label | Family |
| :- | :- | :- |
| `platform` | Global | `platform` |
| `blobhub.compute.workflow` | Workflow | `workflow` |
| `blobhub.compute.scheduler` | Scheduler | `scheduler` |
| `blobhub.model.onnx` | ONNX Model | `onnx` |
| `blobhub.graph.orientdb` | Graph | `orientdb` |
| `blobhub.dataset.nlp` | NLP Dataset | `nlp` |

Groups always come in this order. An error names a limit by its id; the [limits routes](#reading-limits) return each
limit inside its group.

## Kinds

| Kind | What it bounds | Value |
| :- | :- | :- |
| `quota` | How many of something a holder may hold at once | An integer |
| `ceiling` | How large or how long one operation or one run may be | An integer, in the limit's `unit` |
| `entitlement` | Whether something is allowed, or which things are | `true` or `false`, or a list |
| `retention` | How long finished work is kept | A number of days, or `null` for forever |

Every limit has a unit — `count`, `bytes`, `seconds`, `days`, `flag` or `modules` — and applies **per** one thing:
a quota per holder, such as an `org` or a `session`; a ceiling per operation or run, such as a `write`, a `query`, a
`node` or an `execution`.

## How a value is chosen

```mermaid theme={null}
flowchart LR
  D["Catalog default"] --> U["Owner's override"]
  U --> O["Organization's override"]
  O --> B["Blob's override"]
  B --> V["Value in force"]
```

* **Defaults first.** A limit's value is its default unless an override applies. A user, organization or blob with
  no overrides works on the defaults alone.
* **Overrides follow ownership.** An override on a user applies to every organization that user owns. An override on
  an organization applies to every blob in it. An override on a blob applies to that blob alone. The nearest
  override wins, limit by limit.
* **Ownership is read on every request.** Nothing is copied when a user, organization or blob is created, so an
  organization that changes owner follows its new owner's overrides from its next request.
* **Work is limited where it runs.** A session's limits, and the limits of every execution in it, come from the
  session's blob — not from the blob that holds the definition being run. `workflow.running_executions_per_org`
  counts against the organization of the session's blob. A schedule's fire runs in its target session, so the
  target's limits apply. See [Whose rules apply](/blob-types/workflow/workflows/workflow-reference#whose-rules-apply).
* **Each limit says where it can be overridden.** The catalog's **Set on** column lists the levels: `user`, `org`,
  `blob`. A blob type's `enabled` entitlement, for example, can be set on a user or an organization, never on a
  single blob.
* **Some limits have a most permissive value.** It comes from the platform itself — a cloud service quota, or a
  timeout BlobHub configures — and no override can go past it. For `scheduler.min_interval_seconds` less is more
  permissive, so an override can only raise it.
* **Fixed limits are published, not configurable.** A limit whose **Set on** is `fixed` is a bound of the platform,
  the same for every account. It is in the catalog so that it can be seen next to everything else.
* **A list is replaced, not merged.** An override of `workflow.code_imports` is the whole list for its level and
  everything below it.

## Holdings

A quota counts what its holder holds now. The count lives on the holder's own record and changes in the same step as
the thing it counts: a create the API refuses takes no slot, and the step that removes a thing frees its slot.

| Limit | Held by | Taken by | Freed by |
| :- | :- | :- | :- |
| `platform.orgs_per_user` | The user | [Create Organization](/rest-api/orgs/create-org) | [Delete Organization](/rest-api/orgs/delete-org) |
| `platform.blobs_per_org` | The organization | [Create Blob](/rest-api/orgs/create-blob) | [Delete Blob](/rest-api/blobs/delete-blob), when the blob's record is removed |
| `platform.revisions_per_blob` | The blob | [Create Revision](/rest-api/blobs/create-revision), and [Create Blob](/rest-api/orgs/create-blob) for the blob's first revision | [Delete Revision](/rest-api/revisions/delete-revision), when the revision's record is removed |
| `workflow.definitions_per_revision`, `workflow.playgrounds_per_revision` | The revision | [Create Definition](/blob-types/workflow/operations/create-definition), per category | [Delete Definition](/blob-types/workflow/operations/delete-definition) |
| `workflow.sessions_per_revision` | The revision | [Create Session](/blob-types/workflow/operations/create-session) | [Delete Session](/blob-types/workflow/operations/delete-session), as the session enters `deleting` |
| `workflow.executions_per_session` | The session | [Create Execution](/blob-types/workflow/operations/create-execution) | [Retention](#retention), as it removes the execution |
| `workflow.running_executions_per_org` | The organization of the session's blob | [Create Execution](/blob-types/workflow/operations/create-execution) | The execution's end, whatever ends it |
| `scheduler.schedules_per_revision` | The revision | [Create Schedule](/blob-types/scheduler/operations/create-schedule) | [Delete Schedule](/blob-types/scheduler/operations/delete-schedule) |

* **A `failed` blob or revision keeps its slot.** One whose creation ends in status `failed` holds its slot until it
  is deleted.
* **A blob's first revision takes a slot too.** It is counted when the blob is created, without being checked
  against the limit.
* **Deleting a holder deletes its counts.** Deleting a session, a revision, a blob or an organization needs no
  separate release: the counts go with the record.
* **Session objects are counted by listing them.** `workflow.session_objects_per_session` is checked only when a
  write would create a new alias, by counting the session's objects. Two writes that create new aliases at the same
  moment can take a session one past its limit.

Where to read a holding:

| Holding | Read it on |
| :- | :- |
| `platform.orgs_per_user` | [Get User Limits](/rest-api/users/get-limits), as `held` |
| `platform.blobs_per_org`, `workflow.running_executions_per_org` | [Get Organization Limits](/rest-api/orgs/get-limits), as `held` |
| `platform.revisions_per_blob` | [Get Blob Limits](/rest-api/blobs/get-limits), as `held` |
| A revision's definitions, playgrounds, sessions and schedules | [Get Revision](/rest-api/revisions/get-revision), in its `held` map |
| `workflow.executions_per_session` | [Get Session](/blob-types/workflow/operations/get-session), in its `held` map |

## Reading limits

Three routes return the limits in force, group by group, with each limit's default, where its value came from, and —
where the target being read is the holder — how much is held:

| Route | Returns |
| :- | :- |
| [`GET /users/:id/limits`](/rest-api/users/get-limits) | `platform` and every type, as a new organization of this user starts |
| [`GET /orgs/:id/limits`](/rest-api/orgs/get-limits) | `platform` without `platform.orgs_per_user`, and every type |
| [`GET /blobs/:org_id/:blob_id/limits`](/rest-api/blobs/get-limits) | `platform.revisions_per_blob` and the blob's own type |

The organization and blob routes need `read` access to their target. A user's limits are readable by that user, and
by anyone with `admin` standing on them, such as a service account's owner. A type an organization has not enabled
still appears, with its `<family>.enabled` entry `false`, so the organization can see what it would get.

## When a limit is reached

A request refused by a limit answers HTTP `400` with the error `limit_exceeded`, except where a refusal has a code
of its own: the blob-type gate's `invalid_blob_type` and the fixed limits on session writes and graph queries,
below. Each of these refusals carries a `limit` object naming the limit:

```json theme={null}
{
  "status": "failure",
  "error": "limit_exceeded",
  "message": "This session holds 1,000 executions, its limit (workflow.executions_per_session).",
  "limit": {"id": "workflow.executions_per_session", "value": 1000, "held": 1000}
}
```

| Field | Type | Description |
| :- | :- | :- |
| `limit.id` | string | The limit, as it appears in [the catalog](#the-catalog). |
| `limit.value` | integer, boolean or array | The value in force for this request. |
| `limit.held` | integer | Quotas only: how many the holder held when the request arrived. |

Key off `error` and `limit.id`. `message` is prose and may be reworded.

| Operation | Limits it checks |
| :- | :- |
| [Create Organization](/rest-api/orgs/create-org) | `platform.orgs_per_user` |
| [Create Blob](/rest-api/orgs/create-blob) | The type's `enabled` entitlement, `platform.blobs_per_org` |
| [Create Revision](/rest-api/blobs/create-revision) | `platform.revisions_per_blob` |
| [Upload Model](/blob-types/onnx/upload) | `onnx.model_bytes` |
| [Create Definition](/blob-types/workflow/operations/create-definition) | `workflow.definitions_per_revision` or `workflow.playgrounds_per_revision` |
| [Create Session](/blob-types/workflow/operations/create-session) | `workflow.sessions_per_revision` |
| [Create Execution](/blob-types/workflow/operations/create-execution), and every schedule fire | `workflow.executions_per_session`, `workflow.running_executions_per_org`, `workflow.execution_input_bytes` |
| [Upload Session Object](/blob-types/workflow/operations/upload-session-object) and [Upload Session File](/blob-types/workflow/operations/upload-session-file), and the same writes from `logic.code` | `workflow.session_objects_per_session`, when the write creates a new alias |
| [Create Schedule](/blob-types/scheduler/operations/create-schedule) | `scheduler.schedules_per_revision`, `scheduler.min_interval_seconds` |
| [Update Schedule](/blob-types/scheduler/operations/update-schedule) | `scheduler.min_interval_seconds` |
| A graph blob's query | `orientdb.query_results`, which refuses nothing: rows past it are dropped, and the response says `truncated: true` |

**A blob type answers with its own code.** Creating a blob of a type the organization has not enabled answers
`400 invalid_blob_type`, and the body carries `limit` with the type's entitlement, such as
`{"id": "orientdb.enabled", "value": false}`. A type string that names no blob type answers the same code without
`limit`.

**The fixed limits on session writes and graph queries answer with their own codes.** A request past one of them
is refused with HTTP `400` and the code below, and its body carries the same `limit` object — for
`graph_traversal_limit_exceeded`, naming the cap that tripped:

| Error | Limit |
| :- | :- |
| `session_object_too_large` | `workflow.session_object_bytes` |
| `session_file_too_large` | `workflow.session_file_bytes` |
| `thread_item_too_large` | `workflow.thread_item_bytes` |
| `too_many_files` | `workflow.thread_item_files` |
| `graph_element_too_large` | `workflow.graph_element_bytes` |
| `graph_mutation_too_large` | `workflow.graph_mutations_per_call` |
| `graph_traversal_limit_exceeded` | `workflow.graph_query_depth`, `workflow.graph_query_fanout`, `workflow.graph_query_results`, `workflow.graph_query_elements` |

Inside `logic.code`, a write refused by `workflow.session_objects_per_session` raises `LimitExceeded`, carrying the
`limit` object; see [Code Component](/blob-types/workflow/workflows/component-code#limits).

## Limits during a run

`create_execution` resolves five limits once, and the execution keeps those values until it ends:
`workflow.processor_runs_per_execution`, `workflow.execution_seconds`, `workflow.code_run_seconds`,
`workflow.code_imports` and `workflow.session_objects_per_session`. A change to any of them applies to executions
created afterwards.

| Limit | What happens past it |
| :- | :- |
| `workflow.processor_runs_per_execution` | The node run that would exceed it does not start. The execution fails with a [`flow.error`](/blob-types/workflow/workflows/execution-events#flow-events) event whose `attributes` carry the error `limit_exceeded` and the `limit` object. No `failure` route can recover it. |
| `workflow.execution_seconds` | Counted from the moment the execution starts running and checked before every node run; the first run past the deadline fails the execution the same way. A `logic.sleep` whose wait would end past the deadline fails it at once instead of waiting. |
| `workflow.sleep_seconds` | A `logic.sleep` that asks to wait longer than this fixed limit fails the execution the same way, without waiting. |
| `workflow.code_run_seconds` | The [`logic.code`](/blob-types/workflow/workflows/component-code#limits) run is stopped. The node takes its `failure` route, and the error message names the limit. |
| `workflow.session_objects_per_session` | A write from `logic.code` that would create a new session object is checked against the value the execution started with, and raises `LimitExceeded` past it. A write over the REST API is checked against the value in force when it arrives. |

A schedule fire that a limit refuses is recorded in the schedule's history with the status `refused`, carrying the
same `limit` object. A refusal does not disable the schedule: a recurring schedule stays active and keeps firing.
See [Execution History](/blob-types/scheduler/overview#execution-history).

## Retention

| What | Kept for, by default | Limit |
| :- | :- | :- |
| A finished execution, with its events and data | 90 days | `workflow.execution_retention_days` |
| A scheduler run, one entry of a schedule's history | 30 days | `scheduler.run_retention_days` |

* **Age counts from the end.** An execution is past retention once it ended longer ago than its limit, and a run
  once it fired longer ago than its limit.
* **A daily sweep removes what is older.** Anything past its retention is removed by the next sweep, so it can
  outlive its period by up to a day.
* **Only finished work.** A running execution, a session and a definition are never removed by retention. Deleting
  a session still removes everything in it at once.
* **Removing an execution frees its slot.** Each execution retention removes frees one slot in its session's
  `workflow.executions_per_session`, so a long-lived session — one a schedule fires into every minute, say — stays
  below its limit.
* **`null` keeps forever.** An override can set either retention to `null`.

## Changing a limit

No API changes a limit: the routes above only read. Values other than the defaults are set by BlobHub. To ask for a
different value, write to [support@blobhub.io](mailto:support@blobhub.io), naming the limit's id, the user,
organization or blob it should apply to, and the value. A `fixed` limit cannot be changed for one account, and no
value goes past a limit's most permissive one.

## The catalog

Every limit, by group. **Per** is what one value applies to. **Default** is the value when no override applies.
**Most permissive** is the furthest an override can go, `—` when the platform sets no bound. **Set on** lists where
an override can be set; `fixed` means nowhere.

### Global

| Id | Kind | Per | Default | Most permissive | Set on | Description |
| :- | :- | :- | :- | :- | :- | :- |
| `platform.orgs_per_user` | quota | user | 1 | — | user | Organizations the user owns. |
| `platform.blobs_per_org` | quota | org | 3 | — | user, org | Blobs in the organization. |
| `platform.revisions_per_blob` | quota | blob | 2 | — | user, org, blob | Revisions of the blob, counting a `failed` one until it is deleted. |

### Workflow

| Id | Kind | Per | Default | Most permissive | Set on | Description |
| :- | :- | :- | :- | :- | :- | :- |
| `workflow.enabled` | entitlement | org | `true` | — | user, org | Whether the organization may create workflow blobs. |
| `workflow.definitions_per_revision` | quota | revision | 10 | — | user, org, blob | `workflow` definitions on one revision. |
| `workflow.playgrounds_per_revision` | quota | revision | 10 | — | user, org, blob | `playground` definitions on one revision. |
| `workflow.sessions_per_revision` | quota | revision | 30 | — | user, org, blob | Sessions on one revision, not counting those being deleted. |
| `workflow.executions_per_session` | quota | session | 1,000 | — | user, org, blob | Executions one session holds. Retention frees a slot as it removes an execution. |
| `workflow.running_executions_per_org` | quota | org | 10 | — | user, org | Executions not yet finished, across every session in the organization's blobs. |
| `workflow.session_objects_per_session` | quota | session | 1,000 | 10,000 | user, org, blob | Objects in one session, checked when a write creates a new alias. |
| `workflow.processor_runs_per_execution` | ceiling | execution | 1,000 | 1,000 | user, org, blob | Node runs in one execution, every pass of a loop included. |
| `workflow.execution_seconds` | ceiling | execution | 86,400 | 86,400 | user, org, blob | Seconds from an execution's start to its end. |
| `workflow.code_run_seconds` | ceiling | node | 870 | 870 | user, org, blob | Wall-clock seconds of one `logic.code` run. |
| `workflow.code_imports` | entitlement | execution | `[]` | — | user, org, blob | Third-party modules `logic.code` may import. See [Code Component](/blob-types/workflow/workflows/component-code#limits). |
| `workflow.execution_retention_days` | retention | execution | 90 | — | user, org, blob | Days a finished execution is kept after it ends, with its events and data. |
| `workflow.execution_input_bytes` | ceiling | execution | 65,536 | — | fixed | Size of `create_execution`'s `input_data`. |
| `workflow.session_object_bytes` | ceiling | write | 5,242,880 | — | fixed | Size of one session object, serialized. |
| `workflow.session_file_bytes` | ceiling | write | 4,194,304 | — | fixed | Size of one session file. |
| `workflow.thread_item_bytes` | ceiling | write | 65,536 | — | fixed | Size of one thread item, serialized. |
| `workflow.thread_item_files` | ceiling | write | 10 | — | fixed | `file` blocks in one thread item. |
| `workflow.graph_element_bytes` | ceiling | write | 65,536 | — | fixed | Size of one graph element, serialized. |
| `workflow.graph_mutations_per_call` | ceiling | call | 40 | — | fixed | Operations in one graph mutation call. |
| `workflow.graph_query_depth` | ceiling | query | 10 | — | fixed | Steps in one graph query. |
| `workflow.graph_query_fanout` | ceiling | query | 1,000 | — | fixed | Elements one adjacency hop of a graph query may expand. |
| `workflow.graph_query_results` | ceiling | query | 1,000 | — | fixed | Size of a graph query's working set and result. |
| `workflow.graph_query_elements` | ceiling | query | 5,000 | — | fixed | Elements one graph query may fetch across all its steps. |
| `workflow.sleep_seconds` | ceiling | node | 43,200 | — | fixed | Longest wait of one `logic.sleep` node. |

### Scheduler

| Id | Kind | Per | Default | Most permissive | Set on | Description |
| :- | :- | :- | :- | :- | :- | :- |
| `scheduler.enabled` | entitlement | org | `true` | — | user, org | Whether the organization may create scheduler blobs. |
| `scheduler.schedules_per_revision` | quota | revision | 10 | — | user, org, blob | Schedules on the revision. A scheduler blob has one revision, so this is per blob. |
| `scheduler.min_interval_seconds` | ceiling | schedule | 60 | 60 | user, org, blob | Shortest gap between two consecutive fires of a cron expression. Less is more permissive, so an override can only raise it. |
| `scheduler.run_retention_days` | retention | run | 30 | — | user, org, blob | Days one run of a schedule is kept in its history after it fires. |

### ONNX Model

| Id | Kind | Per | Default | Most permissive | Set on | Description |
| :- | :- | :- | :- | :- | :- | :- |
| `onnx.enabled` | entitlement | org | `true` | — | user, org | Whether the organization may create ONNX blobs. |
| `onnx.model_bytes` | ceiling | upload | 524,288,000 | — | user, org, blob | Declared size of one model upload. |

### Graph

| Id | Kind | Per | Default | Most permissive | Set on | Description |
| :- | :- | :- | :- | :- | :- | :- |
| `orientdb.enabled` | entitlement | org | `false` | — | user, org | Whether the organization may create graph blobs. |
| `orientdb.query_results` | ceiling | query | 50 | — | fixed | Rows one graph query returns. Rows past it are dropped, and the response says `truncated: true`. |

### NLP Dataset

| Id | Kind | Per | Default | Most permissive | Set on | Description |
| :- | :- | :- | :- | :- | :- | :- |
| `nlp.enabled` | entitlement | org | `false` | — | user, org | Whether the organization may create NLP dataset blobs. |

An `enabled` entitlement gates creation only. An organization whose entitlement for a type is `false` keeps every
blob of that type it already has.


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