All API failures return HTTP 422 with a JSON validation-style body. The reading itself never fails synchronously — failures during AI processing arrive on the reading.failed webhook.
Synchronous errors are always 422 with the same shape: an errors object whose keys are field names and whose values are arrays of human-readable messages. Multiple fields can fail in a single response.
{"errors": {"<field>": ["<message>", "<message>"]}}
authorizationAuthorization header is absent.subscriptionThe account linked to the API key has no active subscription.limitYour plan's monthly request limit (e.g. 250 / 500) has been reached.imageThe image field is missing from the multipart body.authorization— Missing or invalid API keyCommon causes
{"errors": {"authorization": ["Invalid or inactive API key"]}}
subscription— No active subscriptionCommon causes
{"errors": {"subscription": ["No active subscription"]}}
limit— Monthly request limit reachedCommon causes
{"errors": {"limit": ["Monthly request limit reached"]}}
image— Image field invalidCommon causes
{"errors": {"image": ["Image must not exceed 10MB"]}}
Once a request is accepted (HTTP 200 + reference_id), AI errors no longer appear over HTTP. Instead, a reading.failed webhook fires with the same reference_id so you can mark the record as failed on your side.
{"event": "reading.failed","timestamp": "2026-04-28T14:32:02.318Z","data": {"reference_id": "9c7d3f2a-1e4b-4d5a-9b6c-1f2e3d4c5b6a","api_key_id": 17,"reason": "AI processing error"}}
For HTTP errors, check res.ok and read the errors object. For asynchronous AI failures, your webhook handler should treat reading.failed as a terminal state for that reference_id.
const res = await fetch("https://www.aquameter.site/api/meter-reading", {method: "POST",headers: { Authorization: `Bearer ${apiKey}` },body: formData,});if (!res.ok) {const { errors } = await res.json();// errors is { authorization?: string[], subscription?: string[],// limit?: string[], image?: string[] }const firstField = Object.keys(errors)[0];throw new Error(`${firstField}: ${errors[firstField][0]}`);}const { reference_id } = await res.json();// Save reference_id; the reading will arrive on your webhook.