# API conventions

Create and read sales orders and quotes, customers, and the supporting product catalogue, for your company.

Every request is authenticated with a bearer token and is scoped to the authenticated company.

## Errors

Every error response, whatever the HTTP status, has the same body — an error envelope with a short stable "type" naming the kind of failure, a human-readable "message", the request's idempotency key echoed back as "requestId" when the failing request carried one, and an "errors" array. The "type" values are "validation_failure" (HTTP 400 — a malformed or invalid request), "failed_precondition" (HTTP 400 — the request was valid but the resource's state does not allow it, for example writing a line on an invoiced order), "unauthenticated" (HTTP 401), "permission_denied" (HTTP 403), "not_found" (HTTP 404), "conflict" (HTTP 409), "rate_limited" (HTTP 429 — you are being throttled, and the Retry-After header, when present, says when to come back), "resource_exhausted" (HTTP 429 — a limit other than a request rate was exceeded, in practice a request or a response too large to carry; the message names the limit, and retrying unchanged will fail the same way — send less, or ask for a smaller page), "internal" (HTTP 500), "not_implemented" (HTTP 501 — a valid request the API does not support yet), "unavailable" (HTTP 503), and "timeout" (HTTP 504); treat an unrecognised type as a generic error. On a validation failure the errors array carries one entry per field-level problem — each with a stable error code, a human-readable message, and a pointer to the offending field (for example "lines[2].catalogue.product_id"). On an idempotency conflict (HTTP 409, type "conflict") it carries one entry whose code says which kind: "request_replayed" (the original request completed — "resourceId" on the envelope holds the id it created; read it instead of retrying), "request_id_reused" (the key was reused with a different body), "request_attempt_failed" (the original attempt failed and may still have written) or "request_in_progress" (its outcome is unknown); verify with a read before sending a fresh key. For every other type the array is empty. Stable validation codes defined so far include "customer_not_found" (the customer reference on a create could not be resolved) and "product_not_found" (a catalogue line references a product id that does not exist). The list is not exhaustive — more codes will be added as validations grow, so treat an unrecognised code as a generic validation failure rather than an error.

## Unknown request fields

A JSON property the API does not recognise — a misspelt or removed field name — is silently ignored, not rejected, so check field names against this document.

A request without a valid bearer token returns HTTP 401 (unauthenticated). Retrieving a single resource by an id that does not exist in your company returns HTTP 404 (not_found).

## Rate limits

Requests are rate limited. A request over the limit returns HTTP 429 (rate_limited) with a Retry-After header saying how long to wait before retrying; concrete limits will be announced.

## Field filtering

Reduce response payload size by requesting only the fields you need. Pass a `fields` query parameter with a comma-separated list of camelCase JSON names — e.g. `?fields=orderId,total.amountMicros`. Dot paths reach into nested objects and map transparently across arrays. Paths are relative to the resource, not the response envelope; envelope keys like `nextPageToken` are always preserved. To keep everything except a few heavy fields, use `excludeFields` instead. If both are provided, `fields` takes precedence. Filtering applies to successful (2xx) JSON responses only — error bodies are never filtered. Unknown field names are silently ignored.

## Webhooks

Subscribe with WebhookService to be notified when orders and quotes change. Deliveries are thin events: the signed JSON body names the event (`id`, `type`, `occurred_at`) and the resource by id (`data.order.id` or `data.quote.id`, plus `data.previous` on transitions) — it never carries the resource itself. Fetch current state with GetOrder / GetQuote, deduplicate on the event `id` (delivery is at-least-once), and verify the `X-Factory-Signature` header with the secret CreateWebhook returns once. The change feeds (QueryOrders, QueryQuotes with an updatedSince checkpoint) remain the way to backfill and reconcile.

Money, enums and the versioning policy are explained in [Core concepts](https://developer.factory.app/concepts.md).

---

Source: https://developer.factory.app/conventions · Factory Sales API v1
