Skip to content

Errors

APIError is a JSON response and an exception at the same time. Raise it from any handler or hook and Muffin returns the response.

Response shape

By default the body is:

{"error": true, "message": "Bad Request"}

The status code comes from the shortcut or from status_code:

from muffin_rest import APIError

raise APIError.BAD_REQUEST("Invalid filter")
# 400 {"error": true, "message": "Invalid filter"}

raise APIError.NOT_FOUND()
# 404 {"error": true, "message": "Nothing matches the given URI"}

raise APIError.CONFLICT("Email is already used")
# 409 {"error": true, "message": "Email is already used"}

Every HTTPStatus name works as a shortcut: UNAUTHORIZED, FORBIDDEN, METHOD_NOT_ALLOWED, UNPROCESSABLE_ENTITY, TOO_MANY_REQUESTS, INTERNAL_SERVER_ERROR, and so on. Use the names, not raw numbers.

Extra keyword arguments are merged into the body:

raise APIError.BAD_REQUEST("Invalid filter", field="age")
# {"error": true, "message": "Invalid filter", "field": "age"}

A dictionary as the first argument replaces the default body entirely:

raise APIError.BAD_REQUEST({"error": True, "message": "Invalid filter", "field": "age"})

This is how validation errors carry details:

raise APIError.BAD_REQUEST("Bad request data", errors=err.messages)

Errors raised by the library

Situation Status Body
No authorization 401 default APIError.UNAUTHORIZED
Invalid JSON body 400 parser message
Schema validation failed 400 message plus errors
Invalid pagination params 400 Pagination params are invalid
Unknown resource 404 Resource not found
Invalid Peewee id 400 Invalid resource ID
Invalid Mongo ObjectId 404 default NOT_FOUND
Unsupported method 405 router response
Rate limit exceeded 429 whatever you raise

Validation errors look like this:

{
  "error": true,
  "message": "Bad request data",
  "errors": {"name": ["Missing data for required field."]}
}

Handler binding

self.api raises HandlerNotBindedError when the handler was not registered with api.route. The error is a RuntimeError. This usually means a handler class was imported but never decorated.

api.route raises InvalidEnpointError (a TypeError) when the object is neither a path string nor a RESTBase subclass.

Custom routes and hooks

APIError works anywhere the request is handled:

@PWRESTHandler.route("/pet/{id}/adopt", methods="post")
async def adopt(self, request, *, resource=None):
    if resource is None:
        raise APIError.NOT_FOUND()
    if resource.status != "available":
        raise APIError.CONFLICT("Pet is not available")
    ...

For errors that are not part of the API contract, let the exception propagate and let Muffin's error handling produce the response. APIError is for expected client-facing failures.