Skip to main content

Errors

Every response outside the 2xx range uses the same JSON envelope. Your client can handle errors from a single format.

Envelope

{
"code": "validation_failed",
"message": "Please review the submitted fields and try again.",
"detail": "Please review the submitted fields and try again.",
"hint": "Check the type and format of each field.",
"details": {
"errors": [
{ "loc": ["body", "email"], "msg": "value is not a valid email address" }
]
},
"debug_id": "8e1f2c3a4b5c6d7e8f90",
"request_id": "8e1f2c3a4b5c6d7e8f90"
}

Fields

FieldTypeDescription
codestringCanonical, machine-readable code (validation_failed, forbidden, etc.). Use this for client logic.
messagestringHuman-readable message, translated according to the x-locale header.
detailstringSame as message, kept for backward compatibility.
hintstring | nullSuggested action for the user/developer. May be null when there is no specific hint.
detailsobjectAdditional failure-specific context (invalid fields, resource IDs, etc.). Structure varies per error.
debug_idstringUnique request identifier — use it on support for investigation.
request_idstringSame value as debug_id; also returned in the X-Request-Id response header.

Canonical codes

HTTPcodeMeaningHow to react
400bad_requestMalformed request (invalid JSON, wrong type).Fix the payload and retry.
401unauthorizedMissing, invalid or expired token/key.Re-authenticate.
403forbiddenAuthenticated but lacking permission.Use a key with a broader scope, or ask the admin for the permission.
404not_foundResource doesn't exist or isn't in your scope.Confirm the ID and the org/project context.
409conflictIncompatible current state (version already published, duplicate email, etc.).Refresh local state and retry.
413payload_too_largeBody above 8 MB.Split the payload or reduce content.
422validation_failed / request_validation_failedInvalid field according to the schema.Read details.errors and fix it.
429rate_limitedRequest limit exceeded.Read Retry-After and wait. See Limits.
500internal_errorUnexpected server error.May retry with backoff. If it persists, report with the debug_id.

Some endpoints return more specific codes in code (for example plan_quota_exceeded, api_key_scope_insufficient, project_not_found). Treat any unknown code as "error of this HTTP type" — the HTTP status is the high-level source of truth.

Schema validation (422)

Requests with a body that doesn't match the schema return:

{
"code": "request_validation_failed",
"message": "Invalid data in request.",
"hint": "Check the required fields and formats.",
"details": {
"errors": [
{
"type": "string_type",
"loc": ["body", "email"],
"msg": "Input should be a valid string",
"input": 42
}
]
},
"debug_id": "..."
}

details.errors[] follows the standard Pydantic v2 format. Relevant fields:

  • loc: path of the offending field inside the payload (["body", "items", 0, "name"]).
  • type: error type (missing, string_too_short, int_parsing, etc.).
  • msg: detailed message.
  • input: rejected value (may be omitted).

Authentication and permission (401 / 403)

codeTypical detail
unauthorizedMessages like "Session expired or revoked", "Invalid or expired API key".
forbidden"Insufficient permission", "Insufficient API key scope", "Organization out of session scope".

Practical difference:

  • 401: the client cannot proceed without refreshing credentials.
  • 403: the client is authenticated but the credential isn't enough for the operation. May be API key scope, role in the organization, or attempt to access a project from another organization.

Rate limiting (429)

Response when per-minute or per-hour limit is exceeded:

{
"code": "rate_limited",
"message": "Rate limit exceeded.",
"hint": "Wait for the reset shown and try again.",
"details": {
"route": "analysis.validate",
"reset_at": "2026-04-24T15:31:00Z"
},
"debug_id": "..."
}

Extra headers on this response:

  • Retry-After: <seconds> — how long to wait before retrying.
  • X-RateLimit-Limit, X-RateLimit-Remaining — limit state at block time.

See Limits and usage for per-plan policies.

Large payload (413)

{
"code": "payload_too_large",
"message": "Payload above the allowed limit (8388608 bytes).",
"hint": "Reduce the payload size before retrying."
}

Default limit is 8 MB. For logtest events above that, split into separate calls.

Message internationalization

Error messages (message, hint) are resolved in the client's language. Resolution order:

  1. x-locale header (accepted values: pt-BR, en-US).
  2. Accept-Language header.
  3. Fallback to pt-BR.

code values are never translated — they are your stable API contract. Always branch logic on code; use message and hint only to display to end users.

Debug IDs and correlation

The debug_id field also appears in the response X-Request-Id header. It is generated automatically (or accepted from the client via the same request header).

Use this ID to:

  • Attach to a bug/support report — makes server log search easy.
  • Correlate chained calls — you can send the same X-Request-Id on sub-calls for a single trace.

For distributed tracing (OpenTelemetry), see the Traceparent header described in Conventions.

Handling examples

Python

import httpx

resp = httpx.post("https://ruleforge.example.com/api/v1/analysis/validate",
headers={"X-API-Key": key},
json=payload)

if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", "60"))
time.sleep(retry_after)
# retry
elif resp.status_code >= 400:
body = resp.json()
raise RuleForgeError(
code=body["code"],
message=body["message"],
debug_id=body.get("debug_id"),
)

JavaScript

const res = await fetch(`${base}/analysis/validate`, {
method: "POST",
headers: { "X-API-Key": key, "Content-Type": "application/json" },
body: JSON.stringify(payload),
})

if (!res.ok) {
const err = await res.json()
console.error(`[${err.code}] ${err.message} (debug_id=${err.debug_id})`)
throw err
}