Morffeus Docs
morffeus.com

Errors

When a call fails, the HTTP status tells you what kind of problem it is and the body tells you where. Read the status first: it is the one thing every error has. Bulk endpoints are the exception, because they report failed rows inside a 200.

Status codes#

StatusWhat it means
200Done. On bulk endpoints also when some rows failed; see Failures inside a 200.
201Created, for example by POST /customers and POST /warehouses. POST /int/cards answers 201 also when the card already existed.
204Deleted. There is no body.
400We could not read the request: the body is not valid JSON, the top-level key the endpoint expects is missing (stocks on PUT /int/stocks), a required query parameter is missing, or a value has the wrong type.
401The token is missing, invalid, expired or revoked. See Authentication.
404The path does not exist, or the record is not in your app.
406Your Accept header rules out JSON. Send application/json or leave the header out.
413The request body is larger than we accept. Split the batch.
422We read the request and rejected it: a field failed validation, or a rule said no, for example an order that already exists.
500Something failed on our side. See When to retry.

The error body#

Most errors come as an errors object. Each key names where the problem is, and each value is a list of messages:

  • A field name, such as city, when a field of the object you sent failed validation.
  • An area, such as Warehouses, taken from the message key: API.Warehouses.NotFound goes under Warehouses.
  • message, when the message has no area.

Some responses differ from that shape:

  • 401: errors.token holds one string, not a list.
  • 400, 406, 413 and some 500s that happen before your request reaches an endpoint: {"errors": {"detail": "Bad Request"}} with the status text.
  • A few rule errors: {"error": "<message>"}, singular, for example a duplicate POST /int/orders.
  • 404: an unknown path answers []; a record that is not there may answer with an empty body.
  • 500 from an endpoint: {"message": "An unexpected error occurred."}.

Some error responses are sent without a Content-Type header. Parse the body as JSON whatever the header says.

Error404 Not Found
{
  "errors": {
    "Warehouses": ["API.Warehouses.NotFound"]
  }
}
Error422 Unprocessable
{
  "error": "API.Order.OrderAlreadyExists"
}
Error400 Bad Request
{
  "errors": {
    "detail": "Bad Request"
  }
}

Messages#

Messages are keys such as API.Warehouses.NotFound or API.Order.AppLocationNotFound: API, the area, then what went wrong. When your app has a translation for a key in your token's language, you get the translation instead of the key. Validation of a single object answers with the plain text of the rule, such as can't be blank.

Branch on the status and the key, not on the message text. The same error can come back as a key or as a translated sentence, depending on your app's translations and your token's language. Log the full body: it is what we need when you ask us.

Validation errors#

When you create or update one object, every field that fails comes back under its own name, with the rules it broke. A warehouse without an address, city, postcode and market answers like the first sample.

Bulk writes report the first failure only, as one key that names the area, the field and the rule. The same warehouse sent through PUT /warehouse answers like the second sample. Fix it and send again to see the next one.

Where a value has the wrong type, POST /int/orders adds a fields list that names each field, the type we expected and what we got.

POST/warehouses422
{
  "errors": {
    "address_line_1": ["can't be blank"],
    "city": ["can't be blank"],
    "postcode": ["can't be blank"],
    "market_id": ["can't be blank"]
  }
}
PUT/warehouse422
{
  "errors": {
    "Warehouses": ["API.Warehouses.AddressLine1CantBeBlank"]
  }
}
POST/int/orders422
{
  "errors": {
    "Utils": ["API.Utils.WrongDataType"],
    "fields": [
      { "field": "email", "expected_type": "string", "received_type": "integer", "received_value": 5 }
    ]
  }
}

Failures inside a 200#

Endpoints that take a list write what they can and tell you what they could not. The status is 200 either way, so always read the list of failures:

EndpointWhere the failures are
PUT /int/stocksdata.failed: the row you sent in input, the reason in error.message. See Stock.
POST /stock/reconciledata.failed: the entry you sent in product, the reason in error. See Stock.
POST /int/app_locationsInside data, among the locations written: an entry with a message instead of a location.

A lookup that finds nothing can also answer 200: GET /products with a single code, barcode, slug or id answers {"data": {}}.

When to retry#

Last updated 30 September 2026 · API v2 Something wrong on this page?