Skip to content

Errors

Every failure returns JSON with a message, and nothing else is guaranteed:

{
"message": "Vendor not found"
}

Two optional fields appear depending on how the request failed:

{
"message": "Invalid payment payload",
"errors": [
{
"path": "allocations",
"code": "custom",
"message": "Allocation total cannot exceed amountCents"
}
]
}
Field When it appears
message Always. Safe to show to a user for 4xx; generic for 5xx.
errors Field-level validation detail. path is a dotted path into the request body.
code A stable machine-readable code, on the errors that have one — for example rate_limited.
Code Meaning
400 The request body, query, or path parameter did not validate. Check errors.
401 Missing, malformed, or expired access token. Refresh it.
403 Authenticated, but your role cannot perform this action.
404 No such resource in your organization. Also returned for resources that exist elsewhere.
405 The endpoint exists but the resource is system-managed — chart of accounts categories, for example.
409 The request conflicts with current state: an invalid status transition, a payment that cannot be edited, a resource still in use.
412 A precondition is unmet — most often online payments not yet configured for the organization.
429 Rate limited. See Rate limits.
502 An upstream provider (Stripe, QuickBooks, Gmail) failed. Safe to retry.
503 A capability is not configured on this deployment, such as online payments or a webhook secret.

409 means the API rejected the request on purpose, and repeating it unchanged will fail the same way. Two common cases:

  • Status transitions. Work orders move open → in_progress → completed, with cancelled reachable from either open state. Anything else is a 409.
  • Immutable records. PUT /api/payments/{id} always returns 409; payments are reversed and recreated rather than edited.

502 and 503, by contrast, are about the environment rather than the request, and are worth retrying with backoff.