Skip to main content

What happens if…?

Docs usually tell you the happy path. This page tells you what happens down the other branch — the retry, the duplicate, the delete, the misconfigured receiver. Each answer links its source. [GAP] marks the questions the documentation cannot answer yet; if one blocks you, ask support@upwell.com.

Writes and retries

The second POST fails with 400 Uniqueness violation on the (sourceSystem, sourceSystemId) pair. First sync is POST, every later change is PUT. Source: Idempotency & retries.
It silently succeeds and changes nothing — a no-op upsert that does not update name, email, phone, or anything else you sent. If you meant to update, use PUT. Source: Syncing foundation entities.
Both inserts succeed and return different ids. Seconds later, an async dedup pass soft-deletes the newer row, keyed on (tenant, sourceSystemId) — note: not scoped by integration, so two integrations reusing id strings collide. The id your retry returned may be the deleted one; reconcile by looking the invoice up by sourceSystemId afterward. Source: Re-submitting. One caveat on the lookup itself — see filtering a list below.
Fails fast with 400 DOCUMENT_ALREADY_UPLOADED — synchronous, unlike carrier invoices. Exception: re-requesting one whose upload never completed returns a fresh URL for the same document. Source: Purchase orders & vendor invoices.
bulk-carriers: the entire batch fails (no upsert). bulk-bills: upserts like its singular endpoint. Neither reports partial success. What the failure response tells you about which row broke the batch: [GAP]. Source: Syncing foundation entities.
Runtime rejection: "The value 2847.5 lies outside the bounds or is not an integer." All monetary fields are integer cents. Source: Money & payloads.
400 with the column name in a flat error string, e.g. "Not-NULL violation. null value in column \"shipment_id\"…". Known cases: shipments.shipmentId, invoices.balance — the OpenAPI spec marks these required, so generated clients catch them before the wire. Source: Integration patterns.

Errors, limits, and the reference

A flat object: {"error": "<message>"}, sometimes with a code such as constraint-violation or bad-request. An invalid or missing API key returns 401 with an empty body. There is no nested error envelope. The message→fix table lives in Error handling.
There is currently no rate limiting on the API — no 429, no X-RateLimit-* headers, no Retry-After. A 429 you observe came from your own proxy or gateway. Design your client politely (bounded concurrency, backoff on 5xx), but don’t build 429 machinery. Source: Error handling.
It now declares authentication (ApiKeyAuth in the Authorization header), the machine-enforced required fields, the real UPPER_SNAKE_CASE status enums, the nested invoiceShipments insert, and generic 400/401 responses. One remaining caveat: the where/orderBy filters that some list GETs accept are not in the spec — pending verification of the query-string transport. [GAP — verification in progress] Prefer the POST /search endpoints for filtering.

Reading data back

You get the first 10 records — the default limit is 10 (a handful of endpoints default to 100). Nothing marks the response as truncated. Always pass limit/offset explicitly. Source: API conventions.
Three cases:
  1. POST /<resource>/search exists for customers, carriers, shipments, shipment line items, invoices, bills, bill payments, customer payments (+ line items), vendors, purchase orders, vendor invoices — use it (Integration patterns).
  2. Addresses and companies have no search — page the collection or go through the parent.
  3. A ?where= JSON filter on list GETs: the underlying queries accept it, but whether the GET query-string transport parses object-typed params is still being verified — [GAP — verification in progress]. Until it’s confirmed, use POST /search where it exists.
You can’t get its id from the create response (POST /api/rest/companies returns no address ids), companies expose no address read path, and addresses carry no external key — the row is recoverable only by paging GET /api/rest/addresses. Create company addresses standalone instead. Source: Working with addresses.

Deletes

Hard delete, and its addresses go with it — and per the AR overview, its invoices too. The external key pair is freed. Prefer the reversible soft delete (PUT with deletedAt), which frees the key equally. A dedicated “deactivate” mechanism beyond soft delete is not documented. [GAP] Source: Deleting and archiving.
Soft delete — the record disappears from the app, its (case-insensitively unique) name is freed, and its addresses are orphaned rather than removed. Source: Deleting and archiving.
It fails outright (restrict-on-delete). Reassign or remove the bills first. Vendor deletes are permanent — no soft-delete column. Source: Syncing foundation entities.
Gone permanently — rules never soft-delete. Set enabled: false instead. Source: Rules.

Webhooks misbehaving

The delivery retries — 5xx, timeouts, and network failures all count — up to the subscription’s retryAttempts, with exponential backoff (1 min, 2 min, 4 min, …). retryAttempts: 3 (the default) means up to 4 total deliveries. Each attempt has a 15-second timeout — ack fast, work async. Source: Outbound webhooks.
The event is recorded as delivered and never retried. This is the silent-loss case: Upwell’s side shows success while your side rejected everything. Alert on your receiver’s 4xx rate; reconcile by polling. Source: Outbound webhooks, Reliability.
After retries exhaust, there is no automatic replay and no delivery-inspection surface — recovery is polling the resource endpoints (GET /carrier_invoices/{id} etc.), which the status guide recommends running periodically anyway. Source: Outbound webhooks, Reliability.
Each change fires exactly one trigger — the most specific that applies. A tracked status transition (e.g. to APPROVED) fires only update.carrier_invoice.status.APPROVED, not also the generic update.carrier_invoice. Subscribe to every trigger you care about. Source: Webhook event catalog.
Those four triggers (create/update/delete.carrier_document, update.invoice.status.APPROVED) exist in the dispatcher but are not self-service — they’re marked † in the event catalog; contact support to enable them.
No webhook fires — foundation entities have no triggers by design; your system remains the system of record and reads state back by polling. Source: Webhook event catalog.

Lifecycle surprises

Expected. A brand-new row lists matching findings (“no matching shipment / bill”) that clear on their own when ingestion completes. Wait for updatedAt > createdAt before judging exceptions. Source: Carrier invoice status.
The carrier-invoice status enum has 16 values; the docs’ tables define 13. AWAITING_BILL, CANCELLED, and REFUNDED are currently undefined in prose. [GAP — definitions needed]
The invoice is rejected — but which status it lands in (REJECTED? EXCEPTION?) is not documented. [GAP] Source: How approvals work, Responding to approval requests.
Auto-approval runs on a schedule, not instantly; it only touches invoices with no open or in-progress exceptions, matched to a bill and carrier, inside your guardrails — and the master switch is off by default. The cadence is not published. [GAP — cadence] Source: How approvals work.
No. Creating payment line items does not recalculate the target invoice’s balance/status; your integration updates the invoice afterward. (Upwell’s own internal payment pipelines do update invoices — which is why payments recorded inside Upwell behave differently.) Source: Customer invoices (Tip), Remittances.
It is parsed and stored as what you labeled it — a carrier rate con typed CUSTOMER_RATE_CONFIRMATION pollutes customer-side validation and vice versa. Re-type and re-process rather than editing amounts; never let carrier cost reach the customer side. Source: Document conventions.
Almost always a type problem: pages render only specific types (CUSTOMER_BILLING, COMPANY_ADDRESS), and ADDRESS_BOOK is read by nothing. The write succeeded; the page filtered it out. Debug order and per-page field lists: Working with addresses.