Skip to main content
Wildo.ai Coming soon

Systems of record

Connect any OData v4 service

A company's own ERP surface or middleware that speaks OData v4 can be connected even if Wildo has never heard of the vendor: records read live or copied, changes written back, actions run, and records kept in step in both directions.

An OData warehouse service supplies live stock and copied shipments, while the application sends a release-shipment action that leaves a record.

Connect any OData v4 service

Internal tools often sit on systems no catalogue lists: a bespoke middleware, an ERP’s own service layer, a vendor nobody has described yet. When such a system speaks OData v4, the open standard many ERP and CRM products publish, an application can declare it as a system of record without waiting for Wildo to support that vendor by name.

Everything the OData v4 protocol defines is available. Read in place: a screen asks the service when it opens. Copy kept current: a pipeline copies chosen fields, and can read only what changed when the service offers change tracking or a modification timestamp. Write back: records can be created, changed and deleted, each update conditioned on the version tag the service attaches to the record, so a stale change is refused by the service itself when it keeps such tags. Actions: an operation the service exposes, such as releasing an order, can be declared and run under the same controls as a write. Two-way: where records carry version tags, one pipeline can keep records edited on both sides in step.

The generic facet knows nothing vendor-specific, and that is its cost. When a vendor has its own facet, as Dataverse and Business Central do, that facet is the better choice: it carries the corrections that vendor needs.

Example — An in-house logistics service

A company exposes its warehouse system through an OData v4 service of its own. Its operations tool reads stock levels live, copies shipments every few minutes through the service’s change tracking, and runs the service’s own “release shipment” action from a button, with the action recorded like any other change.

For engineers

The generic facet offers five credential kinds, so the instance names the one its service accepts. A design example:

systemsOfRecord: {
  logistics: {
    vendor: 'odata',
    dialect: HttpApiTransportDialect.ODATA,
    credential: { kind: SystemOfRecordCredentialKind.OAUTH2_CLIENT_CREDENTIALS_SECRET },
    maxRequestsPerMinute: 600,
  },
},
KindWhat the environment supplies
API_KEY_HEADERapiKey as a secret, sent as Authorization: Bearer <key>
BASICusername as an address, password as a secret
OAUTH2_CLIENT_CREDENTIALS_SECRETclientId and tokenUrl as addresses, clientSecret as a secret; the client authenticates in a Basic header, the form OAuth requires every server to accept
OAUTH2_CLIENT_CREDENTIALS_CERTIFICATEclientId, tokenUrl and the public certificatePem; the privateKeyPem that signs the assertion as a secret
MTLS_CLIENT_CERTIFICATEthe public certificatePem, the privateKeyPem as a secret, and a caBundlePem only when the service’s certificate is not one the platform already trusts; the certificate is presented in the TLS handshake, so baseUrl must be https:

baseUrl is the OData service root, the URL an entity-set name is appended to, not the host. An instance that names no credential kind is refused at startup with the five listed. maxRequestsPerMinute paces every resource bound to the instance together.

What the protocol gives you, and what the service must have

A live list pages with $top and $skip; a page the service cuts short is read to its end through @odata.nextLink. A pipeline asks for its page size with Prefer: odata.maxpagesize and follows the service’s links. Incremental passes use either ODATA_MODIFIED_AT (a ge filter on a timestamp property the pipeline names) or ODATA_DELTA_LINK (the service’s change tracking); a service that tracks no changes answers without a delta link, and that pass is refused by name.

Writes send POST, PATCH and DELETE, never PUT, with If-Match carrying the tag read first. Under VERSION_CHECKED the comparison and the service’s own check use the same tag, so a concurrent change is refused with 412 and not retried. A record the service serves without a tag is refused by name under VERSION_CHECKED, and is still sent If-Match: * under LAST_WRITER_WINS so that a deleted record is never re-created.

Actions are declared on the binding of the entity they act on, as SystemOfRecordActionCallShape.ODATA_BOUND_ACTION with the action’s qualified name, or ODATA_UNBOUND_ACTION with its action import. The engine calls them the way the protocol defines; whether the service exposes a given action is the declaration’s to know.

A vendor that deserves its own facet

Each vendor Wildo describes is one authored facet file in the @wildo-ai/systems-of-record library: the dialects it speaks, the address and credentials it needs, how it pages, whether it versions rows, how it partitions, which actions it accepts. Adding a vendor to the framework is adding such a file and its row in the library’s record. A package can also add a partition field or a further credential kind to a facet it does not own. That addition applies only to an instance that names the package in facetAugmentations, and two sources declaring the same partition or credential kind are refused at startup, naming both.

Building a B2B product or an internal tool?

Wildo is not self-service yet. Tell us what you have in mind and we will say plainly whether it fits, and what happens next.