Skip to navigation

Errors

Moyo Suite doesn’t use one uniform error envelope everywhere. Most endpoints return a plain message; a handful of endpoints — permissions, plan/module gating, and a few business-rule checks — return a richer structured object. Both are real, and you should handle both.

The common case: a plain message

{
"detail": "Import job not found."
}

The structured case: permission and entitlement errors

Requests blocked by RBAC or by your organization’s plan return a code you can branch on, plus a human-readable message:

{
"detail": {
"code": "permission_denied",
"message": "You don't have permission to view CRM leads.",
"required": "crm:lead:view"
}
}
{
"detail": {
"code": "module_not_subscribed",
"message": "Your organization hasn't enabled the CRM module.",
"module": "crm"
}
}

A few other endpoints — expense approval/rejection, data import mapping — use the same {code, message} shape for their own business-rule errors (e.g. read_only_field, approved_expense_cannot_be_edited).

Validation errors (422)

Request-body validation failures always come back the same way, with one entry per invalid field plus a request_id:

{
"detail": "Validation error",
"errors": [
{ "loc": ["body", "email"], "msg": "value is not a valid email address" }
],
"request_id": "..."
}

Status codes

StatusMeaning
400Bad Request — invalid input outside of body validation
401Unauthorized — missing/invalid/expired session
403Forbidden — authenticated, but blocked by RBAC or module entitlement
404Not Found
409Conflict — e.g. approved expense can’t be edited, duplicate reference
422Unprocessable Entity — body validation failure
429Rate Limited
500Internal Server Error

Every response carries a request ID — in the header

Every response — success or failure — includes an X-Request-ID header. Include it when reporting an issue.

The response body only echoes request_id for two cases: 422 validation errors (shown above) and unhandled 500s. A plain {"detail": "..."} error (404s, most 400s, RBAC/entitlement 403s, etc.) does not include request_id in its body — read it from the header instead.