What is a canonical object?
Every entity in Structural is stored as a canonical object - a platform-agnostic representation with:- A typed, prefixed ID generated deterministically from the source system and entity.
Prefix indicates the entity type:
pay_for payments,proj_for projects,cust_for customers,cor_for contracts,inv_for invoices. - An
objectdiscriminator ("payment","project","invoice", etc.) for type safety. - An
external_idsmap preserving each platform’s native identifiers - Procore’s numeric IDs, Dynamics’ GUIDs, and any secondary identifiers needed for lossless round-trip conversion. - An optional
provider_metadatamap storing platform-specific fields that have no canonical equivalent, so nothing is lost in translation.
Support matrix
| Entity type | Key fields | /connect + /objects | /propagate | /transform | Procore | Dynamics |
|---|---|---|---|---|---|---|
| project | name, number, status | ✓ | ✓ | ✓ | ✓ | ✓ |
| customer | name, currency, tax_id | ✓ | ✓ | ✓ | ✓ | ✓ |
| contract | title, number, amount, status | ✓ | ✓ | ✓ | ✓ | ✓ |
| invoice | number, amount, status | ✓ | ✓ | ✓ | ✓ | ✓ |
| payment | amount, check_number, status | ✓ | ✓ | ✓ | ✓ | ✓ |
| employee | first_name, last_name, email, job_title | — | — | ✓ | ✓ | ✓ |
| time_entry | hours, date, employee | — | — | ✓ | ✓ | ✓ |
| change_order | title, amount, status | — | — | ✓ | ✓ | ✓ |
| budget_line_item | cost_code, budgeted_amount, name | — | — | ✓ | ✓ | ✓ |
contract on /transform requires context: {"commitmentType": "WorkOrderContract"} or
context: {"commitmentType": "PurchaseOrderContract"} when source is procore. See the
Transform endpoint for details.Example: Payment #1 - Lusail Boulevard Tower
The same payment as it appears in each system. This is real seed data, not a fabricated example.- Procore
- Dynamics
- Canonical
- Money in minor units. Procore sends
"1700000.00"(a string in dollars). The canonical stores170000000(an integer in cents). This eliminates floating-point rounding across systems. - ISO 4217 currency codes. The canonical
amountobject carries the currency code explicitly. Dynamics’ payment (6188000) is in QAR (Qatari Riyal) - a different currency for the same payment. Structural normalizes to the canonical amount in the source currency. external_idsfor lossless round-trip. Procore’s numeric IDs and Dynamics’ GUIDs are both preserved. The reverse mapper reads these to reconstruct the original platform payload exactly.- Cross-entity references. The canonical payment references its project and invoice
via typed
ObjectReferencefields (omitted from this example for brevity) that link to other canonical objects by their prefixed IDs.