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 or revoked. |
| 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, owner-only money action, a paused or unverified account. |
| 404 | Not found — or it belongs to another workspace. |
| 409 | The resource is in the wrong state: still being created, disabled, or a delete blocked by money or work in flight. |
| 410 | A database that no longer exists on the edge. |
| 413 | Too large: an upload over the size cap, or AI input over 200,000 characters. |
| 422 | Refused on purpose: a suppressed email address, 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 or an image media asset). |
| 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 or a memory quota 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). 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 workspace past its included usage that cannot pay for more: the Balance is empty, or the last charge did not fit the Balance or the monthly spending limit. metric, limit, current, balance_cents, add_funds_url. |
| insufficient_balance | 402 | The Balance does not cover a purchase. price_usd, balance_usd, shortfall_usd, add_funds_url, card_on_file. |
| 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 enterprise. Shorten it, cap the output or split the work. |
| spend_limit_reached | 402 | The workspace's monthly spending limit 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. |
| 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. |
| 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). |
| confirm_forfeit_required | 409 | Deleting a workspace would give up a positive Balance; send confirmForfeit: true to accept. |
| suppressed | 422 | The recipient is on the suppression list. |
| 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;
}