Errors come back as { "error": "…", "code"?: "…", …details }. The sentence says what happened and what to do; the code is stable for programs. Nothing is charged for a refused request; the one exception is an AI model that used its whole token budget without answering (budget_exhausted, see AI Gateway).
Status codes
| Status | Meaning |
|---|---|
| 400 | The request is wrong: a missing field, a bad value, or a SQL mistake (the database's own message, cleaned). |
| 401 | No credential, or it is invalid, revoked or past its expiry date. |
| 402 | Money or plan: a plan count or usage allowance reached, the Balance or AI credit too low, or the spending limit reached. |
| 403 | The credential may not do this: role, read-only key, project key, a paused key, owner-only money action, a suspended or unverified account, or a workspace that requires two-factor sign-in of a member who has not turned it on. |
| 404 | Not found — or it belongs to another workspace. |
| 409 | The resource is in the wrong state: still being created, disabled, another deploy of it running, or a delete blocked by money or work in flight. |
| 410 | A database that no longer exists on the edge, or a resumable upload that expired. |
| 413 | Too large: an upload over the size cap, or AI input over 200,000 characters. |
| 422 | Refused on purpose: a suppressed email address, a phone app sent to deploy, or an AI answer that used the whole token budget with nothing to show. |
| 429 | Too many requests. Wait Retry-After seconds. |
| 501 | Not available yet (for example, a Postgres database, an image media asset, or a test checkout before test mode is on). |
| 502 | Something upstream failed: a delivery provider refused, or a delete could not finish (it is safe to retry). |
| 503 | Temporarily unavailable. Retry after the Retry-After header when there is one. |
Codes
| Code | Status | Meaning and fields |
|---|---|---|
| plan_limit | 402 / 429 | A plan count, the email quota, a memory quota, the users of a sign-in pool or the vectors in a collection is reached, or the plan does not include the service (required_plan names the plan that does). limit, current; a memory quota adds limit_name (memory_items_per_store or memory_bytes_per_workspace), and the users or vectors feature (pool_users or vectors_per_collection); the public sign-in endpoints answer pool_users without limit or current. As a 429: more agent runs at once than the plan allows. |
| usage_limit | 402 | Hobby: the plan's file storage, database storage or build minutes are used up, so uploads, writes that add data or deploys are refused. metric, limit, current, required_plan. |
| usage_paused | 402 | A paid plan past its included usage that cannot pay for more: the paying workspace's Balance is empty, or the last charge did not fit that Balance or its monthly spending limit. metric, limit, current, balance_cents (the paying workspace's), add_funds_url, and shared_workspaces when the plan covers several workspaces. |
| insufficient_balance | 402 | The Balance does not cover a purchase. price_usd, balance_usd, shortfall_usd, add_funds_url, card_on_file. In a workspace another workspace's plan covers, the Balance short is that workspace's own, which pays for its domains (balance_scope: "own"): add_funds_url and add_funds_api add to it. |
| ai_credits_exhausted | 402 | Included AI and the Balance are used up. available_usd, resets_on, buy_url. |
| ai_credits_insufficient | 402 | Some AI credit is left, but less than this request's estimate. Lower max_tokens, shorten the prompt, choose a cheaper model or add funds. |
| ai_request_too_large | 402 | One AI request may reserve at most $2 on hobby, $10 on pro, $10 on team, $10 on scale. Shorten it, cap the output or split the work. |
| spend_limit_reached | 402 | The monthly spending limit of the workspace whose Balance pays (on a paid plan, the paying workspace's; for a domain, the buying workspace's own, limit_scope: "own" where another workspace's plan covers it) would be passed. spend_limit_usd, spent_this_month_usd, limits_url. |
| email_unverified | 402 / 403 | The workspace owner's email must be verified (Hobby vendor AI models, live payment links). |
| payouts_require_owner | 403 | Withdrawals, refunds, payout setup and adding or removing the card on file need the owner signed in to the dashboard. |
| dashboard_only | 403 / 409 | Only the person signed in to the dashboard can make this change: changing, cancelling or resuming a paid plan, the billing portal, the spending limit and alerts, adding funds with the card on file, switching auto-reload on, making someone an owner, leaving a workspace and deleting one. An AI connector or another app whose role could make the change gets this instead (one whose role could not is told so), and nothing was changed. dashboard_url is the page to do it on: hand it to the person. As a 409: a checkout link asked for a workspace that already pays for a plan, since changing that plan charges the card on file at once. Every call is listed in Billing → What needs the dashboard. |
| card_requires_owner | 402 / 403 | Paying with the card on file (payWithCard on a domain purchase, renewal or transfer) or letting renewals fall back to it (autoRenewCard, cardBackup) needs the workspace owner signed in to the dashboard. An AI connector or API key gets this instead (403 for cardBackup), and nothing was charged or changed. dashboard_url is the Domains page, where the owner can do it; or pay from the Balance (a purchase, renewal or transfer that asked for the card also carries add_funds_url; in a workspace another workspace's plan covers, that is this workspace's own balance, which pays for its domains, with balance_scope own). See Domains → Who pays, and how. |
| key_expired | 401 | The API key is past the expiry date an owner or admin gave it. Give it a new date, or use a new key. |
| key_paused | 403 | An owner or admin paused the API key. Resuming it in Settings → API access makes it work again. |
| account_suspended / key_creator_suspended | 403 | Harakumo suspended the account making the request, or the account that created the API key. Nothing has been deleted; write to support. |
| workspace_requires_mfa | 403 | The workspace requires two-factor sign-in of every member, and yours is off. Turn it on in Settings → Account; your own account pages stay open. API keys are not affected. |
| ai_gateway_off / ai_model_off | 403 | The AI Gateway, or this model, is switched off for the workspace (Dashboard → AI Gateway). |
| origin_not_allowed | 403 | An auth pool refused a browser Origin that is not on its list. |
| browser_access_off | 403 | An auth pool's browser sign-in is turned off. |
| mail_paused / mail_suspended | 403 | Sending is paused by an admin, or suspended by the platform. |
| invalid_domain | 400 | A sending domain that is not a real domain you could own (a single word, a test domain, a Harakumo name). |
| invalid_from / invalid_reply_to | 400 | A From or Reply-To address that is not allowed; the sentence says which rule. |
| sender_name / reserved_subject | 400 | A sender name or subject that breaks the rules in Mail → Sender names; the sentence says which. |
| from_domain_not_added | 400 | A from address on a domain that is not a sending domain of this workspace. Add it first. |
| from_not_project_domain | 403 | A key limited to one project asked to send from, or to set its project's From address on, a domain that is not its project's own. It sends from its project's sending domain or the shared no-reply; a person moves the project to a new domain on the Mail page. |
| sender_name_other_project | 403 | A key limited to one project asked to send under, or to save as its project's sender name, another project's name or sender name, or a close variant of one. Leave fromName out to use its own project's name. |
| domain_claimed_elsewhere / domain_taken | 409 | The domain belongs to another workspace on Harakumo. |
| dns_hosting_off | 409 | A sending domain bought on Harakumo whose DNS hosting is off. platform_option says how to turn it on. |
| mail_not_ready | 409 | An auth pool cannot email codes: the app has no sender name recipients may see. Set one on the Mail page. |
| payments_disabled | 409 | The payment account is disabled; re-enable it on the project. |
| deletion_blocked | 409 | A delete is refused. blockers[] has one sentence per reason (earnings, a withdrawal, an open checkout, a running deploy, bought domains). |
| deployment_serving | 409 | The deployment cannot be deleted on its own: the project serves it, or it still answers a custom domain. Deploy a new version (or connect the domain) first, or delete the project. |
| deploy_in_progress | 409 | Another deploy, rollback or delete of this function is running. Try again when it finishes. |
| not_a_static_site | 409 | A site password or the app-link files were asked for on a project that is not a static site: a server app or an edge program serves its own paths. |
| confirm_price / price_moved | 409 | A domain renewal above $100 a year needs expectedPriceCents at or above its price (confirm_price), or the price is now above the one you sent (price_moved). price_cents is the price; nothing was charged. |
| over_cap | 403 | A domain renewal above $100 a year asked for by anyone but the workspace owner in the dashboard: the API, an AI connector or another role. dashboard_url is the Domains page where the owner renews it; nothing was charged. |
| reserved_name | 400 | A project name whose address would start with hk- or harakumo, which Harakumo keeps for its own services. Choose another name. |
| test_mode_off | 501 | A test checkout was asked for, and payments test mode is not on yet. |
| upload_expired | 410 | A resumable upload more than six days old. Start a new one. |
| confirm_forfeit_required | 409 | Deleting a workspace would give up a positive Balance; send confirmForfeit: true to accept. |
| size_required | 400 | An upload link asked for maxBytes without size, the file's exact length: a link can only be locked to one exact length, so maxBytes alone would limit nothing. Send both. max_bytes is the limit that applies; no link was made. |
| file_too_large | 413 | An upload link's size is over maxBytes or the plan's per-file cap (1 GB on Hobby, 5 GB on the other plans). max_bytes is the limit that refused it; no link was made. |
| suppressed | 422 | The recipient is on the suppression list. |
| phone_app | 422 | A deploy of an Expo, React Native, Flutter, iOS or Android app with nothing else to deploy (no website, no web build) and no start script that runs a server (only a phone development server such as expo start, or none). The app ships through the App Store and Google Play instead. Nothing was built. app names the kind; docs links Use Harakumo from a phone app; the sentence says how to deploy the app's companion website. |
| flutter_web_build | 422 | A deploy whose build script runs flutter build web. The build machines have no Flutter toolchain, so it was not started and nothing was built. Run flutter build web on your machine and upload the build/web folder, which deploys as a static site. |
| budget_exhausted | 422 | The model used its whole max_tokens budget without an answer. Those tokens are charged; raise max_tokens. |
| ai_request_refused | 400 / 413 | The model refused the request itself (a parameter, or its size). It was not sent to other models; nothing was charged. |
| rate_limited | 429 | A rate limit. retry_after; API quotas add limit and window_seconds. |
| ai_rate_limited | 429 | The AI per-minute limit. limit, window_seconds, retry_after_seconds. |
| daily_cap | 429 | The workspace's own daily email cap. |
| code_limit | 429 | An auth pool's codes asked for from the browser reached the pool's limit or the workspace's share of the email quota. Retry-After. |
| zone_limit / account_zone_limit | 429 | New DNS setups for today are used up: 10 per workspace, or 20 across the workspaces one person owns (that one carries retry_after). |
| delivery_failed | 502 | The mail provider refused the message; message has the reason. |
| provider_error | 502 | A sending domain could not be set up; nothing was saved. Try again in a minute. |
| ai_unavailable / ai_provider_error | 502 | No model could answer right now; nothing was charged. harakumo-1 fails over between vendors. |
| zone_capacity | 503 | Harakumo is not accepting new domain setups for a while; nothing was set up. Buying a domain is not affected. |
| teardown_incomplete | 502 | A delete left some resources behind; the project is kept as teardown_failed. Delete again to retry. |
| processor_error | varies | The payment processor refused; the sentence says why. |
Handling errors in code
SDK
import Harakumo, { HarakumoError } from '@harakumo/sdk';
try {
await hk.databases.query(dbId, 'SELECT 1');
} catch (err) {
if (err instanceof HarakumoError) {
if (err.status === 429) { /* wait err.body.retry_after seconds, then retry */ }
if (err.body?.code === 'plan_limit') { /* show err.message to the person */ }
}
throw err;
}