Skip to content

Errors

All errors come back in the same envelope. Build your logic on error.code; message is in English and may change, so it is not suitable for showing to users.

{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid calculation input",
"details": { "fields": [{ "field": "amount", "reason": "SCALE_EXCEEDED", "message": "amount allows at most 2 fractional digits" }] }
},
"requestId": "8a3e7260-dcb6-4227-acdb-88b2a07774e8"
}

requestId is the same as the X-Request-Id header. Include this value when asking for support.

Code HTTP When Retry?
VALIDATION_ERROR 422 (malformed JSON: 400, content type: 415) Parameter/body schema or calculation input error. No, fix the request.
NOT_FOUND 404 The tool does not exist, is not published or is not in the requested language; unknown address. No.
CONFLICT 409 details.reason = "VERSION_OUTDATED": the version you sent is no longer live. Fetch the definition again and retry.
CALCULATION_ERROR 422 / 500 422: the result is undefined for these inputs. 500: the tool temporarily cannot be calculated. 422: no. 500: after a while.
RATE_UNAVAILABLE 422 No rate is in force on the asOf date, or the rate is unavailable. Change the date.
PAYLOAD_TOO_LARGE 413 The body exceeds 16 KB. No.
RATE_LIMITED 429 The rate limit was exceeded. Yes, after waiting Retry-After.
TIMEOUT 504 The request could not be completed within 15 s. Yes (with backoff).
SERVICE_UNAVAILABLE 503 The service is temporarily unavailable. Yes (with backoff).
MAINTENANCE 503 The tool is temporarily under maintenance. Yes, after Retry-After.
INTERNAL_ERROR 500 Unexpected error; details only in server logs. Yes, a few times; if it persists, report it with the requestId.

Parameter or body schema: details.issues[]

Section titled “Parameter or body schema: details.issues[]”
{ "issues": [{ "path": "inputs.amount", "message": "Invalid input", "code": "invalid_union" }] }

path is the path of the faulty field, and code is the schema validator’s code (invalid_type, unrecognized_keys…). For an invalid asOf, details.field = "asOf" is returned.

{ "fields": [{ "field": "vatRateOption", "reason": "MISSING_INPUT", "message": "vatRateOption is required" }] }
reason Meaning
MISSING_INPUT A required field was not sent.
NULL_INPUT The value is null.
INVALID_INPUT The value is invalid (e.g. an enum value that is not in the definition).
TYPE_MISMATCH The type does not match.
OUT_OF_RANGE Outside min/max.
SCALE_EXCEEDED Too many decimal places.
UNKNOWN_INPUT A field that is not in the definition.

To show these errors to the user per field, match fields[].field to the field in your form; generate the text in your own language.

For 422 CALCULATION_ERROR: DIVISION_BY_ZERO, CALCULATION_OVERFLOW, INVALID_DATE_OPERATION, INVALID_ARGUMENT, NO_SOLUTION. The input combination is mathematically meaningless; ask the user to change the value.

{ "error": { "code": "RATE_UNAVAILABLE", "message": "No rate is in force for the requested date", "details": { "rates": ["tr.vat.standard"], "asOf": "1990-01-01" } }, "requestId": "…" }

It comes with the Retry-After header (seconds) and details.retryAfterSeconds. Details: Rate limits.

  1. Do not retry 4xx (except 429 and 409): without fixing the request, it gives the same result.
  2. 429 and 503 MAINTENANCE: wait for Retry-After.
  3. 5xx, 504 and network errors: try 2–3 times with exponential backoff and randomness (jitter).
  4. Always log the requestId.
async function calculate(key, body, attempt = 0) {
const response = await fetch(`https://api.mirac-ozdemir.com/v1/public/tools/by-key/${key}/calculate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
signal: AbortSignal.timeout(20_000),
});
if (response.ok) return response.json();
const { error, requestId } = await response.json().catch(() => ({ error: { code: 'INTERNAL_ERROR' } }));
const retryAfter = Number(response.headers.get('Retry-After'));
const retryable = response.status === 429 || response.status >= 500;
if (retryable && attempt < 3) {
const wait = retryAfter > 0 ? retryAfter * 1000 : 2 ** attempt * 500 + Math.random() * 250;
await new Promise((resolve) => setTimeout(resolve, wait));
return calculate(key, body, attempt + 1);
}
throw Object.assign(new Error(error.code), { code: error.code, details: error.details, requestId });
}