Error Handling
The Moloni ON API uses a consistent error model across all operations. Understanding how errors are returned lets you build robust integrations that handle failures gracefully.
Error model
Every query and mutation returns an errors array alongside data. The Error type has two fields:
| Field | Type | Description |
|---|---|---|
field | String! | The input field that caused the error, or "*" for general errors |
msg | String! | A human-readable error message |
{
"data": {
"customerCreate": {
"errors": [
{ "field": "vat", "msg": "A customer with that vat already exists for your company" }
],
"data": null
}
}
}
Validation errors
Validation errors are returned inside the response, not as top-level GraphQL errors. The API validates all inputs and returns every problem at once, so you don't need to fix one field at a time.
{
"data": {
"customerCreate": {
"errors": [
{ "field": "vat", "msg": "Invalid VAT inserted" },
{ "field": "zipCode", "msg": "Invalid zip code format" },
{ "field": "number", "msg": "A customer with that number already exists for your company" }
],
"data": null
}
}
}
The field value corresponds directly to the input field name, making it straightforward to map errors back to form fields in a UI.
Common validation scenarios
| Scenario | Field | Example message |
|---|---|---|
| Duplicate value | vat, number, etc. | "A customer with that vat already exists for your company" |
| Entity not found | customerId, productId, etc. | "The customer provided does not exist for this company" |
| Invalid format | vat, zipCode, etc. | "Invalid VAT inserted" |
| Missing required field | * or field name | "Name or VAT is required." |
| Invalid reference | salespersonId, etc. | "That salesperson does not belong to your company" |
General errors
When an error applies to the operation as a whole rather than a specific field, the field is set to "*":
{
"errors": [
{ "field": "*", "msg": "Name or VAT is required." }
]
}
Authentication errors
Authentication failures are the only case where errors appear at the top level of the GraphQL response (outside any mutation/query). These occur when the token is missing, invalid, or expired.
{
"errors": [
{
"message": "Not authenticated",
"extensions": { "code": "UNAUTHENTICATED" }
}
]
}
The HTTP status code is 401 Unauthorized in this case. Common reasons:
| Reason | Description |
|---|---|
| Missing token | No Authorization header provided |
| Expired access token | The 1-hour access token has expired; refresh it |
| Expired API key | The API key has passed its expiration date |
| Invalid token | The token is malformed or has been revoked |
Authorization errors
If the authenticated user doesn't have permission for the operation, the error is returned inside the response (not at the top level):
{
"data": {
"customerCreate": {
"errors": [
{ "field": "*", "msg": "You don't have permissions to execute this action." }
],
"data": null
}
}
}
Other authorization-related messages include:
| Message | Cause |
|---|---|
"You must be logged in to execute this action" | Token is valid but user session is missing |
"You don't have access to this company." | User is not a member of the company |
"Expired subscription." | The company's subscription has expired |
HTTP status codes
The API uses standard HTTP status codes:
| Code | When |
|---|---|
| 200 | All successful GraphQL responses, including those with validation errors in the errors array |
| 401 | Authentication failure (missing, expired, or invalid token) |
| 500 | Internal server error (unexpected failure) |
Best practices
Always check errors first:
const result = data.customerCreate;
if (result.errors.length > 0) {
// Handle errors
for (const error of result.errors) {
console.error(`${error.field}: ${error.msg}`);
}
return;
}
// Safe to use result.data
const customer = result.data;
Map errors to form fields:
const fieldErrors = {};
for (const error of result.errors) {
if (error.field === '*') {
// General error: show at the top of the form
fieldErrors._general = error.msg;
} else {
// Field-specific error: show next to the input
fieldErrors[error.field] = error.msg;
}
}
Handle auth errors separately:
const response = await fetch('https://api.molonion.pt/v1', { ... });
const json = await response.json();
// Top-level errors = auth failure
if (json.errors) {
if (json.errors[0]?.extensions?.code === 'UNAUTHENTICATED') {
// Token expired; refresh or re-authenticate
}
return;
}
// Response-level errors = validation
const result = json.data.customerCreate;
if (result.errors.length > 0) {
// Handle validation errors
}
Next steps
- API Reference: Browse every query, mutation and type
- Explorer: Try queries live in the GraphiQL interface