# splitch error codes

Every failure code the API, SDK, and CLI can emit. Each one also has its own page at https://splitch.dev/docs/error/{code}.md carrying its cause, its fix, and the action that clears it: that is the URL printed in the error message itself.

## API (80)

Returned by the splitch API, with an HTTP status.

- [ACTIVATION_NOT_AVAILABLE](https://splitch.dev/docs/error/ACTIVATION_NOT_AVAILABLE.md): 409. The Metric Event cannot activate a matching Experiment Run in its current state.
- [ACTIVATION_TIMESTAMP_INVALID](https://splitch.dev/docs/error/ACTIVATION_TIMESTAMP_INVALID.md): 400. An activation timestamp was placed at or before the first Exposure it would filter, which would silently drop the Entities the Run already measured.
- [ALLOCATION_INVALID](https://splitch.dev/docs/error/ALLOCATION_INVALID.md): 400. The Variant allocation percentages on a Run do not sum to 100.
- [APPROVAL_APPLICATION_FAILED](https://splitch.dev/docs/error/APPROVAL_APPLICATION_FAILED.md): 409. The Review was authorized but applying the change did not complete. The request remains pending; inspect `details.applicationError` before retrying with a new Review idempotency key.
- [APPROVAL_REQUEST_NOT_FOUND](https://splitch.dev/docs/error/APPROVAL_REQUEST_NOT_FOUND.md): 404. No Approval Request matched the identifier in the request, within the scope your credential can reach.
- [APPROVAL_REQUEST_RESOLVED](https://splitch.dev/docs/error/APPROVAL_REQUEST_RESOLVED.md): 409. A different Review already resolved this request.
- [APPROVAL_REQUEST_STALE](https://splitch.dev/docs/error/APPROVAL_REQUEST_STALE.md): 409. The target changed after the request was proposed, so approving it would apply a diff computed against state that no longer exists. The request is terminal.
- [APPROVAL_REVIEW_FORBIDDEN](https://splitch.dev/docs/error/APPROVAL_REVIEW_FORBIDDEN.md): 403. This principal may not perform this Review.
- [APPROVAL_REVIEW_REQUIRED](https://splitch.dev/docs/error/APPROVAL_REVIEW_REQUIRED.md): 409. The Environment Policy gates this change at `confirm`, and the call carried no inline `review`. A durable Approval Request now exists and is pending.
- [APP_MISMATCH](https://splitch.dev/docs/error/APP_MISMATCH.md): 403. The credential belongs to a different App than the one the request addressed.
- [APP_NOT_FOUND](https://splitch.dev/docs/error/APP_NOT_FOUND.md): 404. No App matched the identifier in the request, within the scope your credential can reach.
- [ATTENTION_FANOUT_LIMIT_EXCEEDED](https://splitch.dev/docs/error/ATTENTION_FANOUT_LIMIT_EXCEEDED.md): 409. The App-wide attention rollup spans more Environments and running Experiments than it will read in one pass. It issues one Analysis read per running Experiment per Environment, and past the budget it refuses the whole read.
- [CLOUDFLARE_INSTALLATION_NOT_FOUND](https://splitch.dev/docs/error/CLOUDFLARE_INSTALLATION_NOT_FOUND.md): 404. No Cloudflare installation matched the identifier in the request, within the scope your credential can reach.
- [CONTEXT_USE_INVALID](https://splitch.dev/docs/error/CONTEXT_USE_INVALID.md): 400. context_use was called without a session, or with empty or unusable App and Environment arguments.
- [CONVEX_INSTALLATION_NOT_FOUND](https://splitch.dev/docs/error/CONVEX_INSTALLATION_NOT_FOUND.md): 404. No Convex installation matched the identifier in the request, within the scope your credential can reach.
- [CREDENTIAL_NOT_FOUND](https://splitch.dev/docs/error/CREDENTIAL_NOT_FOUND.md): 404. No credential matched the identifier in the request, within the scope your credential can reach.
- [CREDENTIAL_REVOKED](https://splitch.dev/docs/error/CREDENTIAL_REVOKED.md): 403. The credential is well-formed and known, but has been revoked.
- [DECISION_BLOCKED](https://splitch.dev/docs/error/DECISION_BLOCKED.md): 409. One or more checks in the shipped Experiment decision gate failed, so the Run cannot be concluded.
- [DECISION_LOCKED](https://splitch.dev/docs/error/DECISION_LOCKED.md): 409. A decision-family or alpha setting was edited on a running Run. These fix the statistical test, and moving them mid-Run invalidates the result.
- [DECISION_RESULT_STALE](https://splitch.dev/docs/error/DECISION_RESULT_STALE.md): 409. The selected Run's server-owned result at the submitted data watermark does not match the result token the caller observed.
- [DECISION_RESULT_UNAVAILABLE](https://splitch.dev/docs/error/DECISION_RESULT_UNAVAILABLE.md): 409. The selected Run has no decision-bearing result evidence: the envelope is not ready, or its ready response lacks the conclusion evidence pair.
- [ENTITY_TYPE_MISMATCH](https://splitch.dev/docs/error/ENTITY_TYPE_MISMATCH.md): 400. The Metric Event's Entity type did not match the Entity type declared by its published Event Definition Version.
- [ENVIRONMENT_EXPIRED](https://splitch.dev/docs/error/ENVIRONMENT_EXPIRED.md): 410. The Temporary Environment reached its expiry and is read-only until teardown.
- [ENVIRONMENT_KIND_UNSUPPORTED](https://splitch.dev/docs/error/ENVIRONMENT_KIND_UNSUPPORTED.md): 409. The selected Environment kind does not support the requested operation.
- [ENVIRONMENT_NOT_FOUND](https://splitch.dev/docs/error/ENVIRONMENT_NOT_FOUND.md): 404. No Environment in the already-authorized App matched an Environment selector supplied as an App-level read filter.
- [ENVIRONMENT_SYNC_INCOMPLETE](https://splitch.dev/docs/error/ENVIRONMENT_SYNC_INCOMPLETE.md): 503. Some Flag Configurations could not be copied from the source Environment.
- [EVENT_DEFINITION_IMMUTABLE](https://splitch.dev/docs/error/EVENT_DEFINITION_IMMUTABLE.md): 409. The request attempted to change a published Event Definition Version, whose contract is immutable so accepted Metric Events remain traceable.
- [EVENT_DEFINITION_NOT_FOUND](https://splitch.dev/docs/error/EVENT_DEFINITION_NOT_FOUND.md): 404. No Event Definition in this App matched the requested Metric Event name or Event Definition reference.
- [EVENT_DEFINITION_UNPUBLISHED](https://splitch.dev/docs/error/EVENT_DEFINITION_UNPUBLISHED.md): 409. The Event Definition exists, but it has no published Version that can validate and stamp the Metric Event.
- [EVENT_DEFINITION_VERSION_NOT_FOUND](https://splitch.dev/docs/error/EVENT_DEFINITION_VERSION_NOT_FOUND.md): 404. The requested Event Definition Version does not exist under this Event Definition.
- [EVENT_ID_CONFLICT](https://splitch.dev/docs/error/EVENT_ID_CONFLICT.md): 409. The same `exposureId` (or other client-owned event id) was reused with a different payload than the one already accepted.
- [EVENT_SCHEMA_MISMATCH](https://splitch.dev/docs/error/EVENT_SCHEMA_MISMATCH.md): 400. The Metric Event did not match the published Event Definition Version: a field or Dimension was unknown, missing, or the wrong type, or a JSON value violated its declared schema.
- [EXPERIMENT_KEY_CONFLICT](https://splitch.dev/docs/error/EXPERIMENT_KEY_CONFLICT.md): 409. An Experiment already holds this `(App, Environment, key)`. Archiving an Experiment does not free its key, so the holder may be archived rather than live.
- [EXPERIMENT_NOT_FOUND](https://splitch.dev/docs/error/EXPERIMENT_NOT_FOUND.md): 404. No Experiment matched the identifier in the request, within the scope your credential can reach.
- [EXPERIMENT_NO_DRAFT](https://splitch.dev/docs/error/EXPERIMENT_NO_DRAFT.md): 409. Start was called but the draft is identical to the Run already live. Starting it would open a second Run that measures the same configuration, splitting one cohort across two Runs for no reason.
- [EXPERIMENT_RUNNING](https://splitch.dev/docs/error/EXPERIMENT_RUNNING.md): 409. The operation is blocked while the Experiment has a live Run. Delete is the common one.
- [EXPOSURE_TICKET_EXPIRED](https://splitch.dev/docs/error/EXPOSURE_TICKET_EXPIRED.md): 410. The Exposure Ticket's `issued_at` is older than the 24-hour redemption TTL.
- [EXPOSURE_TICKET_INVALID](https://splitch.dev/docs/error/EXPOSURE_TICKET_INVALID.md): 400. An Exposure Ticket could not be verified: the MAC failed, the payload was tampered, or the ticket's App/Environment does not match the calling credential.
- [FLAG_LIFECYCLE_INCOMPLETE](https://splitch.dev/docs/error/FLAG_LIFECYCLE_INCOMPLETE.md): 400. A flags_update patch explicitly cleared the owner or expiry of a release or experiment Flag. Those classes are temporary by definition, so the platform refuses to keep one that nobody is due to remove. Omitted values are never refused: creates and reclassifications fill them with defaults.
- [FLAG_NOT_FOUND](https://splitch.dev/docs/error/FLAG_NOT_FOUND.md): 404. No Flag matched the identifier in the request, within the scope your credential can reach.
- [FORBIDDEN](https://splitch.dev/docs/error/FORBIDDEN.md): 403. The principal is authenticated but is not authorized for this resource under its Organization role.
- [IDEMPOTENCY_KEY_CONFLICT](https://splitch.dev/docs/error/IDEMPOTENCY_KEY_CONFLICT.md): 409. The same idempotency key was reused with a different canonical payload. Honoring it would let one key stand for two different changes.
- [INSUFFICIENT_SCOPES](https://splitch.dev/docs/error/INSUFFICIENT_SCOPES.md): 403. The credential is valid but does not carry the scopes this operation requires.
- [INTERNAL_SERVER_ERROR](https://splitch.dev/docs/error/INTERNAL_SERVER_ERROR.md): 500. An unhandled fault on the platform side. Nothing about the request was at fault.
- [INVALID_PAGINATION](https://splitch.dev/docs/error/INVALID_PAGINATION.md): 400. The `cursor` or `limit` on a list request could not be used.
- [INVALID_SORT](https://splitch.dev/docs/error/INVALID_SORT.md): 400. The requested sort field is not sortable on this collection.
- [LAST_ENVIRONMENT_REQUIRED](https://splitch.dev/docs/error/LAST_ENVIRONMENT_REQUIRED.md): 409. The delete would leave an App with no Environment, and an App cannot serve without one.
- [LAST_OWNER_REQUIRED](https://splitch.dev/docs/error/LAST_OWNER_REQUIRED.md): 409. The change would leave a shared Organization with no owner, which would strand every member without an escalation path.
- [MEMBERSHIP_CONFLICT](https://splitch.dev/docs/error/MEMBERSHIP_CONFLICT.md): 409. The User is already a member of the Organization.
- [METRIC_NOT_FOUND](https://splitch.dev/docs/error/METRIC_NOT_FOUND.md): 404. No Metric matched the identifier in the request, within the scope your credential can reach.
- [MULTIPLE_VARIANT_CONFLICT](https://splitch.dev/docs/error/MULTIPLE_VARIANT_CONFLICT.md): 409. One Entity was observed under more than one Variant in the same Run, so it is bucketed to `__multiple__` and its contribution is untrusted.
- [ORGANIZATION_NOT_FOUND](https://splitch.dev/docs/error/ORGANIZATION_NOT_FOUND.md): 404. No Organization matched the identifier in the request, within the scope your credential can reach.
- [ORIGIN_NOT_ALLOWED](https://splitch.dev/docs/error/ORIGIN_NOT_ALLOWED.md): 403. A valid Client Key was presented from an origin that is not on that key's allow-list.
- [PRIVACY_CONFIRMATION_REQUIRED](https://splitch.dev/docs/error/PRIVACY_CONFIRMATION_REQUIRED.md): 409. A destructive privacy job was submitted without confirmation. These erase subject data irreversibly across every store.
- [PRIVACY_JOB_FAILED](https://splitch.dev/docs/error/PRIVACY_JOB_FAILED.md): 500. A privacy job did not complete against every store, so subject data may remain in the ones it could not reach.
- [PRIVACY_JOB_NOT_FOUND](https://splitch.dev/docs/error/PRIVACY_JOB_NOT_FOUND.md): 404. No privacy job matched the identifier in the request, within the scope your credential can reach.
- [QUOTA_EXCEEDED](https://splitch.dev/docs/error/QUOTA_EXCEEDED.md): 409. The User already owns the maximum number of non-provisional Organizations allowed by the ownership ceiling.
- [RATE_LIMITED](https://splitch.dev/docs/error/RATE_LIMITED.md): 429. The caller exceeded the rate budget for this surface.
- [RESOURCE_NOT_EMPTY](https://splitch.dev/docs/error/RESOURCE_NOT_EMPTY.md): 409. A destructive delete was refused because child resources still exist under the target and this delete does not cascade.
- [RUN_FROZEN](https://splitch.dev/docs/error/RUN_FROZEN.md): 409. The edit touches a field that a running Run freezes. Changing it mid-flight would rebucket Entities that were already measured, so the write is refused whole.
- [RUN_NOT_FOUND](https://splitch.dev/docs/error/RUN_NOT_FOUND.md): 404. No Run matched the identifier in the request, within the scope your credential can reach.
- [RUN_NOT_RUNNING](https://splitch.dev/docs/error/RUN_NOT_RUNNING.md): 409. An operation that only a live Run supports (End, for example) was called on a Run that is still a draft or already ended.
- [SCOPE_UNRESOLVED](https://splitch.dev/docs/error/SCOPE_UNRESOLVED.md): 400. The tool needs an App or Environment and neither the call nor the session supplied one.
- [SEGMENT_NOT_FOUND](https://splitch.dev/docs/error/SEGMENT_NOT_FOUND.md): 404. No Segment matched the identifier in the request, within the scope your credential can reach.
- [SELECTOR_AMBIGUOUS](https://splitch.dev/docs/error/SELECTOR_AMBIGUOUS.md): 409. The App slug or Environment key matches more than one resource within the caller's reachable scope.
- [SENTRY_INSTALLATION_CONFLICT](https://splitch.dev/docs/error/SENTRY_INSTALLATION_CONFLICT.md): 409. The Environment already publishes its Flag changes to a Sentry organization. Sentry's change-tracking payload carries no environment, so a second installation would interleave two Environments' toggles into one audit log.
- [SENTRY_INSTALLATION_NOT_FOUND](https://splitch.dev/docs/error/SENTRY_INSTALLATION_NOT_FOUND.md): 404. No Sentry installation matched the identifier in the request, within the scope your credential can reach.
- [SERVICE_UNAVAILABLE](https://splitch.dev/docs/error/SERVICE_UNAVAILABLE.md): 503. The edge returned HTTP 503: Provider configuration could not be resolved. This is retryable and does not mean your Flag is misconfigured.
- [SLUG_CONFLICT](https://splitch.dev/docs/error/SLUG_CONFLICT.md): 409. The Organization slug is already taken. Slugs are a global handle, so the winner may be an Organization you cannot see.
- [SYNC_LIMIT_EXCEEDED](https://splitch.dev/docs/error/SYNC_LIMIT_EXCEEDED.md): 409. The App exceeds the Flag count supported by Temporary Environment create and Sync.
- [TARGETING_KEY_MISMATCH](https://splitch.dev/docs/error/TARGETING_KEY_MISMATCH.md): 409. The Targeting Key changed on a Run that is already assigning traffic. Every Entity would rebucket, and the Exposures already recorded would describe a different population than the ones that follow.
- [TARGET_CONFIGURATION_STALE](https://splitch.dev/docs/error/TARGET_CONFIGURATION_STALE.md): 409. The target Flag Configuration changed after the caller read the version used for the conclusion proposal.
- [TEMPORARY_ENVIRONMENT_LIMIT_EXCEEDED](https://splitch.dev/docs/error/TEMPORARY_ENVIRONMENT_LIMIT_EXCEEDED.md): 409. The App has reached its limit of unexpired Temporary Environments.
- [UNAUTHORIZED](https://splitch.dev/docs/error/UNAUTHORIZED.md): 401. No valid credential was presented, or the one presented could not be parsed.
- [UNSUPPORTED_MEDIA_TYPE](https://splitch.dev/docs/error/UNSUPPORTED_MEDIA_TYPE.md): 415. A mutating API request carried a body without the application/json media type.
- [UNSUPPORTED_OBJECT_KEY](https://splitch.dev/docs/error/UNSUPPORTED_OBJECT_KEY.md): 400. A JSON own `"__proto__"` key reached a Zod record that would otherwise silently drop it (Precomputed Evaluations Flag Keys, or a shape that must keep every own key).
- [USER_NOT_FOUND](https://splitch.dev/docs/error/USER_NOT_FOUND.md): 404. No user matched the identifier in the request, within the scope your credential can reach.
- [VALIDATION_ERROR](https://splitch.dev/docs/error/VALIDATION_ERROR.md): 400. The request body, path, or query failed contract validation at the Worker boundary.
- [VARIANT_NOT_AVAILABLE](https://splitch.dev/docs/error/VARIANT_NOT_AVAILABLE.md): 409. A referenced Variant is not promoted into this Environment. Flag definitions are App-level, but the set of Variants servable in a given Environment is per-Environment.
- [VARIANT_NOT_FOUND](https://splitch.dev/docs/error/VARIANT_NOT_FOUND.md): 404. No Variant matched the identifier in the request, within the scope your credential can reach.

## SDK (13)

Thrown by @splitch/sdk at construction, before any request goes out.

- [SDK_BOOTSTRAP_CONTEXT_MISMATCH](https://splitch.dev/docs/error/SDK_BOOTSTRAP_CONTEXT_MISMATCH.md): `createSplitchBrowserClient` received bootstrap evaluated for a different Targeting Key, id type, or attribute set than the client's fixed Evaluation Context.
- [SDK_CACHED_TELEMETRY_FAILED](https://splitch.dev/docs/error/SDK_CACHED_TELEMETRY_FAILED.md): A cached-evaluation telemetry report could not be delivered.
- [SDK_CONTEXT_INVALID](https://splitch.dev/docs/error/SDK_CONTEXT_INVALID.md): An Evaluation Context contains an invalid field: `createSplitchBrowserClient` was given one without a non-empty `targetingKey`, or `evaluate` / `evaluateDetails` received an empty or non-string explicit `idempotencyKey`.
- [SDK_CREDENTIAL_CONFIGURATION_INVALID](https://splitch.dev/docs/error/SDK_CREDENTIAL_CONFIGURATION_INVALID.md): `createSplitchClient` was given no credential, or both a `clientKey` and an `apiKey`.
- [SDK_IDEMPOTENCY_KEY_UNAVAILABLE](https://splitch.dev/docs/error/SDK_IDEMPOTENCY_KEY_UNAVAILABLE.md): `evaluate`, `evaluateDetails`, or `evaluateAll` was called without an `idempotencyKey` in a runtime where `crypto.randomUUID` does not exist. It is secure-context-only, so a page served over plain `http://` reaches this.
- [SDK_NOT_INITIALIZED](https://splitch.dev/docs/error/SDK_NOT_INITIALIZED.md): A synchronous Flag read on `@splitch/sdk/browser` happened before `init()` resolved.
- [SDK_REACT_PROVIDER_MISSING](https://splitch.dev/docs/error/SDK_REACT_PROVIDER_MISSING.md): A hook from `@splitch/sdk/react` rendered outside `SplitchProvider`.
- [SDK_RETRIES_INVALID](https://splitch.dev/docs/error/SDK_RETRIES_INVALID.md): `retries` was set to anything other than `0`.
- [SDK_SEEN_SET_MAX_SIZE_INVALID](https://splitch.dev/docs/error/SDK_SEEN_SET_MAX_SIZE_INVALID.md): The seen-set `maxSize` is not a positive integer.
- [SDK_SEEN_SET_TTL_INVALID](https://splitch.dev/docs/error/SDK_SEEN_SET_TTL_INVALID.md): The seen-set TTL is not a positive duration.
- [SDK_TRANSPORT_NETWORK](https://splitch.dev/docs/error/SDK_TRANSPORT_NETWORK.md): The SDK's transport threw before receiving an HTTP response — for example a network failure, a cancelled request that was not a timeout, or a local `fetch` misconfiguration such as an unbound `Window.fetch`.
- [SDK_TRANSPORT_PARSE](https://splitch.dev/docs/error/SDK_TRANSPORT_PARSE.md): The transport received a response body that could not be parsed as the expected evaluate/peek/verify shape.
- [SDK_TRANSPORT_TIMEOUT](https://splitch.dev/docs/error/SDK_TRANSPORT_TIMEOUT.md): The per-call request timeout elapsed, or the request was aborted. The timeout covers the whole call, so it can fire before any response arrives or while the response body is still streaming.

## CLI (20)

Raised by splitch itself. Each one carries the process exit code it returns.

- [CLI_API_ORIGIN_MISSING](https://splitch.dev/docs/error/CLI_API_ORIGIN_MISSING.md): exit 1. The platform target in `SPLITCH_PLATFORM_TARGET` has no API origin configured in the environment.
- [CLI_CONFIG_READ_FAILED](https://splitch.dev/docs/error/CLI_CONFIG_READ_FAILED.md): exit 1. `splitch.json` exists but could not be read or parsed.
- [CLI_CREDENTIAL_STORE_FAILED](https://splitch.dev/docs/error/CLI_CREDENTIAL_STORE_FAILED.md): exit 1. The credential store could not be read, written, or cleared.
- [CLI_DATA_PLANE_ERROR_CODE_MISSING](https://splitch.dev/docs/error/CLI_DATA_PLANE_ERROR_CODE_MISSING.md): exit 4. `splitch flags verify` got `reason: "ERROR"` from the data plane with no error code attached, so there is nothing to attribute the failure to.
- [CLI_DEVICE_APPROVAL_TIMEOUT](https://splitch.dev/docs/error/CLI_DEVICE_APPROVAL_TIMEOUT.md): exit 2. The device code expired before it was approved in the browser.
- [CLI_DEVICE_AUTHORIZATION_FAILED](https://splitch.dev/docs/error/CLI_DEVICE_AUTHORIZATION_FAILED.md): exit 2. The device authorization request failed, or the auth service returned a response missing a required field.
- [CLI_DEVICE_TOKEN_EXCHANGE_FAILED](https://splitch.dev/docs/error/CLI_DEVICE_TOKEN_EXCHANGE_FAILED.md): exit 2. The device code was approved but exchanging it for a token failed, or the token response carried no `user_id` to identify the session.
- [CLI_EMAIL_UNVERIFIED](https://splitch.dev/docs/error/CLI_EMAIL_UNVERIFIED.md): exit 2. The identity provider has not verified an email address for this account.
- [CLI_LOGOUT_REVOKE_FAILED](https://splitch.dev/docs/error/CLI_LOGOUT_REVOKE_FAILED.md): exit 2. The local credential was removed but the server refused to revoke the session, so the token may still be accepted elsewhere until it expires.
- [CLI_NOT_AUTHENTICATED](https://splitch.dev/docs/error/CLI_NOT_AUTHENTICATED.md): exit 2. No CLI login session is available.
- [CLI_OPERATION_UNKNOWN](https://splitch.dev/docs/error/CLI_OPERATION_UNKNOWN.md): exit 1. The command resolved to an operation id that is not in the registry.
- [CLI_ROUTE_SURFACE_UNSUPPORTED](https://splitch.dev/docs/error/CLI_ROUTE_SURFACE_UNSUPPORTED.md): exit 1. The operation the command maps to has no public origin the CLI can address.
- [CLI_SCOPE_UNRESOLVED](https://splitch.dev/docs/error/CLI_SCOPE_UNRESOLVED.md): exit 3. The App, Environment, or Flag the command addresses could not be pinned down: nothing matched the selector, more than one thing matched it, or an Environment was named without an App.
- [CLI_SERVER_CODE_UNRECOGNIZED](https://splitch.dev/docs/error/CLI_SERVER_CODE_UNRECOGNIZED.md): exit 4. The server returned an error code this CLI build does not know. The message is passed through verbatim.
- [CLI_SESSION_EXPIRED](https://splitch.dev/docs/error/CLI_SESSION_EXPIRED.md): exit 2. A login session exists but could not mint a usable token.
- [CLI_TOKEN_BINDING_REFUSED](https://splitch.dev/docs/error/CLI_TOKEN_BINDING_REFUSED.md): exit 3. The session token could not be bound to the requested scope, usually because the account holds no membership that reaches it.
- [CLI_TOKEN_INVALID](https://splitch.dev/docs/error/CLI_TOKEN_INVALID.md): exit 2. `SPLITCH_TOKEN` is empty or malformed, or a request in token mode returned HTTP 401 or HTTP 403 with `CREDENTIAL_REVOKED`. Other HTTP 403 responses keep their server error code.
- [CLI_UNEXPECTED_ERROR](https://splitch.dev/docs/error/CLI_UNEXPECTED_ERROR.md): exit 1. An unhandled fault inside the CLI. The underlying message is passed through.
- [CLI_USAGE_INVALID](https://splitch.dev/docs/error/CLI_USAGE_INVALID.md): exit 1. The invocation did not parse: an unknown command or flag, a flag missing its value, a required positional missing or supplied twice, an out-of-range value, or malformed `--body-json`.
- [CLI_VALIDATION_ERROR](https://splitch.dev/docs/error/CLI_VALIDATION_ERROR.md): exit 1. A typed flag or local setting could not be converted into a request, so nothing was sent. A malformed `--variants` list, `--variants` without `--key`, or an unrecognized `SPLITCH_PLATFORM_TARGET` all land here. Request bodies are never judged locally: the API validates them and answers `VALIDATION_ERROR`.

Source: https://splitch.dev/docs/errors