> ## 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.

# Conventions

> Shared patterns across the BlobHub REST API

## Response Envelope

Every REST response carries a top-level `status`, whatever else it carries. On success it is `success`, and
the endpoint's own fields sit beside it:

```json theme={null}
{
  "status": "success",
  "org": { "id": "org_123456" }
}
```

An endpoint with nothing to report answers with the status alone — `{"status": "success"}` — which is why no
endpoint on this API ever returns a bare `{}`. Each endpoint page documents only its own fields; `status` is
always there and is not repeated in those tables.

Failures use the same envelope with two more keys:

```json theme={null}
{
  "status": "failure",
  "error": "forbidden",
  "message": "Request forbidden -- authorization will not help"
}
```

`error` is a stable machine-readable code — key off it, not off `message`, which is prose and may be
reworded. Each page lists the codes its endpoint can return.

One endpoint sets `status` itself rather than inheriting it:
[Check Definition](/blob-types/workflow/operations/check-definition) answers `200` with
`"status": "failure"` when a workflow fails validation, because the verdict is the payload. The HTTP status
still describes the request, not the verdict.

<Note>
  Two surfaces are deliberately outside this envelope. The [WebSocket API](/web-socket/protocol/overview) is
  [JSON-RPC 2.0](https://www.jsonrpc.org/specification) and answers with `result` or `error`. Binary transfers
  — [ONNX model download](/blob-types/onnx/download) — put the file in the body and move the envelope into the
  `X-Response-Body` header.
</Note>

## Pagination

Nothing on this API paginates through the query string. A cursor is a field in the JSON request body, or the
listing has none at all.

There are four shapes, and **which one you are in is a property of the endpoint, not of the response** — the
last two are indistinguishable from the payload. The lists below are the only way to tell them apart.

| Shape         | Recognized by                            | Behaviour                                                      |
| :------------ | :--------------------------------------- | :------------------------------------------------------------- |
| Opaque cursor | a `cursor` field in the response         | Echo it back as `cursor`. `null` means exhausted.              |
| Record cursor | a `last_<noun>_id` field in the response | Send it back as `start_<noun>_id`. `null` means exhausted.     |
| Complete      | no pagination field at all               | Pages internally and returns everything.                       |
| Unpaginated   | no pagination field at all               | Complete only if it fitted in one page. See the warning below. |

### Two rules wherever a cursor exists

**Omit the cursor on the first call — never send `null`.** Cursors are typed as strings, so `"cursor": null` is
rejected with `invalid_request_body`. Leaving the key out is what asks for the first page.

**A short page does not mean you are finished, and neither does an empty one.** `limit` bounds the rows *read*,
not the rows *returned*: access filtering runs after the page is fetched, so a page can come back short — or
completely empty — with more results waiting behind it. **Only a `null` cursor means exhausted.** Loop on the
cursor, never on the page size.

### Opaque cursor

Send `cursor` and `limit`. The cursor is opaque — it wraps different underlying tokens depending on the store
behind the listing — so pass it back byte for byte; a modified one is rejected with `invalid_cursor`.

Used by the workflow session listings: `list_session_objects`, `list_session_thread_items`,
`list_session_graph_elements`, `list_session_graph_neighbors`, `list_session_events` and `list_execution_events`.
Each command's own page documents its default and maximum `limit`; a value above the maximum is an error, not
silently reduced.

### Record cursor

**The request and response names differ.** Read `last_session_id` from the response and send it back as
`start_session_id`. `limit` is not accepted — the page size is fixed at 10 — and supplying it fails the request.
Results are newest-first and the order cannot be changed.

Used by `list_sessions` and `list_executions` on the workflow engine, and `list_schedules` and `list_executions`
on the scheduler engine.

Unlike an opaque cursor this token is a real record id, which the server re-reads to resume from. If that record
has been deleted between two pages, resumption fails as a server error rather than a client one.

### Complete listings

These page internally, so no cursor genuinely means the whole set:

* `GET /api-keys/target/:target/:target_id`
* `GET /credentials/target/:target/:target_id`
* `GET /users/target/:target/:target_id`
* the `query_session_graph` command, which is bounded by explicit caps and refuses with
  `graph_traversal_limit_exceeded` rather than returning a partial graph

### Unpaginated listings

<Warning>
  These accept no cursor and return a single page of roughly 1 MB. Beyond that the response is **silently
  incomplete** — no cursor, no count, no flag, nothing distinguishing a truncated listing from a complete one.
</Warning>

* the `list_definitions` command
* `GET /orgs/:id/blobs`
* `GET /blobs/:org_id/:blob_id/revisions`
* `GET /users/:id/orgs`
* `GET /members/target/:target/:target_id`
* `GET /blobs/:org_id/:blob_id/metadata/:alias`
* `GET /revisions/:id/metadata/:alias`

Where one of these also filters — `list_definitions` by category, blobs and organizations by visibility — the
filter is applied *after* the page budget is spent. Such a listing can come back **empty while matching records
exist** past the boundary, so an empty result is not proof of absence.

## Identifiers

Most API resources support two types of identifiers in path parameters:

| Type  | Format                           | Example        |
| :---- | :------------------------------- | :------------- |
| UUID  | System-generated unique ID       | `usr_a1b2c3d4` |
| Alias | User-chosen globally unique name | `janedoe`      |

Either form can be used wherever a path parameter accepts a resource ID (e.g. `{user_id}`,
`{org_id}`).

## The "me" Alias

User endpoints accept the special value `me` as the `{user_id}` path parameter. It resolves
to the authenticated user's ID before any authorization checks are applied.

```bash theme={null}
# These are equivalent when authenticated as usr_a1b2c3d4
GET /v1/users/me
GET /v1/users/usr_a1b2c3d4
```

The `me` alias works with both bearer token and API key authentication.

## Permissions

Access to user resources depends on whether the caller is the target user (self) or a
different user (other).

| Endpoint                      | Self                                 | Other                               |
| :---------------------------- | :----------------------------------- | :---------------------------------- |
| `GET /users/{user_id}`        | Full record (`role` is `owner`)      | Same full record (`role` is `read`) |
| `PATCH /users/{user_id}`      | Allowed                              | Forbidden (403)                     |
| `GET /users/{user_id}/orgs`   | All organizations (public + private) | Public organizations only           |
| `GET /users/{user_id}/limits` | Allowed                              | Forbidden (403)                     |

See each endpoint's documentation for details.
