Opening a Hushscript account used to need a person, at least once. That is no
longer the whole story. A payment-first flow lets an agent open its own
account, fund it, and run it, with no human account holder anywhere in the
loop. /developers and /mcp document the two interfaces this account then
uses; this page is the walkthrough for getting one.
Sign up
POST /v1/agent/accounts opens a headless signup. There is no account yet,
no cookie, and no token: just a pending purchase.
curl -X POST https://api.hushscript.com/v1/agent/accounts \
-H "Content-Type: application/json" \
-d '{
"contact_email": "ops@example-agent.dev",
"pack_id": "300min",
"accept_terms_version": "2026-08-01",
"agent": {
"name": "research-crawler",
"platform": "langgraph",
"contact_url": "https://example-agent.dev/bots/research-crawler"
}
}'
{
"signup_id": "hsr1:eu:4c3a1f9e7b2d4e6f8a0c1b2d3e4f5061",
"claim_secret": "cs_9f3d2a1b7e6c4f5a8b9d0e1f2a3b4c5d",
"checkout_url": "https://checkout.stripe.com/c/pay/cs_test_a1B2c3D4e5F6",
"pack": "300min",
"amount": 599,
"currency": "usd",
"expires_at": 1789112400
}
contact_email is for receipts and notices only, never a login. pack_id is
one of 45min, 300min, 900min, 1800min, 6000min; never send amount
or currency yourselves, both are derived from the pack on the server.
Only the two smallest packs, 45min and 300min, can be bought at signup. A
larger pack_id is refused, and the refusal carries details.allowed_packs
listing what this account may buy right now, so a caller can retry without
guessing. The rest of the ladder unlocks with age, not with volume: see
What a new account may buy below.
accept_terms_version must match the server’s current policy version exactly,
or the call fails with policy_version_stale, which names the current value
in its body. An optional data_region hint is honored only when it agrees
with the region your network location already implies. An optional
dry_run: true validates everything with no Stripe session and no charge;
its signup_id is prefixed dry_ and can never be claimed.
claim_secret is shown exactly once, in this response. Hushscript stores
only its hash. Losing it before the claim call means losing the signup: there
is no recovery route for a claim secret, and the escrowed payment is refunded
by the sweep described below, not handed back through a lookup.
Complete the checkout in your own browser
checkout_url is a hosted Stripe Checkout page. It is the only step in this
entire flow that needs a browser, and it does not need to be a person’s
browser: an agent can drive it with its own automation (fill the card fields,
submit, follow the redirect). Hushscript never sees the card details either
way; Stripe does. The window to finish it and then claim is 30 minutes, the
expires_at above.
A signup that gets paid but is never claimed is refunded automatically by an hourly sweep, roughly an hour after that 30 minute window closes. The refund is full, because minutes are only credited to a balance at claim time, so an unclaimed signup never had any to consume.
Claim the account
POST /v1/agent/accounts/{signup_id}/claim turns a paid, unclaimed signup
into a real account, in one call.
curl -X POST https://api.hushscript.com/v1/agent/accounts/hsr1:eu:4c3a1f9e7b2d4e6f8a0c1b2d3e4f5061/claim \
-H "Content-Type: application/json" \
-d '{"claim_secret": "cs_9f3d2a1b7e6c4f5a8b9d0e1f2a3b4c5d"}'
{
"user_id": "usr_7d1a2b3c4d5e6f708192a3b4c5d6e7f8",
"pat": "hsr1:eu:pat.k3n2j1.8f7e6d5c4b3a29180716253443526170",
"scopes": [
"transcripts:read",
"transcripts:write",
"transcribe",
"account:read",
"export",
"billing",
"pat:rotate"
],
"balance_seconds": 18000
}
Claiming is single use. It creates exactly one account, with a synthetic,
unresolvable login identity behind it, grants the paid pack exactly once even
if the request is retried, and mints exactly one PAT: the pat value above,
also shown exactly once.
A wrong secret, an unknown signup_id, a signup already claimed, a refunded
one, and an expired one all answer the identical agent_claim_invalid error.
There is no way for a caller to tell those apart from the response, on
purpose. Only once the secret itself checks out does payment state come into
it: a Checkout Session that has not settled yet answers wrong_state
instead.
The balance_seconds above is the whole starting balance. Agent accounts get
no welcome bonus and no free minutes from card verification the way a
person’s first account does; they start from nothing but the pack they paid
for. That is deliberate, not a gap: the account was already paid for before
it existed.
Buy more minutes
From here the account behaves like any other, over the same /v1/ surface
/developers documents in full.
curl -X POST https://api.hushscript.com/v1/billing/purchase \
-H "Authorization: Bearer hsr1:eu:pat.k3n2j1.8f7e6d5c4b3a29180716253443526170" \
-H "Content-Type: application/json" \
-d '{
"pack": "300min",
"quick_payment_method_id": "pm_1PqR2sT3uV4wX5yZ",
"idempotency_key": "purchase-2026-09-11-01"
}'
{
"balance_seconds": 36000
}
This needs the billing scope, which the claim response above already
granted, and it shares a tighter 60 requests a minute per account with the
card-saving route, ahead of the Stripe call either one makes.
What a new account may buy
Pack size unlocks on the age of settled payments, not on how much an account spends. A purchase counts toward the next tier only once it is 7 days past its own payment date, so a brand new account cannot reach the large packs by buying quickly.
| Settled payments aged past 7 days | Packs it may buy |
|---|---|
| Nothing yet, including a fresh claim | 45min, 300min |
| At least 45 minutes’ worth | adds 900min, 1800min |
| At least 15 hours’ worth | adds 6000min |
A refused purchase names the current set in details.allowed_packs rather
than failing blind. The signup call applies the first row of that table, which
is why the walkthrough above buys 300min and not something larger. Separately
from the ladder, a rolling 24 hour burn cap limits how fast balance that has
not yet aged past the dispute window can be spent.
Transcribe
Uploading and transcribing is the same multipart flow as any other account.
curl -X POST https://api.hushscript.com/v1/uploads \
-H "Authorization: Bearer hsr1:eu:pat.k3n2j1.8f7e6d5c4b3a29180716253443526170" \
-H "Content-Type: application/json" \
-d '{
"size_bytes": 48213504,
"duration_seconds": 1860,
"title": "weekly-standup-2026-09-11",
"idempotency_key": "upload-2026-09-11-01"
}'
{
"job_id": "job_3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d",
"part_urls": [
{ "n": 1, "url": "https://r2.hushscript.com/uploads/job_3a2b1c0d.../part-1?X-Amz-Signature=..." }
]
}
PUT each part to its presigned URL, then POST the same job’s /complete
with the n and etag of every part sent that way. Then poll the job:
curl https://api.hushscript.com/v1/jobs/job_3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d \
-H "Authorization: Bearer hsr1:eu:pat.k3n2j1.8f7e6d5c4b3a29180716253443526170"
{
"id": "job_3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d",
"state": "done",
"transcript_id": "trs_6f5e4d3c2b1a0908f7e6d5c4b3a29180"
}
curl https://api.hushscript.com/v1/transcripts/trs_6f5e4d3c2b1a0908f7e6d5c4b3a29180 \
-H "Authorization: Bearer hsr1:eu:pat.k3n2j1.8f7e6d5c4b3a29180716253443526170"
{
"id": "trs_6f5e4d3c2b1a0908f7e6d5c4b3a29180",
"language": "en",
"duration_seconds": 1860,
"body": "..."
}
Rotate the credential
A machine account has no password and no usable mailbox, so there is no “forgot your password” path if a PAT leaks or simply needs replacing. Rotation is that path.
curl -X POST https://api.hushscript.com/v1/agent/credentials/rotate \
-H "Authorization: Bearer hsr1:eu:pat.k3n2j1.8f7e6d5c4b3a29180716253443526170"
{
"pat": "hsr1:eu:pat.p9q8r7.1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d",
"pat_id": "p9q8r7",
"scopes": [
"transcripts:read",
"transcripts:write",
"transcribe",
"account:read",
"export",
"billing",
"pat:rotate"
],
"expires_at": null
}
The call takes no body. It atomically mints a successor and revokes the token that authenticated the request, so the old PAT fails on the very next call made with it. The successor keeps the predecessor’s scopes, billing permission, name, client label, and expiry policy. This route touches nothing but the caller’s own token: it cannot list, mint, or revoke any other credential on the account. It is the only credential-recovery path a machine account has, so rotate before the old token is discarded, not after.
Errors
| Status | Code | Slug | Meaning |
|---|---|---|---|
| 401 | 2000 | unauthorized |
Missing or invalid bearer token |
| 403 | 2023 | pat_scope_missing |
The token lacks the scope this call needs, or billing is off for the account |
| 403 | 4021 | pat_purchase_requires_app |
No saved card was named, or the charge was declined; nothing was charged |
| 403 | 4023 | pat_purchase_requires_authentication |
The card issuer wants a challenge the caller cannot complete; the card itself is fine |
| 402 | 3001 | insufficient_balance |
Not enough balance to admit the job |
| 429 | 5000 | rate_limited |
Too many requests; wait for Retry-After |
These four are specific to agent accounts and are never reached by a human one:
| Status | Code | Slug | Meaning |
|---|---|---|---|
| 409 | 4035 | agent_signup_pending |
A signup for this contact_email is still open. Wait for it to be claimed, refunded or expired, or use a different address |
| 401 | 4036 | agent_claim_invalid |
Wrong secret, unknown signup, already claimed, refunded, or expired. Identical on purpose |
| 403 | 4037 | agent_pack_locked |
This account’s tier does not allow that pack yet. Carries details.allowed_packs; retry with one of those |
| 429 | 4038 | agent_purchase_capped |
3 purchases, paid or declined, in the trailing 24 hours. Counted per account across every credential it has held, so rotating does not reset it. Carries details.retry_after_seconds |
Once the claim secret itself checks out, an unpaid Checkout Session answers
wrong_state (4001) rather than agent_claim_invalid. The full catalog is the
OpenAPI document.
Limits
Calls are limited to 1,200 a minute. For an agent account that budget belongs to the account, not to the individual token: every credential in the rotation lineage draws on the same one, so rotating a PAT does not hand the successor a fresh allowance. The two billing writes, saving a card and buying a pack, share a tighter 60 a minute per account, ahead of the Stripe call either one makes. The 24 hour purchase cap above is counted the same way, per account rather than per credential.
Learn more
This page covers the account lifecycle: opening one, funding it, and keeping its credential alive. For everything the account can then do, see the REST API, including the full OpenAPI document, or the MCP server for the same account over tools instead of raw HTTP.