Skip to content

A blocked account tells three different stories: healthy auth status, a websocket Authorization prompt on engine commands, a 402 on REST #1745

Description

@sneg55

Context: API Makeathon participant. This failure mode cost more debugging time than anything else in our project, because the message on the engine path points at credentials when the actual problem is billing.

The account state

Free tier, all included usage consumed, no payment method on file:

$ zoo api /user/payment/balance
  "monthly_api_credits_remaining": 0,
  "stable_api_credits_remaining": 0,
  "subscription_details": { "modeling_app": { "display_name": "Free", ... } }

The three stories

1. zoo auth status reports healthy, throughout:

$ zoo auth status
https://api.zoo.dev/
✔ Logged in to https://api.zoo.dev/ as <account> (ZOO_API_TOKEN)
✔ Token: *******************

2. Every engine-path command reports a credential problem:

$ zoo kcl analyze cube.kcl --format json
engine: Please send `{ headers: { Authorization: "Bearer <token>" } }` over this websocket.
$ echo $?
1

Persistent across retries, same for zoo kcl volume and other engine subcommands.

3. Only the REST path tells the truth:

$ zoo ml text-to-cad export --output-format=step ...
Server Error: 402 Payment Required {
  "error_code": "missing_payment_method",
  "message": "Your account is blocked because you have used all included usage on
              your current plan for this month and do not have a payment method on
              file. ..."
}

Why the auth prompt is misleading

The token is valid: zoo user get succeeds with it, and a hand-rolled websocket handshake against /ws/modeling/commands with that token in an Authorization header returns HTTP/1.1 101 Switching Protocols. The identical handshake with no Authorization header also returns 101, so the upgrade itself is not the gate; the server asks for credentials in-band afterwards, and that in-band prompt is what the CLI surfaces to the user.

A user who sees story 2 goes hunting for a token problem that does not exist: re-exporting ZOO_API_TOKEN, trying KITTYCAD_API_TOKEN, re-running zoo auth status (which says everything is fine). Nothing on that path mentions billing.

Honest caveat

We could not retest the engine path after restoring credits (the account is still blocked), so we cannot prove the in-band prompt is caused by the blocked account rather than coinciding with it. The report stands either way: the same account state yields a healthy status, a credential prompt, and an actionable 402 depending on which path reports it, and only one of the three tells the user what to do.

Suggested behavior

  • When the engine websocket rejects a session for a billing/entitlement reason, surface the same error_code/message the REST path returns, not an Authorization prompt.
  • zoo auth status could additionally report entitlement ("logged in, but account is blocked: missing payment method"), since it is the first thing a user checks.

Environment

  • Zoo CLI v0.2.184 (latest release, published 2026-07-22) against https://api.zoo.dev/
  • Captured 2026-07-25; token supplied via ZOO_API_TOKEN (no stored zoo auth login credentials on the machine, so that code path is untested)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions