Errors
Every error from /v1 is one shape, application/problem+json (RFC 9457):
{
"type": "https://docs.hopforms.com/api/errors#insufficient_scope",
"title": "Forbidden",
"status": 403,
"code": "insufficient_scope",
"detail": "This API key does not hold the flows:read scope.",
"request_id": "req_example_0001"
}
Branch on code. It is stable for the life of /v1; detail is a sentence for a person and its wording may change. type links to the code's section on this page. Validation failures add errors, a list of {field, code, detail} naming what was wrong. Quote request_id when asking for help.
Codes are only ever added. The list below is complete for the current build. Codes for endpoints not yet published are listed ahead of their release, so a client can branch on them from day one.
malformed_request
400. The request body could not be read as JSON. Check the body is valid JSON and the Content-Type is application/json.
unknown_parameter
400. A query parameter is not one the endpoint accepts. Where an endpoint declares parameters, errors names the offending one and suggests the closest known one.
invalid_parameter
400. A query parameter you named correctly carries a value the endpoint will not accept — a limit above the maximum, a timestamp that is not RFC 3339, or a cursor that did not come from this endpoint's own next_cursor. errors names the parameter. Note that limit above the maximum is an error rather than a silent clamp, so a page size you did not ask for can never be mistaken for the end of the data.
unauthorized
401. No usable key: missing, malformed, unknown, revoked, or expired. The body is identical in every case. Mint a new key on your organisation's API page and send it as Authorization: Bearer hop_sk_....
insufficient_scope
403. The key is valid but does not hold the permission this endpoint needs. The WWW-Authenticate header names the scope; edit the key's permissions or mint one that has it.
not_found
404. Nothing at that path for this key: the resource does not exist, or belongs to another organisation. The two cases are deliberately indistinguishable.
in_flight
409. A request with the same Idempotency-Key is still being processed. Wait for it to finish, then read the result rather than sending again.
not_in_draft_state
409. Publish refused: the version is not a draft.
draft_not_yet_uploaded
409. Publish refused: WhatsApp has not finished receiving the draft. Poll the version until publishable is true, then publish.
not_published
409. Unpublish refused: the flow is not currently published.
must_unpublish_first
409. Delete refused: the flow is published. Unpublish it, then delete.
register_in_use
409. The record action cannot be removed while an option list still reads the register it provisioned; the detail says which flow, or that it is this flow's own version. Remove that binding in the flow editor, then retry.
code_conflict
409. Making the flow public would claim a keyword another organisation already holds. Change the keyword in the app, then retry.
plan_cap_reached
409. Publish refused: the organisation's plan has reached its published-flow cap. detail names the cap. Email billing@hopforms.com to raise the cap.
request_too_large
413. The body is over this endpoint's limit. On endpoints that accept a body, detail states the bytes sent and the bytes allowed.
unsupported_media_type
415. The Content-Type is not one this endpoint accepts. Endpoints take JSON unless their reference page says multipart.
validation_failed
422. The request has the right shape but a wrong value, or a body key the endpoint does not know. errors lists each field with its own code and detail. On the record-action write, errors[].code is one of unknown, not_register_bound, not_applicable, no_processed_version and not_a_flow_register; the operation's description says when each applies.
Elsewhere errors[].code is invalid for a value the field refuses, unknown for a name the endpoint or the flow does not have, invalid_format for a malformed idempotency key, conversion_failed for a spreadsheet that would not convert, and, on a body that fails the schema, the validator's own reason (invalid_type, missing_field, unexpected_field, min_length). That last set is the validator's and grows with it; match on field first and code second.
unknown_answer_field
422. A submission names an answer field that is not in the flow's published Flow JSON. errors suggests the closest known field and lists the known ones.
conversion_failed
422. The XLSForm could not be converted. errors is per cell; warnings may also be present and does not fail the request.
idempotency_key_missing
422. This write requires an Idempotency-Key header. Send a unique value per intended operation and reuse it only when retrying that same operation.
idempotency_key_reuse
422. The Idempotency-Key was already used with a different payload. Use a fresh key for a new operation.
no_published_version
422. The flow has no published version, so there is no schema to validate against or submit to. Publish a version first.
no_number_configured
422. The organisation has no WhatsApp number to attach the flow to. Configure one in the app, or pass number_id.
record_not_resolved
422. A submission in update mode could not be matched to an existing record. Nothing was written; retrying with the same Idempotency-Key is safe once the record exists.
rate_limited
429. Over a bucket. Obey Retry-After (seconds); detail names the bucket. RateLimit-* headers on every response the API answers show how much room is left.
internal_error
500. Something failed on Hop's side. The body carries the standard fields and a request_id, and nothing about the failure; quote the id when reporting the problem.
converter_unavailable
502. The XLSForm converter could not run. Nothing was created; retry, and quote request_id if it persists.
storage_unavailable
502. The source form could not be stored. Nothing was created; retry with the same Idempotency-Key.
converter_timeout
504. The XLSForm converter did not finish inside its budget. Nothing was created; try a smaller form or retry.