Sync Status Updates

The shared close-the-loop mechanism used by every accounting record type. Cross-references your target system's ID onto the Tai record, keeps the batch queue clean, and powers the failure retry loop through Accounting Sync History.

What this is for

Sync status is how you tell Tai "I got this record, and here's what happened." Every record that leaves Tai via Outbound Data Transmission, and every record you create in Tai via Inbound Data Transmissions, carries a sync status. If you skip this call:

  • Outbound: the record stays at SyncStatus=None in Tai, and every batch pull will keep serving it to you forever. There's no other way for Tai to know you handled it.
  • Inbound: the record you just created lives in Tai without a cross-reference to your target system. When someone in six months tries to reconcile a discrepancy, they'll have no way to find the corresponding record in your ERP.

Sync status is small, cheap to call, and non-negotiable. Every attempt (success or failure) gets a sync call.

The shared shape

Every sync endpoint uses the same 3-field request payload:

  • status β€” the outcome (see enum below).
  • transactionId β€” the target system's primary key for the record (on success), or a datetime stamp (on failure).
  • The record's Tai ID β€” billId, invoiceId, billPaymentId, or invoicePaymentId depending on which endpoint you're hitting.

The response is a plain string (not a JSON object), e.g. "Invoice 14041027 successfully updated."

The ExternalSyncStatus enum

ValueWhen to use it
NoneThe initial state on every new record in Tai. You generally don't send this β€” Tai sets it.
CompleteYou successfully pushed the record to the target system and got a positive acknowledgement. transactionId = target system's primary key.
FailedYou attempted the push and the target system rejected it (validation error, missing reference, whatever). transactionId = a datetime stamp (e.g. 20260904) so the attempt is at least recorded.
AlreadySyncedIdempotency case β€” you re-processed a record that had already been pushed. Rare but useful when your integration is replaying a queue.
UpdatedThe record was updated on the target side after an initial sync. Used when you've re-pushed a revision.

How sync status appears on the record

Once you've called sync, the Tai record's externalSync block reflects the outcome. When you re-read the record (via GET /Bills/{billId}, for example), you'll see:

"externalSync": {
  "date": "2026-09-04T20:00:51.397+00:00",
  "status": "Complete",
  "referenceNumber": "SAP-DOC-4400218994"
}
  • date β€” when Tai recorded the sync outcome.
  • status β€” the last value you sent.
  • referenceNumber β€” the transactionId you sent (Tai stores it under referenceNumber on the record).

The four sync endpoints

All four share the same shape β€” only the ID field name changes.

Bills

PUT  /PublicApi/Accounting/v2/Bills/Sync

View in API Reference β†’

Request payload

{
  "billId": 18042026,
  "status": "Complete",
  "transactionId": "SAP-DOC-4400218994"
}

Response (200)

"Bill 18042026 successfully updated."

Invoices

PUT  /PublicApi/Accounting/v2/Invoices/Sync

View in API Reference β†’

Request payload

{
  "invoiceId": 14041027,
  "status": "Complete",
  "transactionId": "SAP-INV-9900341882"
}

Response (200)

"Invoice 14041027 successfully updated."

Bill Payments

PUT  /PublicApi/Accounting/v2/BillPayments/Sync

View in API Reference β†’

Request payload

{
  "billPaymentId": 1687709,
  "status": "Complete",
  "transactionId": "SAP-PAY-77118203"
}

Invoice Payments

PUT  /PublicApi/Accounting/v2/InvoicePayments/Sync

View in API Reference β†’

Request payload

{
  "invoicePaymentId": 2021562,
  "status": "Complete",
  "transactionId": "SAP-RCPT-3320019844"
}

Failure payload

Any of the four endpoints, when the target-system push failed:

{
  "billId": 18042026,
  "status": "Failed",
  "transactionId": "20260904"
}

transactionId is required even on failure β€” a datetime stamp is a fine placeholder. What matters is that the record moves out of SyncStatus=None so the batch queue is clean and Tai's Accounting Sync History has an entry to display.

The retry loop

A status=Failed sync doesn't lose the record β€” it hands it to a Tai user to fix. Here's the loop:

  1. You push to the target, it rejects. Say the target's error was "Bill To Address line 1 is required."
  2. You call PUT /Sync with status=Failed. The Tai record now shows as failed sync.
  3. You call POST /ShipmentActivityLogs on the same shipment with the error text so a Tai user sees it inline. (See Shipment Activity Logs for how to write this well.)
  4. A Tai user opens the Accounting Sync History page, sees the failed record with your error message, fixes the underlying data (adds the missing address, corrects the wrong customer, whatever).
  5. The user moves the record from Failed β†’ Pending. Tai flips the sync status back to None.
  6. The record shows up on your next batch pull. You re-attempt the push. If it succeeds this time, status=Complete and the loop closes.

Without steps 2 and 3, the loop breaks β€” the record either sits at None forever (silently re-served on every batch, cluttering your queue) or the Tai user has no idea why it failed and no way to fix it.

Idempotency

PUT /Sync is idempotent β€” safe to call multiple times with the same payload. Useful for retry logic in your integration: if the sync-status call itself fails (network hiccup), just try again. Tai reconciles to the last call.

The only value that changes state each time is status β€” if you first send Failed and then later send Complete after a successful retry, Tai reflects the current outcome.

Where this is called from

Pair every sync call with a matching Shipment Activity Log entry β€” sync handles the machine-readable state, the activity log handles the human-readable "why."


Did this page help you?