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)
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:
The three stories
1.
zoo auth statusreports healthy, throughout:2. Every engine-path command reports a credential problem:
Persistent across retries, same for
zoo kcl volumeand other engine subcommands.3. Only the REST path tells the truth:
Why the auth prompt is misleading
The token is valid:
zoo user getsucceeds with it, and a hand-rolled websocket handshake against/ws/modeling/commandswith that token in anAuthorizationheader returnsHTTP/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, tryingKITTYCAD_API_TOKEN, re-runningzoo 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
error_code/message the REST path returns, not an Authorization prompt.zoo auth statuscould additionally report entitlement ("logged in, but account is blocked: missing payment method"), since it is the first thing a user checks.Environment
https://api.zoo.dev/ZOO_API_TOKEN(no storedzoo auth logincredentials on the machine, so that code path is untested)