Repository navigation
Conversation
7cd039d to
b69cdc1
Compare
ebe76ec to
5660716
Compare
5660716 to
697020d
Compare
|
|
ebaec87 to
2891cbb
Compare
2768536 to
0d69f5d
Compare
0d69f5d to
b463e46
Compare
ae0ea59 to
22f5e73
Compare
tkkhq
left a comment
There was a problem hiding this comment.
Existing sandbox contract mismatches remain at this head:
- Setting
idle_timeout_secondsstill adds that field to the create request atsrc/volcano_sdk/sandboxes.py:68–70, butCreateSandboxSessionRequestforbids extra properties and does not define it. Remove the option fromSandboxCreateOptionsand request construction, and update their tests. Existing discussion. - An unlimited local session returns
expires_at: null, which the contract permits. Session construction and refresh still pass it to the timestamp decoder atsrc/volcano_sdk/sandbox_session.py:89,166; the decoder rejects None withTypeError. Accept None in the public session type and decoder, and add regression coverage. Existing discussion.
2390c31 to
2f166c8
Compare
AI review: 1 P0-P2 finding(s)This bot PR re-vendors 🟠 P1
⚪ P3
🟣 Pre-existing
Not checked: The AI review read the diff, the code, and the PR's text; it ran no tests or builds and read nothing outside this repository.
Push a fix, or have a collaborator with write access (including the PR author) add the Human review: needed
Decide the points above: approve to accept them, or request changes. Suggested reviewer: |
| return response_204 | ||
|
|
||
| if response.status_code == 401: | ||
| response_401 = Error.from_dict(response.json()) |
There was a problem hiding this comment.
⚪ P3: A logout 401 that doesn't match the Error schema now raises a raw exception from sign_out and leaves the local session set
The regenerated auth_logout._parse_response now runs Error.from_dict(response.json()) on a 401. AuthIdentityTransport.auth_logout (src/volcano_sdk/_transport_auth_identity.py:365) still calls auth_logout.sync_detailed and passes the result to parsed_response.
If a 401 body is not JSON, or has no error key (for example a 401 from a gateway or proxy), the parser raises JSONDecodeError or KeyError. Before this change, a 401 had no parser: parsed was None and parsed_response decoded the body leniently.
_sign_out_captured (src/volcano_sdk/_auth_requests.py:229-240) only catches VolcanoError. The raw exception therefore skips clear_session_if_current, and client.auth.sign_out() fails without clearing the local session. Previously it raised AuthenticationError after clearing the session.
The documented contract says the Volcano server returns Error JSON on a 401, so the trigger needs a 401 that breaks that contract. The fix is to switch the logout adapter to generated_request(...) plus unparsed_response, the pattern auth_forgot_password, auth_reset_password, and auth_signup_anonymous already use.
Found by claude-opus-5-5
| return response_204 | ||
|
|
||
| if response.status_code == 401: | ||
| response_401 = Error.from_dict(response.json()) |
There was a problem hiding this comment.
🟡 P2: Preserve local sign-out cleanup when a 401 body cannot be parsed
If an auth proxy returns HTTP 401 with an empty or HTML body, this new parser raises JSONDecodeError. AuthIdentityTransport.auth_logout() calls it directly, while AuthRequests._sign_out_captured() catches only VolcanoError, so client.auth.sign_out() skips clear_session_if_current() and retains local credentials, violating the cleanup guarantee in docs/authentication.md. Previously this response reached normal error handling and session cleanup. Preserve raw error handling in the handwritten logout adapter or translate parsing failures into TransportError.
Found by gpt-6.1-sol
2f166c8 to
6b6ea84
Compare
| response_204 = cast(Any, None) | ||
| return response_204 | ||
|
|
||
| if response.status_code == 401: |
There was a problem hiding this comment.
⚪ P3: A malformed 401 from logout now escapes sign_out as KeyError/JSONDecodeError instead of AuthenticationError
The regenerated auth_logout._parse_response now runs Error.from_dict(response.json()) on a 401. AuthIdentityTransport.auth_logout (src/volcano_sdk/_transport_auth_identity.py:372) calls auth_logout.sync_detailed without a guard. Before this change, a 401 parsed to None and parsed_response fell back to decoding the raw body, so response_payload raised AuthenticationError. Now a 401 with a non-JSON body, or a JSON body without an error key (for example from a gateway or proxy), raises JSONDecodeError or KeyError from the transport. invoke only wraps httpx.HTTPError, and _sign_out_captured only catches VolcanoError. So sign_out() raises a raw exception and skips clear_session_if_current, leaving the local session in place. The repo already guards this case for other auth operations (see test_generated_transport_preserves_a_malformed_session_auth_error). The fix is to route logout through request_kwargs + generated_request + unparsed_response, as auth_forgot_password does, or to return unparsed_response on a 401 before parsing. A transport test with httpx.Response(401, content=b"not-json") should cover it.
Found by claude-opus-5-5
| return response_204 | ||
|
|
||
| if response.status_code == 401: | ||
| response_401 = Error.from_dict(response.json()) |
There was a problem hiding this comment.
🟡 P2: Logout now parses 401 bodies as Error, so a malformed 401 escapes sign_out() as a raw exception and leaves the session in place
Before this change, auth_logout._parse_response returned None for a 401. parsed_response then decoded the body leniently, and response_payload(response, 204) raised AuthenticationError. The regenerated code calls Error.from_dict(response.json()) on every 401. A 401 whose body isn't JSON, or has no error key (for example a gateway's {"message": "Unauthorized"}), now raises json.JSONDecodeError or KeyError from inside auth_logout.sync_detailed.
Nothing on the way up catches it:
invokeonly wrapshttpx.HTTPError._sign_out_captured(_auth_requests.py:229-234) only catchesVolcanoError.
So client.auth.sign_out() raises a raw exception, and clear_session_if_current never runs, which leaves the local session signed in.
The repo already treats this as a contract. auth_get_user and similar operations return unparsed_response before generated parsing on 401. Tests such as test_generated_transport_preserves_a_malformed_session_auth_error assert that a not-json 401 surfaces as AuthenticationError. No test covers a 401 on logout, so CI won't catch this.
Fix: route AuthIdentityTransport.auth_logout (_transport_auth_identity.py:365-376) through the generated request_kwargs plus generated_request/unparsed_response, as auth_forgot_password already does, or short-circuit 401 before parsing. Then add a malformed-401 logout test.
Found by claude-opus-5-5
| response_204 = cast(Any, None) | ||
| return response_204 | ||
|
|
||
| if response.status_code == 401: |
There was a problem hiding this comment.
🟡 P2: Logout now parses 401 bodies, so a 401 with a malformed body makes sign_out() fail without clearing the local session
Before this PR, auth_logout._parse_response returned None for any non-204 status, and response_payload(response, 204) raised AuthenticationError. The regenerated module now runs Error.from_dict(response.json()) on 401. The adapter (_transport_auth_identity.py auth_logout) still calls sync_detailed, so this runs on every logout.
If the 401 body is not JSON (a gateway or HTML error page, an empty body), response.json() raises JSONDecodeError. If the JSON has no error key, Error.from_dict raises KeyError. Nothing on the path catches either: invoke only catches httpx.HTTPError, and _sign_out_captured only catches VolcanoError. So clear_session_if_current never runs. The stale session stays in memory and the caller gets a raw exception. That breaks the docs/authentication.md promise that a revocation failure is raised after the local session is cleared.
The repo already guards against this elsewhere: the refresh path catches KeyError/TypeError/ValueError, several auth adapters return unparsed_response on 401, and test_generated_transport_preserves_a_malformed_*_auth_error tests pin it.
Fix: switch the logout adapter to generated_request(client, request_kwargs(body=...)) plus unparsed_response, as auth_forgot_password and auth_signup_anonymous do. Add a test for a non-JSON 401 on logout.
Found by claude-opus-5-5
| pass | ||
| return cast(FrontendCustomDomainRedirect | None, data) | ||
|
|
||
| redirect = _parse_redirect(d.pop("redirect")) |
There was a problem hiding this comment.
🟠 P1: Update managed-TLS fixtures for the required redirect field
from_dict() now requires redirect, as does ProjectFrontendCustomDomain.from_dict(), but domain_payload() in src/volcano_sdk/_tests/test_managed_tls_contract.py still omits it. Consequently, the ownership-challenge, pending-domain, failed-domain, and legacy-routing tests raise KeyError('redirect') before reaching their assertions, failing the standard CI test task. Update the fixtures with redirect: None and explicitly resolve compatibility for the older-server cases.
Found by gpt-6.1-sol
| pass | ||
| return cast(FrontendCustomDomainRedirect | None, data) | ||
|
|
||
| redirect = _parse_redirect(d.pop("redirect")) |
There was a problem hiding this comment.
🟠 P1: Newly required redirect breaks the existing managed-TLS contract tests
FrontendCustomDomainResponse.from_dict (and ProjectFrontendCustomDomain.from_dict at project_frontend_custom_domain.py:215) now calls d.pop("redirect") with no default. The PR leaves src/volcano_sdk/_tests/test_managed_tls_contract.py unchanged. That file is in pytest testpaths, and its domain_payload() fixture has no redirect key. As a result, test_create_operation_decodes_the_ownership_challenge, test_pending_domain_decodes_the_certificate_validation_record, test_failed_domain_decodes_its_failure_reason and test_deprecated_routing_record_from_older_servers_round_trips should all raise KeyError: 'redirect' instead of decoding, through both create_frontend_custom_domain/get_frontend_custom_domain and the project feed decoder. Fix: add "redirect": None to domain_payload(), and consider a case with a non-null redirect. Do not hand-edit the generated model.
Found by claude-opus-5-5
| pass | ||
| return cast(FrontendCustomDomainRedirect | None, data) | ||
|
|
||
| redirect = _parse_redirect(d.pop("redirect")) |
There was a problem hiding this comment.
🟡 P2: Update managed TLS fixtures for the required redirect field
The unchanged domain_payload() in src/volcano_sdk/_tests/test_managed_tls_contract.py omits redirect. Consequently, test_create_operation_decodes_the_ownership_challenge raises KeyError('redirect'); project-feed decoding fails similarly, and polling returns a raw dictionary that fails its type assertion. These tests run through poe checks, blocking the mandatory CI gate. Update the shared fixture to include redirect: None for the new contract.
Found by gpt-6.1-sol
| return response_404 | ||
|
|
||
| if response.status_code == 416: | ||
| response_416 = Error.from_dict(response.json()) |
There was a problem hiding this comment.
⚪ P3: Download now parses 416 bodies strictly, so a non-JSON 416 escapes as a raw decode error
download_storage_object._parse_response now runs Error.from_dict(response.json()) for 416. Bucket.download(path, byte_range=...) reaches it through parsed_response in _transport_storage.py:169. A range past end of file that comes back with a non-JSON or error-less body now raises JSONDecodeError/KeyError instead of a VolcanoError with status 416.
The contract documents a JSON Error body (StorageRangeNotSatisfiable), and 429 already behaves this way, so impact depends on a non-conforming response. Using generated_request + unparsed_response for downloads, or catching decode errors around the generated parse, would keep error mapping robust.
Found by claude-opus-5-5
| return response_404 | ||
|
|
||
| if response.status_code == 416: | ||
| response_416 = Error.from_dict(response.json()) |
There was a problem hiding this comment.
⚪ P3: Preserve SDK error mapping for malformed range-error responses
If a byte-range download receives an empty or non-JSON 416, this parser now raises a raw decoding exception. StorageTransport.download_storage_object() calls it directly, so Bucket.download() no longer raises a VolcanoError carrying status 416 as it did before. This requires a response outside the documented JSON error contract; using raw response handling in the handwritten download adapter would preserve the previous robustness.
Found by gpt-6.1-sol
| @@ -6428,44 +6751,45 @@ paths: | |||
| application/json: | |||
| schema: | |||
| $ref: '#/components/schemas/Error' | |||
| /durable-callbacks: | |||
| /durable-approvals: | |||
There was a problem hiding this comment.
🟡 P2: Sync drops the /durable-callbacks contract that ctx.wait_for_callback and the published docs still use
The sync deletes POST /durable-callbacks and its succeed, fail and heartbeat operations, along with every DurableCallback* schema and the generated modules for them. The handwritten SDK, unchanged, still depends on these:
register_callbackinsrc/volcano_sdk/_durable_callback_token.py:169posts to/durable-callbacksforctx.wait_for_callback, the feature added in the base commit (feat(durable): wait for callbacks with ctx.wait_for_callback #400).- The published
docs/functions.md:241-259tells users to call/durable-callbacks/succeed,/failand/heartbeat.
The other platform paths the SDK calls (/durable-approvals, /durable-messages/*) are still in the contract, so only the callback feature is affected. There are two possibilities, and both need handling before merge:
- Hosting at f0ab44447199 does not yet have the endpoints, for example because feat(durable): wait for callbacks with ctx.wait_for_callback #400 was vendored from an unmerged Hosting change. Then this sync rolls the vendored contract back.
- Hosting removed the endpoints. Then
wait_for_callbackand its docs are shipping against a server that no longer serves them.
CONTRIBUTING.md requires SDK-facing wire-contract changes to update the affected facade, tests and public examples together. Hosting compatibility should be confirmed first; then either re-sync from a Hosting commit that has the endpoints, or remove or gate wait_for_callback and its docs.
Found by claude-opus-5-5
| @@ -6428,44 +6751,45 @@ paths: | |||
| application/json: | |||
| schema: | |||
| $ref: '#/components/schemas/Error' | |||
| /durable-callbacks: | |||
| /durable-approvals: | |||
There was a problem hiding this comment.
🟡 P2: Restore the callback contract still used by the SDK
The sync deletes all four /durable-callbacks operations and their generated models, while register_callback() in src/volcano_sdk/_durable_callback_token.py still posts to /durable-callbacks for ctx.wait_for_callback(). Published docs/functions.md also instructs users to call the completion and heartbeat routes. The vendored contract consequently no longer describes this shipped feature. Re-sync a compatible Hosting bundle containing callbacks, or coordinate an intentional removal with the facade and documentation.
Found by gpt-6.1-sol
| response_404 = cast(Any, None) | ||
| return response_404 | ||
|
|
||
| if response.status_code == 416: |
There was a problem hiding this comment.
⚪ P3: A 416 download response is now parsed strictly, so a non-JSON 416 escapes as a raw decode error
The regenerated _parse_response now runs Error.from_dict(response.json()) for 416. StorageTransport.download_storage_object (_transport_storage.py:169) calls sync_detailed and then parsed_response. Suppose a byte-range download past end of file returns a 416 with an empty or non-JSON body, or JSON with no error key. The caller then gets JSONDecodeError or KeyError instead of a VolcanoError with status 416.
The contract documents a JSON Error body (StorageRangeNotSatisfiable), and 429 already behaves this way, so this needs a non-conforming response. Two fixes would keep error mapping robust: route downloads through generated_request plus unparsed_response, or catch decode errors around the generated parse.
Found by claude-opus-5-5
Summary
API change report
Public API
New Endpoints: 20
POST /projects/{id}/frontends/{frontendId}/deployments
GET /projects/{id}/frontends/{frontendId}/domains
POST /projects/{id}/frontends/{frontendId}/domains
DELETE /projects/{id}/frontends/{frontendId}/domains/{domain}
GET /projects/{id}/frontends/{frontendId}/domains/{domain}
PATCH /projects/{id}/frontends/{frontendId}/domains/{domain}
GET /projects/{id}/frontends/{frontendId}/frontend-routes
POST /projects/{id}/frontends/{frontendId}/frontend-routes
DELETE /projects/{id}/frontends/{frontendId}/frontend-routes/{routeId}
PUT /projects/{id}/frontends/{frontendId}/frontend-routes/{routeId}
PUT /projects/{id}/git-connection/root-directory
POST /projects/{id}/issues/{issueId}/lavaflow-runs
GET /projects/{id}/lavaflow
PATCH /projects/{id}/lavaflow
GET /projects/{id}/lavaflow-runs
GET /projects/{id}/lavaflow-runs/{runId}
GET /projects/{id}/notifications
PUT /projects/{id}/notifications
POST /projects/{id}/notifications/test
POST /projects/{id}/notifications/webhook-secret
Deleted Endpoints: 6
GET /projects/{id}/autofix
PUT /projects/{id}/autofix
GET /projects/{id}/autofix-runs
GET /projects/{id}/autofix-runs/{runId}
DELETE /projects/{id}/autofix/api-key
POST /projects/{id}/issues/{issueId}/autofix-runs
Modified Endpoints: 260
POST /auth/device/verify
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'POST /auth/forgot-password
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'POST /auth/logout
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'GET /auth/oauth/providers
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'POST /auth/oauth/{provider}/call-api
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'POST /auth/oauth/{provider}/link
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'POST /auth/oauth/{provider}/refresh-token
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'GET /auth/oauth/{provider}/token
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'DELETE /auth/oauth/{provider}/unlink
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'GET /auth/password-policy
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'POST /auth/platform/exchange
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'POST /auth/refresh
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'POST /auth/resend-confirmation
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'POST /auth/reset-password
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'POST /auth/signup
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'POST /auth/signup-anonymous
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'GET /auth/user
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'PUT /auth/user
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'DELETE /auth/user/cancel-email-change
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'POST /auth/user/change-email
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'POST /auth/user/confirm-email-change
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'POST /auth/user/convert-anonymous
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'GET /auth/user/identities
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'DELETE /auth/user/identities/{identityId}
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it. A 403 without a code leaves the reason unsReport truncated; see the source commit for the complete contract diff.
Validation