feat(errors): actionable messages + PlanNotEntitledError for plan-gated 403s - #87
Merged
Conversation
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 asPlanNotEntitledError— a subclass ofBadRequestErrorviaBadRequestError.__new__:except BadRequestErrorstill 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.Actionable error messages
ApiError.__str__appends a one-line hint:SMALLEST_API_KEY+ where to get a key.Public errors module
New
smallestai.errorsre-exporting the common types;PlanNotEntitledErroron the package root.Notes
core/api_error.py,core/_error_hints.py,atoms/errors/bad_request_error.py,errors.py) added to.fernignorewith re-sync comments.🤖 Generated with Claude Code