Skip to content

feat(errors): actionable messages + PlanNotEntitledError for plan-gated 403s - #87

Merged
abhishekmishragithub merged 3 commits into
mainfrom
chore/devx-errors
Aug 5, 2026
Merged

feat(errors): actionable messages + PlanNotEntitledError for plan-gated 403s#87
abhishekmishragithub merged 3 commits into
mainfrom
chore/devx-errors

Conversation

@abhishekmishragithub

@abhishekmishragithub abhishekmishragithub commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

DevX pass (backward-compatible) — error ergonomics

First slice of the DevX pass. All additive; nothing renamed or removed. Detection is now spec-backed against the atoms main-backend middleware, not a guess.

PlanNotEntitledError (plan / entitlement gating)

The platform (main-backend access.middleware.ts) rejects a plan-gated feature with HTTP 400, body {status: false, errors: ["Your plan has no access to <feature>, please upgrade to a higher plan…"]} (and "reached the maximum … for your plan" limit variants). Such a 400 is now raised as PlanNotEntitledError — a subclass of BadRequestError via BadRequestError.__new__:

from smallestai import PlanNotEntitledError
try:
    client.atoms.campaigns.create(...)   # a plan-gated feature
except PlanNotEntitledError:
    ...  # "needs a higher plan"

except BadRequestError still catches it (subclass) — fully backward-compatible. Gated features per the platform map: campaigns, knowledge base, webhooks, integrations, phone numbers, inbound/outbound telephony, chat, waves models, gpt-4o, electron, voice-clone.

Note: this is HTTP 400, not 403. HTTP 403 is a separate org allow-list gate ("… not available for your organization", e.g. GPT 5.2) that the user cannot self-upgrade — that gets its own hint, no special type.

Actionable error messages

ApiError.__str__ appends a one-line hint:

  • 401 → check SMALLEST_API_KEY + where to get a key.
  • 400 (plan-gated) → upgrade your plan.
  • 403 (org-gated) → contact your account team.

Public errors module

New smallestai.errors re-exporting the common types; PlanNotEntitledError on the package root.

Notes

  • Hand-maintained files (core/api_error.py, core/_error_hints.py, atoms/errors/bad_request_error.py, errors.py) added to .fernignore with re-sync comments.
  • No version bump — accumulating the DevX pass (with feat(waves): TTS helpers + x-expire-content (DevX, cuts 5.5.0) #88); cut one 5.5.0 when the pass lands.
  • 8 unit tests using the real platform envelope; mypy-clean.

🤖 Generated with Claude Code

…ed 403s

DevX pass (backward-compatible):

- A 403 whose body looks like a plan/entitlement gate is now raised as
  PlanNotEntitledError (a subclass of ForbiddenError, via ForbiddenError.__new__)
  so callers can `except PlanNotEntitledError` for enterprise-gated features
  (e.g. DNC writes). Existing `except ForbiddenError` handlers keep working.
- ApiError messages now append an actionable hint: 401 points to SMALLEST_API_KEY
  and where to get a key; a plan-gated 403 points to upgrading the plan.
- New public `smallestai.errors` module re-exporting the common error types;
  PlanNotEntitledError exported from the package root.

The plan-gate detection is a conservative heuristic on the body until the
platform exposes a stable machine-readable code (tracked in SDK_ESCALATIONS.log).
Hand-maintained files added to .fernignore with re-sync notes. No version bump
(accumulating the DevX pass). Adds unit tests.
Verified against atoms main-backend access.middleware.ts: a plan/entitlement
gate is returned as HTTP 400 with body {status:false, errors:["… please upgrade
to a higher plan …"]}, not a 403. (403 is org allow-list gating, e.g. GPT 5.2 —
"not available for your organization" — which the user cannot self-upgrade.)

- PlanNotEntitledError now subclasses BadRequestError (400) and is specialized in
  BadRequestError.__new__; forbidden_error.py restored to generated.
- _body_text reads the platform's `errors` array; markers match the real
  message ("upgrade to a higher plan" / "no access to" / "reached the maximum").
- hint_for: 400+plan-gated -> upgrade hint; 403 org-gated -> contact-account-team
  hint; 401 -> API-key hint. Tests use the real 400 envelope.
…ants

The platform uses several plan-gating wordings (access.middleware.ts +
constants/error-messages.ts): "…please upgrade to a higher plan", "…not
available in your current plan. Please upgrade.", "Maximum … reached. Please
upgrade your plan…". The previous marker list missed the phone-number and
workflow-editor variants. Every variant pairs "upgrade" with "plan", and a
generic 400 does not, so detect on that. Adds tests for the extra wordings.
@abhishekmishragithub
abhishekmishragithub merged commit a7b7c3b into main Aug 5, 2026
3 checks passed
@abhishekmishragithub
abhishekmishragithub deleted the chore/devx-errors branch August 5, 2026 17:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant