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

# End-user ID reference

> Where to send end_user_id on each endpoint, its format, and the refusal codes it can produce.

## What `end_user_id` is

`end_user_id` is a required, top-level field that tells each::labs which of your end users a request is made for. Send it on every request that runs a model or workflow. It lets each::labs act on that one end user instead of your whole organization.

Send a **stable, pseudonymous ID for one human end user**: your internal user ID or a hash of it, never a new random ID per request. Use the same value every time you act for that person, and a different value for every other person. There are exactly two exceptions: a [shared ID](https://docs.eachlabs.ai/end-user-id.md#shared-ids) for one business customer that refuses to give you per-user IDs, and a [job ID](https://docs.eachlabs.ai/end-user-id.md#automated-jobs) for an automated job with no person behind it. Never put an email address, a name, a phone number or an IP address in it, hashed or not. Code samples use `usr_4f9a2c` to stand for your real user's ID; never hard-code it.

How to choose the value for your business and what happens when it is wrong: [End-user ID requirements](https://docs.eachlabs.ai/end-user-id.md).

## Where to send it

Send `end_user_id` on every request that runs a model or a workflow, including automated jobs. In a JSON body it is always top-level, never inside `input`, `messages` or another request object. Transcriptions take it as a form field (see the table).

| Request | Field | Location |
| - | - | - |
| `POST /v1/prediction`, `POST /v1/prediction/` | `end_user_id` | Top-level JSON body, alongside `model` and `input` |
| `POST /v1/chat/completions` (buffered and streaming) | `end_user_id` | Top-level JSON body |
| `POST /v1/responses` (buffered and streaming) | `end_user_id` | Top-level JSON body |
| `POST /v1/decisions` | `end_user_id` | Top-level JSON body. The value is checked, but not yet stored on this call |
| `POST /v1/audio/speech` | `end_user_id` | Top-level JSON body |
| `POST /v1/audio/transcriptions` | `end_user_id` | Multipart form field |
| Workflow triggers, bulk triggers, and public workflow triggers | `end_user_id` | Top-level JSON body; on a bulk trigger it applies to every execution in the batch, so batch only the requests of one person or one job together. The engine copies it onto every step |

each::sense (`eachsense-agent.core.eachlabs.run`) does not accept `end_user_id` yet. Do not send it there.

OpenAI's `user` and `safety_identifier` fields are never read as `end_user_id`, and `POST /v1/responses` rejects both. Send `end_user_id` itself.

## Format and limits

* 1 to 256 characters, counted after leading and trailing whitespace is trimmed. Keep the whole value, including any prefix of your own, to 240 characters or fewer: that is the safe maximum.
* Valid UTF-8, with no control characters such as tabs, line breaks or null bytes. Spaces and non-ASCII letters are allowed.
* JSON `null`, an empty string or a whitespace-only string counts as missing, not as invalid. Sending any of these is the same as omitting the field. Never do it on purpose.
* The string `"null"` passes this check, but it is a placeholder: never send it (see [Never send](https://docs.eachlabs.ai/end-user-id.md#never-send)).
* Any other non-string value, or an invalid string, is refused with `400` and the message `end_user_id must be 1-256 printable characters`. On `POST /v1/prediction`, `details` is the message string itself for this error, not an object with a `code`. On workflow triggers the body is `{"error": "end_user_id must be 1-256 printable characters"}`. On `POST /v1/chat/completions` and the audio endpoints it is `error.message`. On `POST /v1/responses` and `POST /v1/decisions`, `error.message` is `The request is invalid.` On all of them `error.code` is `invalid_request_error`.

The field is required. See [Missing `end_user_id`](#missing-end_user_id).

```json theme={"dark"}
{
  "model": "flux-1-1-pro",
  "version": "1.0.0",
  "input": {
    "prompt": "A beautiful sunset over the ocean"
  },
  "end_user_id": "usr_4f9a2c"
}
```

## When an end user or an organization is blocked

* `END_USER_BLOCKED` (`403`) refuses every request that would create an execution (run a model or a workflow step) for that end user. Other end users in the same organization are unaffected.
* An end-user block matches only requests that carry that `end_user_id`. Sending a blocked user's requests without the field, or under another ID, is an attempt to get around our child-safety checks: see [What happens when it goes wrong](https://docs.eachlabs.ai/end-user-id.md#what-happens-when-it-goes-wrong).
* `ORGANIZATION_BLOCKED` (`403`) refuses every request that would create an execution in the organization, with or without an `end_user_id`. An organization block takes precedence over an end-user block.
* A refused request does not run and is not billed.
* Both blocked codes return the same fixed message: `This request is not allowed under the each::labs usage policy.` It never identifies the end user, the organization, or the reason.
* When the platform recorded the refused execution, the response includes its `execution_id`.
* A block applies to new requests only. An audio stream that is already open is not cut when a block lands.
* A blocked request is terminal: retrying it does not clear the block. If you believe we blocked the wrong end user or organization, write to [safety@eachlabs.ai](mailto:safety@eachlabs.ai).

When we suspend an organization, every request from it that would run a model or a workflow is refused with `403 ORGANIZATION_BLOCKED`.

## Workflows

The workflow engine stores `end_user_id` on the run and copies it onto every step's prediction, so a refusal on any step refers to the same end user.

A workflow trigger, bulk trigger or public workflow trigger creates a workflow execution, but no model execution. It is never refused for a block or a missing `end_user_id`: it returns its normal `202` response with the workflow `execution_id`, and the check runs when each step creates its model execution:

* The refused step fails. Every attempt in that run carries the same `end_user_id`, so a retry or fallback of the step is checked the same way.
* The workflow execution does not carry the refusal code. The failed step in `step_outputs`, and the failed workflow execution, carry an `error_cause` that names the class of refusal, not its exact cause:
  * `workflow prediction RPC PermissionDenied`: the step was not allowed, for example because the end user or the organization is blocked. Treat it as final.
  * `workflow prediction RPC InvalidArgument`: the step's request was refused as invalid, for example because `end_user_id` is missing for an organization that requires it, or because a content-safety check blocked it. Check that the trigger carried a valid `end_user_id` and that the workflow's models are current; if both are fine, treat it as final.
  * `workflow prediction RPC Unavailable`: a service, such as the safety controls, was temporarily unavailable. Trigger the workflow again later.

An invalid `end_user_id` is different: the trigger itself returns `400` (see [Format and limits](#format-and-limits)).

## When safety controls are unavailable

`SAFETY_BLOCKS_UNAVAILABLE` (`503`) and `SAFETY_PREFLIGHT_UNAVAILABLE` (`503`) mean the safety controls could not answer. The request was not executed. Retry with exponential backoff; these are temporary outages, not blocks.

## Missing `end_user_id`

Always send it, and treat a missing `end_user_id` as a bug in your code. Requests without it are not refused today, but any of them can be refused at any time with `400 END_USER_ID_REQUIRED`: enforcement is switched on per organization, without notice. A workflow trigger is still accepted, and the step fails instead (see [Workflows](#workflows)).

When the refusal is returned, the message is `end_user_id is required for this organization.`

Never send a placeholder such as `anonymous`, `guest`, `unknown`, `null`, `none`, `0`, `user`, `default` or `test`: it identifies no one, and the [End-user ID requirements](https://docs.eachlabs.ai/end-user-id.md) treat it like a random ID.

Add `end_user_id` to every endpoint that creates an execution, not only the main prediction call: prediction requests, workflow triggers, the OpenAI-compatible endpoints and Decisions.

## Content safety refusals

Content safety checks can refuse a request before any model runs. A blocked request returns `422 SAFETY_PREFLIGHT_BLOCKED`, usually with the message `This request was blocked by Eachlabs content safety checks.` A temporary outage returns `503 SAFETY_PREFLIGHT_UNAVAILABLE` with `Eachlabs content safety checks are temporarily unavailable. Please retry.`

See the [Error Reference](https://docs.eachlabs.ai/errors.md#safety-refusals) for the response envelopes on each endpoint.

## Handling a refusal

The codes on this page are part of the API contract now. The checks behind them are switched on without notice, so handle them today.

Branch on the code, never on the message text. On prediction creation, read `details.code`; when `details` is a plain string (the invalid-value `400` in [Format and limits](#format-and-limits)), there is no code: fix the value. On `POST /v1/decisions` and the OpenAI-compatible endpoints, read `error.code`. In a workflow, read the failed step's `error_cause` (see [Workflows](#workflows)).

```json theme={"dark"}
{
  "status": 403,
  "error": "forbidden",
  "details": {
    "code": "END_USER_BLOCKED",
    "message": "This request is not allowed under the each::labs usage policy.",
    "execution_id": "e2dba2bb-bc1d-4651-b6bf-fbbbebdee104"
  }
}
```

* `END_USER_ID_REQUIRED`: add `end_user_id` and resend the request.
* `END_USER_BLOCKED`, `ORGANIZATION_BLOCKED`, `SAFETY_PREFLIGHT_BLOCKED`: do not retry.
* `SAFETY_BLOCKS_UNAVAILABLE`, `SAFETY_PREFLIGHT_UNAVAILABLE`: retry with backoff.


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