> For the complete documentation index, see [llms.txt](https://developers.share.inc/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developers.share.inc/errors/error-codes.md).

# Error codes

Every error response carries an `error` field (short human-readable summary) and, where applicable, a structured `rejectionReasons[]` array whose `code` is the machine-readable contract. Some endpoints additionally attach an `errorCode` field at the top level for endpoint-specific business rejections — those are listed in the **Endpoint-specific** table below.

Branch on `code` (or top-level `errorCode`), not on `error` or `message`. See [Error contract](/errors/error-contract.md) for the full envelope.

***

## Generic codes (`rejectionReasons[].code`)

These map class-validator failures and infrastructure errors. Stable across endpoints.

| Code               | HTTP      | Meaning                                                                                           |
| ------------------ | --------- | ------------------------------------------------------------------------------------------------- |
| `REQUIRED`         | 400       | Required field missing.                                                                           |
| `INVALID_FORMAT`   | 400       | Field present but malformed (regex/email/uuid/date/etc.).                                         |
| `INVALID_VALUE`    | 400       | Field well-formed but rejected by domain rules (e.g. negative amount).                            |
| `OUT_OF_RANGE`     | 400       | Numeric/date value outside an accepted range.                                                     |
| `UNSUPPORTED`      | 400       | Value belongs to an enum the endpoint does not currently support.                                 |
| `ALREADY_EXISTS`   | 409       | Uniqueness violation (email, phone, externalId, etc.).                                            |
| `CONFLICT`         | 409       | State conflict not covered by `ALREADY_EXISTS` (e.g. concurrent edit).                            |
| `NOT_FOUND`        | 404       | Resource referenced by path/ID does not exist (or is not visible to the caller's provider).       |
| `FORBIDDEN`        | 403       | Caller authenticated but not allowed to act on this resource.                                     |
| `UNAUTHORIZED`     | 401       | Missing or invalid `X-Access-Id` / `X-Access-Secret`.                                             |
| `PRECONDITION`     | 409 / 422 | Business pre-condition not met (e.g. subscriber must be ACTIVE).                                  |
| `IMMUTABLE`        | 409       | Field/resource is locked in its current state and cannot be changed via this endpoint.            |
| `NOT_ALLOWED`      | 400       | Field is forbidden on this endpoint and belongs to a different surface.                           |
| `RATE_LIMITED`     | 429       | Caller exceeded a documented rate limit.                                                          |
| `TIMEOUT`          | 408 / 504 | A downstream service (Kafka, partner adapter) timed out before producing a result. Safe to retry. |
| `UPSTREAM_FAILURE` | 502       | A required downstream call failed in a way the caller can retry.                                  |
| `INTERNAL_ERROR`   | 500       | Unhandled exception. Always paired with `field: "_"`. Escalate with `correlationId`.              |

The `field: "_"` sentinel marks errors that are not tied to a specific input field (auth, timeout, internal failures). Clients should treat `_` as "render the `message` as a top-level toast" rather than as a form- field validation error.

***

## Endpoint-specific codes (`rejectionReasons[].code`)

Same envelope slot as the generic codes above, but emitted only by the endpoints listed in the **Operations** column.

| Code               | HTTP | Operations                            | Meaning                                                                                                                                                                                                                                                                |
| ------------------ | ---- | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ALREADY_INACTIVE` | 409  | `POST /v1/subscribers/:id/deactivate` | The targeted set is empty — every requested subscription is already INACTIVE, or the subscriber has no subscriptions to deactivate. `field` is `subscriptionIds` for scoped calls and `id` for subscriber-wide calls. No state change. (DEV-1006, private-api parity.) |

***

## Endpoint-specific codes (top-level `errorCode`)

These are surfaced as a top-level `errorCode` on the error response (in addition to `error` and the HTTP status). They appear only on the endpoints listed in the **Operations** column.

| `errorCode`                       | HTTP | Operations                                                                                                         | Meaning                                                                                                                                                                                                                                                                                                                |
| --------------------------------- | ---- | ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PLAN_TYPE_NOT_SUPPORTED_VIA_API` | 400  | `POST /v1/plans`, `PUT /v1/plans/:id`, `POST /v1/plans/duplicate/:id`                                              | The supplied `planType` is recognized by the system but not creatable through the public API (only `RESIDENTIAL` is allowed via this surface today). On duplicate, the validation runs against the source plan's fields — a plan provisioned outside this surface that violates the rules cannot be cloned through it. |
| `PLAN_CURRENCY_NOT_SUPPORTED`     | 400  | `POST /v1/plans`, `PUT /v1/plans/:id`, `POST /v1/plans/duplicate/:id`                                              | The supplied `priceCurrency` is not in Schedule D for the target plan type.                                                                                                                                                                                                                                            |
| `PLAN_SPEED_EXCEEDS_1GBPS`        | 400  | `POST /v1/plans`, `PUT /v1/plans/:id`, `POST /v1/plans/duplicate/:id`                                              | `downloadSpeedMbps` is greater than 1000. The public API caps at 1 Gbps to align with Schedule D — uncapped plans require a manual onboarding path.                                                                                                                                                                    |
| `PLAN_PRICE_BELOW_FLOOR`          | 400  | `POST /v1/plans`, `PUT /v1/plans/:id`, `POST /v1/plans/duplicate/:id`, `PATCH /v1/subscriptions/:id` (plan change) | `priceAmount` is below the Schedule D floor for the (type, currency, speed) tuple.                                                                                                                                                                                                                                     |
| `CONFIRMATION_MISMATCH`           | 400  | `POST /v1/plans/:id/disable`, `DELETE /v1/plans/:id`                                                               | The `confirm` query parameter does not echo the plan name (case-insensitive, whitespace-trimmed). Re-fetch the plan and retry with the correct name.                                                                                                                                                                   |
| `PLAN_ALREADY_DISABLED`           | 409  | `POST /v1/plans/:id/disable`                                                                                       | The target plan has `disabledAt` set. Re-enable via `POST /v1/plans/:id/enable` before disabling again.                                                                                                                                                                                                                |
| `PLAN_ALREADY_ENABLED`            | 409  | `POST /v1/plans/:id/enable`                                                                                        | The target plan is not disabled (`disabledAt` is null). No state change.                                                                                                                                                                                                                                               |
| `PLAN_NOT_DISABLED`               | 409  | `DELETE /v1/plans/:id`                                                                                             | Deletion requires the plan to be disabled first (`POST /v1/plans/:id/disable`). No state change.                                                                                                                                                                                                                       |
| `PLAN_DISABLED`                   | 400  | `PATCH /v1/subscriptions/:id` (plan change)                                                                        | The target plan exists but is disabled — pick a different plan.                                                                                                                                                                                                                                                        |
| `SUBSCRIPTION_TERMINAL`           | 400  | `PATCH /v1/subscriptions/:id`                                                                                      | The subscription is in a terminal state (`CANCELLED`, `MIGRATED_OUT`) and cannot be updated.                                                                                                                                                                                                                           |
| `CONTACT_SHARE`                   | 400  | `PATCH /v1/subscriptions/:id` (plan change), `POST /v1/subscriptions/:id/activate`                                 | The requested change requires Share intervention — typically an external→Share plan migration mid-cycle, or a deferred override conflict.                                                                                                                                                                              |
| `CHARGE_NOT_CANCELLABLE`          | 409  | `POST /v1/subscribers/:subscriberId/charges/:chargeId/cancel`                                                      | Charge is not in `PENDING` (already paid, waived, refunded, or cancelled). No state change.                                                                                                                                                                                                                            |
| `CHARGE_NOT_WAIVABLE`             | 409  | `POST /v1/subscribers/:subscriberId/charges/:chargeId/waive`                                                       | Charge is not in `PENDING` (already paid, waived, refunded, or cancelled). No state change.                                                                                                                                                                                                                            |

***

## Non-coded message-only rejections

A handful of CRM business rules throw `BadRequestException` with a snake-case message but no structured `errorCode`. These are surfaced as the `error` field on the envelope. Treat them as a code for now — they will migrate to the structured top-level `errorCode` form as the backend collapses them into the taxonomy above.

| `error` string                           | HTTP | Where it can fire                                                  | Meaning                                                                                                              |
| ---------------------------------------- | ---- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
| `subscriber_terminal`                    | 400  | `POST /v1/subscribers/:subscriberId/charges`                       | The subscriber is in a terminal state (`TERMINATED`, etc.) and cannot accept new charges.                            |
| `subscriber_not_found`                   | 404  | Charge endpoints (list, get, create)                               | The subscriber does not exist or does not belong to the caller's provider.                                           |
| `subscription_mismatch`                  | 400  | `POST /v1/subscribers/:subscriberId/charges`                       | The supplied `subscriptionId` either does not belong to the URL's subscriber or is owned by a different provider.    |
| `invalid_expires_at`                     | 400  | `POST /v1/subscribers/:subscriberId/charges`                       | `expiresAt` is unparseable or in the past.                                                                           |
| `invalid_created_after` / `..._before`   | 400  | `GET /v1/subscribers/:subscriberId/charges`                        | Query filter date is unparseable.                                                                                    |
| `charge_not_found`                       | 404  | `GET .../charges/:chargeId`, `POST .../charges/:chargeId/cancel`   | The charge does not exist, is not owned by the URL's subscriber, or the subscriber is owned by a different provider. |
| `installation_already_active`            | 409  | `POST /v1/installations`                                           | A non-terminal Installation already exists for the same subscription (single-active invariant).                      |
| `installation_terminal`                  | 400  | `POST /v1/installations/:id/cancel`, `PATCH /v1/installations/:id` | The installation is already in a terminal state (`CANCELLED`, `COMPLETED`).                                          |
| `subscription_missing_demarcation_point` | 400  | `POST /v1/installations`                                           | The target subscription has no `demarcationPointId` set; assign one via `PATCH /v1/subscriptions/:id` first.         |
| `Invalid or inactive plan`               | 400  | `POST /v1/subscriptions`, `PATCH /v1/subscriptions/:id`            | `planId` does not resolve to an active, non-disabled plan.                                                           |
| `Plan does not belong to this provider`  | 400  | `POST /v1/subscriptions`, `PATCH /v1/subscriptions/:id`            | The plan exists but is owned by a different provider — cross-provider IDs are not accepted.                          |
| `Email already in use for this provider` | 400  | `POST /v1/subscribers`, `PUT /v1/subscribers/:id`                  | Soft-uniqueness check against the caller's existing subscribers.                                                     |
| `Phone already in use for this provider` | 400  | `POST /v1/subscribers`, `PUT /v1/subscribers/:id`                  | Soft-uniqueness check against the caller's existing subscribers.                                                     |

***

## See also

* [Error contract](/errors/error-contract.md) — full envelope shape and worked examples.
* [Subscription states](/concepts/subscription-states.md) — for `SUBSCRIPTION_TERMINAL` and `PLAN_DISABLED` context.
* [Revenue split](/concepts/revenue-split.md) — for what a plan price floor is and why it is enforced.
