Skip to main content

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 for one business customer that refuses to give you per-user IDs, and a job ID 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.

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). 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).
  • 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.

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.
  • 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 [email protected].
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).

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). 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 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 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), 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).
  • 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.
Last modified on November 10, 2026