Error codes

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.

The error model

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.

JSON — error envelope
{
"errors": {
"<field>": ["<message>", "<message>"]
}
}

Summary

StatusFieldWhen
422authorizationAuthorization header is absent.
422subscriptionThe account linked to the API key has no active subscription.
422limitYour plan's monthly request limit (e.g. 250 / 500) has been reached.
422imageThe image field is missing from the multipart body.

422authorizationMissing or invalid API key

Common causes

  • Authorization header is absent.
  • Header format is wrong (must be Bearer <space> <token>).
  • API key does not exist, has been deleted, or is set to Inactive.
How to fix: Check the header format and verify the key is active on the API Keys page.
JSON response
{
"errors": {
"authorization": ["Invalid or inactive API key"]
}
}

422subscriptionNo active subscription

Common causes

  • The account linked to the API key has no active subscription.
  • The subscription was cancelled or has expired.
How to fix: Subscribe (or resubscribe) on the Billing page.
JSON response
{
"errors": {
"subscription": ["No active subscription"]
}
}

422limitMonthly request limit reached

Common causes

  • Your plan's monthly request limit (e.g. 250 / 500) has been reached.
How to fix: Upgrade to a higher plan or wait until the next calendar month.
JSON response
{
"errors": {
"limit": ["Monthly request limit reached"]
}
}

422imageImage field invalid

Common causes

  • The image field is missing from the multipart body.
  • The uploaded file is not an image (validation rule "image").
  • The uploaded file exceeds 10 MB.
How to fix: Send a JPEG, PNG, or WEBP under 10 MB in the 'image' field.
JSON response
{
"errors": {
"image": ["Image must not exceed 10MB"]
}
}

Asynchronous failures

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.

JSON — reading.failed webhook
{
"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"
}
}

Handling errors in code

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.

JavaScript
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.