Every first-run failure, and its fix
Each of these is a real failure on the path from the quick start to your first refusal, in roughly the order you would meet it. Nothing here is needed by a run that works.
pip is missing, or refuses to install
The most common failure on the page, and it has two faces: pip: command not found and error: externally-managed-environment. On a current macOS or Linux the interpreter may only answer to python3, and the system Python refuses installs into itself (PEP 668). A virtual environment answers both at once, and it is what this repo's own CI does before it runs every published sample:
python3 -m venv .venv source .venv/bin/activate pip install agentbill-sdk
On Windows the activate line is .venv\Scripts\activate. Everything after this point assumes that environment is active, which is also what makes python3 first_run.py pick up the SDK you just installed.
Could not find a version that satisfies the requirement
The interpreter is too old. agentbill-sdk needs Python 3.9 or newer. Check with python3 --version before looking anywhere else: the resolver error names the package, not the cause, so it reads like a registry problem when it is not.
How to actually run the sample
The quick start hands you twenty lines of Python and the next sentence assumes you ran them. Save them as a file and run that file:
python3 first_run.py
For Node the extra rule is the module system. The sample uses a top-level await, so it has to be ES modules: save it as first-run.mjs and run node first-run.mjs, or add "type": "module" to your package.json. Saved as .js in a default project it fails with Cannot use import statement outside a module before a single line runs. The package being ESM is not enough: that is the dependency, and this rule is about your file.
KeyError: AGENTBILL_API_KEY
The sample reads the key from the environment on its fourth line, before a client exists, so this is what you get when the variable is not set in the shell you ran from. It is not a fault in the SDK and it carries no guidance, which is why it is here.
An environment variable lives in the shell that set it, and nowhere else: a new window, a new tab, or a run launched from your editor will not have it. Either run the export line again in this shell, or take the key out of the environment entirely and pass it in:
from agentbill import AgentBillClient client = AgentBillClient(api_key=SECRET_FROM_YOUR_VAULT)
That argument is Python only. The Node SDK exports functions rather than a client object and reads AGENTBILL_API_KEY itself, so on a host with no shell you set the variable in the platform's own config, or skip the SDK and send an Authorization: Bearer header yourself.
export is not recognized
You are in PowerShell, and the line handed to you with the key is POSIX syntax. The same key goes into $env:AGENTBILL_API_KEY, with the value in quotes. Nothing in the product prints the Windows form yet; this is it.
TaskCeilingRequiredError, or 422 task_ceiling_required
You ran the code before the job had a ceiling. This is neither a yes nor a refusal: there is nothing yet to check the call against, so the answer carries no approved field at all. Give the job a ceiling on the console's start screen or with PUT /tasks/:task_ref/ceiling, then run it again.
It worked once, and the second run is refused immediately
Correct, and it is the mechanism rather than a fault. A job ceiling has no clock. After one run of the quick start, job-1 has used 3 of 3, so the first preflight of a second run is the call that would cross the ceiling. Nothing resets it at midnight or at the start of a month, because a budget that resets tomorrow is exactly the thing a task ceiling exists not to be.
Two ways on: raise the ceiling on that job, in the console or through PUT /tasks/:task_ref/ceiling, or use a new job name and give it its own ceiling.
409 ceiling_below_committed
A ceiling cannot be set under what the job has already used plus what is reserved by calls in flight. The response carries minimum_ceiling_units, the smallest value that would be accepted.
If that minimum is higher than the work you think you did, you have reservations that were never settled, and the usual cause is a record() that was trimmed out of the loop. Preflight reserves; record settles. Without the settle, units stay held for the reservation TTL (60 minutes by default) and the job reads as having cost nothing while its headroom shrinks. They come back on their own when the reservation expires. Always pass the same task_ref to record that you passed to preflight: settlement matches on exactly that pair, and a record without it consumes no reservation.
A task_ceiling you passed was ignored
task_ceiling on a preflight is applied only by the call that opens a job that does not exist yet. Once the job exists the value is dropped in silence, so a retry cannot raise the ceiling it was written to respect. The ceiling in force is the last save through the console or PUT /tasks/:task_ref/ceiling.
A refusal is not an HTTP error
Every refusal comes back as HTTP 200 with approved: false, because the request itself succeeded and the answer was no. Branch on the field or catch the exception; a check on the status code will read every refusal as a success.
Both SDKs then draw one line, and it is the same line in Python and Node. They raise when your own spend rule refused the call (task_ceiling_exceeded, ceiling_exceeded, budget_exhausted) and return the result when AgentBill's own quota refused it (free_tier_exceeded, plan_limit_exceeded), with upgrade_url set. Our billing running out must never crash your agent, so those two come back as a value you can act on rather than an exception you did not plan for.
Getting back to the three-step screen
The link to it disappears from the console once you have had a first refusal, because it is a first-run screen. The address still works: agentbill.dev/app?view=start. And if you no longer have the key at all, /recover shows the filled-in export line again to whoever can read the email the account was registered with.