Appearance
Safe retries (requestid)
A write that times out leaves you guessing: did QuickBooks apply it or not? Retry blindly and you may post the invoice twice. TenkeyBridge supports QuickBooks Online's answer to this, the requestid query parameter: send the same id again and the gateway returns the original result instead of applying the write a second time.
http
POST /v3/company/{realmId}/invoice?requestid=6b1c2f0e-5d7a-4c19-9f64-0a3c1d2e8b77Use it on every write you might retry, and always after a 504 AGENT_TIMEOUT or a dropped connection.
The rules
- Where: any
POST /{entity}write — create, update (including sparse),?operation=delete,?operation=void— and each write item of a/batch.GETand queries ignore it. - Format: 1 to 50 printable ASCII characters, no spaces (a UUID is 36). On
/batchthe limit is 36, and eachbIdmay be at most 10 characters. Otherwise400 REQUESTID_INVALID. - One id, one request. Make a fresh id for every new write and keep it for every retry of that write. Reusing an id with a different body, entity or operation is refused:
422 REQUESTID_BODY_MISMATCH, nothing runs. - Scope: an id is remembered per company (realm), for 7 days. After that it is forgotten and the same id would run as a new write. (Intuit does not publish its window.)
- Nothing is ever re-sent. A replay never sends the write to QuickBooks again.
ts
import { randomUUID } from "node:crypto";
const requestId = randomUUID();
for (let attempt = 0; ; attempt++) {
try {
return await client.invoice.create(invoice, { requestId });
} catch (err) {
if (!(err instanceof TenkeyBridgeApiError) || attempt >= 4) throw err;
// 409 in flight, 503 offline, 504 timeout, 502 outcome unknown: same id, wait, retry.
await sleep(err.retryAfterMs ?? 2000 * (attempt + 1));
}
}What a retry returns
| The original request | Retrying with the same requestid |
|---|---|
| Succeeded (applied) | 200 with the record, read fresh from QuickBooks (same shape as the original success, current values). Header X-TKB-Idempotent-Replay: true. A replayed delete returns { "status": "Deleted" } |
Failed with a definite fault (validation, duplicate name, stale SyncToken…) | The same status and fault code. Nothing runs. Fix the request and send it with a new id |
| Still running | 409 REQUEST_IN_FLIGHT with Retry-After. Wait, then resend with the same id |
Was never sent or was refused by a record lock (503 AGENT_OFFLINE, 503 QB_NOT_OPEN, QuickBooks 3175) | Runs normally. Nothing was recorded, so the id is free |
Lost after it was sent (504 AGENT_TIMEOUT, dropped connection, gateway restart) | The gateway looks, read-only, for proof it applied (see below). Proven: 200 with the record. Not proven: 502 REQUEST_OUTCOME_UNKNOWN |
Outcome unknown
When a write is lost after it reached QuickBooks, it may have applied. On a retry, the gateway tries to settle it with reads only:
| Write | How it is settled |
|---|---|
Create with a DocNumber (Invoice, Bill, Estimate, …) | Query by DocNumber. Exactly one match: applied |
| Create of a Customer, Vendor, Account, Item or Class | Query by DisplayName / Name (QuickBooks enforces these as unique). Exactly one match: applied |
| Delete | Read the record. Gone: applied |
| Void | Read the record. SyncToken has moved and it carries QuickBooks' VOID: memo: applied |
| Update, or a create with no natural key | Cannot be proven: always REQUEST_OUTCOME_UNKNOWN. |
A match is evidence, not proof of authorship: a record found by DocNumber or name could have been made by someone else, and a void or delete could have been done by someone else first. In both cases the record is already in the state you asked for, so it is reported as applied.
Why never just re-send? Over the Web Connector a timed-out request may still be queued and run later, so "not found yet" does not mean "not applied". The gateway stays safe and says so: 502 REQUEST_OUTCOME_UNKNOWN. Retrying with the same id re-checks every time, so a late-running write is reported as soon as it lands. To settle it yourself, look the record up (query by DocNumber or Id; for an update compare the record's SyncToken).
If an update or create still cannot be confirmed and you must resolve it, check QuickBooks and then send any new write with a new requestid.
Batches
Put the requestid on the batch URL. Each write item is remembered as requestid:bId, so resending the whole envelope after a failure replays the items that settled and runs only the ones that did not: no duplicates and no bookkeeping. Changing an item's body under the same requestid and bId faults only that item (REQUESTID_BODY_MISMATCH). Queries in a batch are not recorded.
Sandbox
The developer sandbox behaves identically. A sandbox reset also forgets its remembered ids, since the records they point to are gone.
Privacy
TenkeyBridge remembers the requestid, the operation and entity, the resulting record Id, the status and fault code, timestamps, and a SHA-256 digest of the request. Request and response bodies are never stored.
Differences from QuickBooks Online
Intuit documents only that a repeated id returns the original response. Where it is silent, we chose behavior that cannot double-apply. See requestid in COMPATIBILITY.
See also
- Error codes:
REQUESTID_INVALID,REQUESTID_BODY_MISMATCH,REQUEST_IN_FLIGHT,REQUEST_OUTCOME_UNKNOWN. - Node.js client: the
requestIdoption.

