Skip to content

Errors

Errors are JSON. Most carry a message, and validation failures carry a Laravel-shaped errors object.

json
{
  "message": "The given data was invalid.",
  "errors": {
    "ideas.0.headline": ["The ideas.0.headline field is required."]
  }
}

Status codes

CodeMeaning
200Success
201Created (register, media upload, member invite)
204Success, no body (DELETE /device-tokens)
403Wrong or missing brand, or you lack permission
404Not found, or belongs to a brand you are not in
409A setup flow expired (passkey registration, 2FA setup)
422Validation failed, or a plan limit was reached
429Throttled
500Server error

Expired tokens do not return 401

Read this before writing a retry loop

An unauthenticated request to the API does not return 401. It returns HTTP 200 with this body:

json
{
  "remark": "unauthenticated",
  "status": "error",
  "message": { "error": ["Unauthorized request"] }
}

So a client that only checks response.status will treat a revoked token as a successful call and parse garbage.

Check the body, not just the status:

js
const response = await fetch(url, { headers })
const body = await response.json()

if (body.remark === 'unauthenticated') {
  throw new Error('Postlyra token is invalid or revoked.')
}

This is a known quirk rather than a deliberate design. If it changes, it will change to a real 401, so checking for both is the safe thing to write today.

404 versus 403

Resources are scoped to a brand. Asking for a draft, idea or contact that belongs to a different brand returns 404, not 403, because from your side that id does not exist. A 403 means the brand itself was rejected, which almost always means a wrong X-Postlyra-Brand value.

Plan limits

Hitting a plan allowance returns 422, sometimes with a flag:

json
{ "message": "You have reached your plan limit. Upgrade for more.", "limit_reached": true }

You will meet this on POST /users (member seats) and POST /media (storage). See Entitlements to read your allowances before you hit them.

Postlyra, by MAVA Design