Errors
Problem details, and why a missing fact is never one.
An error is an RFC 9457 problem document, sent as application/problem+json:
{
"type": "https://docs.gethimmel.com/errors/invalid-fact",
"title": "Invalid fact",
"status": 422,
"detail": "booking_date must be a date",
"instance": "/v1/check",
"errors": [ { "fact": "booking_date", "code": "type" } ]
}
type names the problem, and is the thing to branch on: its address leads to the code's own row of the table below. errors names each field that was wrong, when there is one to name. Every response carries an X-Request-Id header; quote it when you ask us about a failed call.
The service's health check, GET /healthz, is the one answer that is no problem document: it says 200 and ok, or 503 and degraded with which part failed, in the same shape, for a monitor to read.
Not errors
A missing fact, an open question about who a recipient is, and a rule your rule owner has not confirmed are answers, never errors. They come back with HTTP 200 as undetermined_missing_facts or not_covered, because they are exactly what the check is about. So is a fact sent as null where a rule needs its value: it is named in missing, as a fact left out is. A 422 means the request was not in the documented shape: a fact of the wrong type, a body that does not parse, a query parameter the endpoint does not take. It never means "we do not know who received this gift".
Codes
| Type | Status | When |
|---|---|---|
invalid-fact | 422 | A fact is not of the type the rule set declares, or is not a fact it takes. An amount is refused in a currency ISO 4217 gives no decimals (currency), with a figure in its own currency or two in one (equivalents), and with a key money does not have (type); a setting's amount, in another currency than a rule set that reads it reads it in, with no figure in that one (currency). A number below zero the rule set does not declare signed (below_zero), and a code of more than 200 characters (format). A recipient that carries a key a customer does not have (unknown_key, the fact naming the key), a description beside an id (described_and_named), a resolution, a kind or a value of another kind (resolution, kind, type), or a value too long (too_long). A person, or the figure an approval was given for, sent with a value by a key made for an agent (not_the_keys): Himmel holds them for the agent |
pii-in-facts | 422 | A fact other than a recipient, a provenance name or value, an actor value, transaction.ref, an execution report's ref or a decision's note_ref looks like an email address or an IBAN - or, on the MCP server, the Himmel-Run or Himmel-On-Behalf-Of header, named in errors[].field |
invalid-request | 422 | The body or a parameter is not in the documented shape. A query parameter the endpoint does not take is refused rather than ignored, on every endpoint: errors[].field names it, with the code unknown_parameter. A reference longer than 200 characters - transaction.ref, a provenance name or value, actor.id or on_behalf_of, a report's ref, a note_ref, the MCP server's Himmel-Run and Himmel-On-Behalf-Of headers - is refused with string_too_long, naming the field. A tool of the MCP server given an argument it does not take refuses it with unknown_argument, rather than pass it on. A body that cannot be read at all is the same type with 400. A check that completes an action is refused where it says the action or a fact of it otherwise than the booking recorded (changed, naming the field, never its value), names another rule set's booking (other_rule_set), or is sent as a proposal (proposed). A check that corrects one is refused the same way where it says the action otherwise - its facts it may change - and where it changes nothing (unchanged) or completes too (both). An agent your account has not registered, named by one of your account's own keys in transaction.actor or given to a new key as its agent, is refused with unknown_agent: register it first |
invalid-policy | 422 | A policy's files do not load, or are not named for your account |
pii-in-policy | 422 | A policy's files hold an email address or an IBAN |
invalid-document | 422 | An uploaded document is not a PDF, DOCX or Markdown file that can be read |
unauthorized | 401 | No key, a malformed header, or a key that is not in use |
insufficient-scope | 403 | The key lacks the scope the endpoint needs; required_scope names it. A key made for an agent gets it too, with the code agent_key, for what your whole account holds and for another agent's record: it reads its own agent's alone |
not-a-member | 403 | Someone signed in to the console whom no account has added yet |
names-withheld | 403 | A search of recipients by name (q) from a caller that is not shown names: every key, and people other than admins and ops |
live-only | 403 | A request acting in test tried to change what your account has decided — its confirmed rules, settings or policies. Those are changed from live |
rule-set-not-enabled | 403 | The rule set is not enabled for your account |
unknown-rule-set | 404 | No rule set of that name |
rule-version-not-found | 404 | The rule set has no such version |
rule-version-not-in-force | 409 | A check named a version other than the one in force on its transaction's date, which answers it: in_force names that one, or is null where none is |
not-found | 404 | Nothing with that id — or, for a mapping, that name — exists for your account; or the path is no path of the API |
method-not-allowed | 405 | The path does not take that method: the Allow header names the ones it does. The MCP server takes POST alone |
idempotency-conflict | 409 | The Idempotency-Key was used with a different body in the last 24 hours |
not-a-proposal | 409 | The execution report is for a transaction that was never proposed, or is already closed. Or a check asks again about an action already taken: send the facts it lacked completing it as taken |
answer-replaced | 409 | A decision was sent to an answer a later one replaced; standing names the answer to decide on. Or a check completes a booking another check already completed or asked about again: transaction_id names the booking to name instead, and standing its answer |
action-not-taken | 409 | A decision was sent about an action that was never taken: it expired, or was reported not done. Or a check completes such an action, or asks about it again. Or a check completes one still being asked about as taken: it is asked about again, sent as proposed. Or such an action is taken back: one still being asked about is reported taken or not instead |
action-taken-back | 409 | A decision was sent about an action taken back after it was taken, or a check completes one or asks about it again: it counts toward nothing, and nothing waits on it |
nothing-to-decide | 409 | A sign-off on an answer that asks no question, or an override of one that decided nothing - it waits on facts, or on your rules. Or an approval or a refusal of a request that no longer waits: request_status says where it stands |
cannot-decide | 403 | The person may not make this decision: only the one a question names signs it off, only an admin overrules, and only the person a request asks approves or refuses it (not_the_approver). Or the key is one made for an agent (agent_key), which records no decision: a decision is a person's - and an approval Himmel asked for is never a key's (a_key) |
not-the-agents-action | 403 | A key made for an agent read, reported on, took back, completed or corrected an action its agent did not take, or read a request about one: it reads and acts on its own agent's actions only, and your account's own keys and your people on any |
agent-switched-off | 403 | The agent was switched off: every request its keys send is refused, and so is a check asking first in its name, until it is switched on again. An action it took is reported as taken, as any is |
cut-off | 403 | The owner of an account you opened for a customer cut you off from it, for good: you act in it no more. Your agents there act on until its owner switches them off |
conflict | 409 | The request conflicts with what is stored - an action taken back is reported taken back at another moment or under another reference; detail says how |
named-in-settings | 409 | A person your settings name from today on — who approves, who signs off — cannot be removed until they name somebody else; settings lists them |
owns-agents | 409 | A person who owns one of your agents, switched on or off, cannot be removed until each has another owner; agents lists them |
last-admin | 409 | The change would leave your account without an admin: make somebody else its admin first |
policy-blocked | 409 | A policy cannot be published; blocking lists why |
body-too-large | 413 | The request's body is larger than the operation takes: a policy's files 5 MB, any other body 1 MB. Refused by its length before any of it is read - or, sent chunked, as it arrives - and nothing was done |
file-too-large | 413 | The file is larger than 20 MB, the most any endpoint takes. Nothing of it was read or kept |
rate-limited | 429 | Too many requests in the last minute - or, opening an account, more accounts in a day than the deployment opens: 50 in all, 5 from one network address, 20 by one builder. Retry-After says in how many seconds the next can be opened, and nothing was opened |
drafting-not-configured | 503 | Drafting from a document is not available in this deployment |
sign-in-unavailable | 503 | Clerk, which signs people in to Himmel, could not be asked just now, or is not set up in this deployment: nothing was sent or changed |
sign-up-closed | 503 | Accounts are not opened here yet - by signing up, or for a customer: the terms in force are a placeholder. Nothing was opened |
rule-set-broken | 500 | A rule set could not be run on an action you asked about. The fault is ours, not your facts'. An action you report as taken is kept instead, answered not_covered with the reason rule_set_broken |
internal | 500 | Something failed on our side |
A 500 is ours to fix, and says so without describing itself; the request id finds it in our logs.