Error Handling | Moloni ON API
Moloni ONGuidesAPI ReferenceExplorer
Guides

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:

FieldTypeDescription
fieldString!The input field that caused the error, or "*" for general errors
msgString!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

ScenarioFieldExample message
Duplicate valuevat, number, etc."A customer with that vat already exists for your company"
Entity not foundcustomerId, productId, etc."The customer provided does not exist for this company"
Invalid formatvat, zipCode, etc."Invalid VAT inserted"
Missing required field* or field name"Name or VAT is required."
Invalid referencesalespersonId, 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:

ReasonDescription
Missing tokenNo Authorization header provided
Expired access tokenThe 1-hour access token has expired; refresh it
Expired API keyThe API key has passed its expiration date
Invalid tokenThe 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:

MessageCause
"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:

CodeWhen
200All successful GraphQL responses, including those with validation errors in the errors array
401Authentication failure (missing, expired, or invalid token)
500Internal 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