Security
What to do if you find a problem, what our code sends from your machine, and what the service does with your account today. The same policy, for the repository, is in SECURITY.md.
Report a vulnerability
Write to hello@agentbill.dev, or use GitHub's private vulnerability reporting on the repository. Please do not open a public issue. Say what you found, how to reproduce it, and what you think it allows. You will hear back within 48 hours.
In scope: the API and every page on agentbill.dev, the console, and the open-source clients (the Python and Node SDKs, the MCP server, the OpenClaw plugin). Out of scope: denial of service or load testing against agentbill.dev, social engineering, and the services we build on (Fly.io, Supabase, Polar, Resend), which you should report to them.
Test only against an account you created yourself. If you do that in good faith, stop as soon as you can reach anything that is not yours, and tell us, we will not take or support legal action against you for it. Please give us time to fix a problem before you publish it.
If a key leaks
Revoke it. From the next request on, it no longer authenticates.
curl -X POST https://agentbill.dev/keys/revoke \
-H "Authorization: Bearer agb_another_key_on_the_account" \
-H "Content-Type: application/json" \
-d '{"key_prefix":"agb_1234…abcd"}'That is the form GET /keys and the console's keys view at
agentbill.dev/app show each key in; neither ever shows a key itself. The keys
view can also revoke every key on the account at once. If you have no key left at all,
agentbill.dev/recover sends a single-use link to the account's email address,
which makes a new key. Rotation (POST /keys/rotate) keeps the old key
working for an hour unless you ask for less, which is for planned changes; for a leak, revoke.
What our code sends from your machine
Every request carries your AgentBill key and goes over HTTPS.
preflight(),record()andmeter()send what you pass them: the agent, the job (task_ref), the customer, the units, an idempotency key, and any metadata you attach.wrap()also sends, for each model call: the provider, the model, the token counts the provider reported, how long the call took, the step you named, the model you asked for when a different one answered, the provider's service tier when it reports one, whether the call was streamed, and the provider's response id, used so a retried record is counted once.wrap()never sends your prompts, the model's answers, or your provider keys. Those stay between your process and the provider.- The OpenClaw plugin sends the job, the token total, the provider and model names, and a tool's name for a tool call. It does not send the conversation or what a tool was given or returned.
What the service does today
- Every page and API call is HTTPS, with HSTS. The connection to our database is encrypted and its certificate is verified.
- Every query that reads or writes your data is scoped to your account, and one account cannot read another's jobs, customers, keys or refusals.
- A key is shown once, when it is made, and is never emailed. Keys are stored in plain text in our database today; storing only a hash of each key is the next change we are making.
- When a key is used from a network it has never been used from before, the account's owner is emailed. The key's very earliest network is the one exception, since nothing has changed yet.
- Requests are limited per key and per account, and repeated attempts with keys that do not exist are limited per network before our database is asked.
- Recovery links are single-use, expire after an hour, and are stored only as a hash. They are kept out of our logs, and so are query strings.
- No API key is issued to an email address that has not been verified. Sign-in by email is a link that works once, expires after fifteen minutes and is stored only as a hash; with Google or GitHub we accept only an address the provider says is verified, and an existing account is connected to one only by somebody already signed in to it, never because an address matches.
- The console's session cookie is HttpOnly, Secure and SameSite. A session opened with a key ends the moment its key is revoked; a signed-in person's session also ends on our side when they sign out, so a copy of the cookie no longer opens anything. Its forms, sign-in and sign-up refuse requests from other sites.
- The MCP server at
https://agentbill.dev/mcpanswers only a request carrying an API key or an access token it issued for itself. An app connects only after you approve it on a page that names it, says where your browser goes next and what it will be able to do, and you can disconnect it in the console. Its codes and tokens are stored only as a hash, an access token lasts an hour, and a refresh token that is used twice ends the connection. No MCP tool can create or show a key, or change your plan or billing. - An alert webhook goes only to an https URL on a public address, checked when you save it and again when we send; it is signed, and a redirect is never followed.
- Payments are handled by Polar. We never see a card number. Polar's notifications are verified by signature, acted on once, and only a paid order or an active subscription for one of our plans changes a plan.
If anything on this page is no longer true, that is a bug. Write to hello@agentbill.dev if you find one.