Skip to content

Ecommerce API and event contract

This contract specializes the common HTTP rules in the platform specification. The implementation repository must publish machine-readable OpenAPI and JSON Schemas that do not weaken this page.

Pilot reference downloads:

HTTP conventions

  • Base path is /api/v1.
  • OAuth/OIDC platform identity is required; Odoo and source credentials never reach the browser.
  • Tenant resources include /tenants/{tenant_id} and enforce membership, role, and assignment server-side.
  • Money is a decimal string plus ISO 4217 currency.
  • Timestamps are UTC RFC 3339; source business date and IANA timezone are explicit.
  • List endpoints use opaque cursor pagination with deterministic ordering.
  • Mutable review resources expose resource_version and require If-Match.
  • Commands require Idempotency-Key, reject unknown properties, persist before queue delivery, and return 202 Accepted.
  • No payload may select an arbitrary source adapter, SQL relation, Odoo model, method, fields, domain, or context.

Resource catalog

Source and ingestion operations

GET  /api/v1/operations/tenants/{tenant_id}/source-capabilities
POST /api/v1/operations/tenants/{tenant_id}/source-capability-scans
GET  /api/v1/operations/source-capability-scans/{scan_id}
GET  /api/v1/operations/tenants/{tenant_id}/ingestion-runs
POST /api/v1/operations/tenants/{tenant_id}/ingestion-runs
GET  /api/v1/operations/ingestion-runs/{run_id}
POST /api/v1/operations/ingestion-runs/{run_id}/replay

Commerce resources

GET  /api/v1/tenants/{tenant_id}/commerce/orders
GET  /api/v1/tenants/{tenant_id}/commerce/orders/{order_id}
GET  /api/v1/tenants/{tenant_id}/commerce/orders/{order_id}/events
GET  /api/v1/tenants/{tenant_id}/commerce/payments
GET  /api/v1/tenants/{tenant_id}/commerce/fulfillments
GET  /api/v1/tenants/{tenant_id}/commerce/refunds
GET  /api/v1/tenants/{tenant_id}/commerce/returns
GET  /api/v1/tenants/{tenant_id}/commerce/disputes

Policy, mapping, and posting commands

GET  /api/v1/tenants/{tenant_id}/mapping-sets
GET  /api/v1/tenants/{tenant_id}/mapping-exceptions
POST /api/v1/tenants/{tenant_id}/commerce/orders/{order_id}/posting-intents
POST /api/v1/tenants/{tenant_id}/commerce/posting-intents/{intent_id}/approve
POST /api/v1/tenants/{tenant_id}/commerce/posting-intents/{intent_id}/execute
GET  /api/v1/tenants/{tenant_id}/operations/{operation_id}

Settlement and reporting resources

GET  /api/v1/tenants/{tenant_id}/settlements
GET  /api/v1/tenants/{tenant_id}/settlements/{settlement_id}
POST /api/v1/tenants/{tenant_id}/payout-matches/{match_id}/approve
POST /api/v1/tenants/{tenant_id}/payout-matches/{match_id}/post
GET  /api/v1/tenants/{tenant_id}/reports/sales
GET  /api/v1/tenants/{tenant_id}/reports/refunds
GET  /api/v1/tenants/{tenant_id}/reports/fees
GET  /api/v1/tenants/{tenant_id}/reports/settlements
GET  /api/v1/tenants/{tenant_id}/reports/posting-health
GET  /api/v1/tenants/{tenant_id}/reports/inventory
GET  /api/v1/tenants/{tenant_id}/reports/data-freshness

The domain contracts define the complete command preconditions and response evidence.

Command envelope

{
  "contract_version": "1.0",
  "resource_version": 7,
  "reason": "Approved Shopify pilot order",
  "requested_at": "2026-07-02T10:00:00Z"
}

The tenant comes from the authorized route binding, not a duplicate body property. The server adds operation_id, correlation_id, actor, request hash, policy/mapping versions, and trusted connection IDs.

Command response:

{
  "contract_version": "1.0",
  "operation_id": "0190f5d4-7d75-7d22-9c7d-45d20e89ce6b",
  "status": "queued",
  "correlation_id": "corr_01J1Z8K0Q6VTX6V6YQ0B42T9N5",
  "links": {
    "self": "/api/v1/tenants/018f.../operations/0190..."
  }
}

Reusing an idempotency key with the same canonical request hash returns the existing operation. Reusing it with a different hash returns 409 IDEMPOTENCY_KEY_REUSED.

Event envelope

{
  "event_id": "0190f5dc-2f6b-7dc4-a609-fb9bfab163dd",
  "event_type": "commerce.order.canonicalized.v1",
  "schema_version": "1.0.0",
  "tenant_id": "018f...",
  "aggregate_type": "commerce_order",
  "aggregate_id": "0190...",
  "occurred_at": "2026-07-02T09:58:00Z",
  "observed_at": "2026-07-02T10:00:00Z",
  "correlation_id": "corr_01J1Z8K0Q6VTX6V6YQ0B42T9N5",
  "causation_event_id": null,
  "producer": "canonicalizer",
  "payload": {}
}

event_id is globally unique. Consumer deduplication uses (consumer, event_id) and occurs before side effects. Events may be delayed, duplicated, or delivered out of order. Consumers use aggregate version/evidence and never assume broker order is business order.

Event catalog

Event type Emitted when Required consumers
source.delivery.accepted.v1 Webhook/Airbyte record is durably accepted Canonicalizer, ingestion metrics
source.capability.changed.v1 Source capability status/evidence changes Enablement, operations
commerce.order.canonicalized.v1 Canonical order projection commits Policy/mapping evaluator, marts
commerce.payment.observed.v1 Payment transaction projection commits Settlement allocator, marts
commerce.fulfillment.observed.v1 Fulfillment projection commits Posting policy, inventory, marts
commerce.refund.observed.v1 Refund projection commits Refund policy, settlement, marts
commerce.return.observed.v1 Return projection commits Inventory workflow, marts
commerce.dispute.changed.v1 Dispute state/evidence changes Accounting review, settlement
mapping.set.approved.v1 Mapping set becomes immutable/active Intent builder, drift monitor
posting.intent.ready.v1 Intent passes policy/mapping checks Approval/operation service
integration.operation.changed.v1 Durable operation status changes UI projection, verification, marts
odoo.readback.confirmed.v1 Odoo result passes invariants Canonical projection, settlement, marts
settlement.balanced.v1 Settlement equation balances Payout matcher, marts
payout.reconciled.v1 Bank/Odoo clearing readback confirms Close workflow, marts
warehouse.mart.refreshed.v1 A mart partition passes controls and publishes Reporting cache, freshness monitoring
data.subject.restriction.changed.v1 Governance restriction changes Export/mart redaction

Domain events are facts, not commands. Consumers cannot infer authorization to mutate Odoo from event receipt.

Error contract

{
  "error": {
    "code": "MAPPING_REQUIRED",
    "message": "A required approved mapping is missing.",
    "correlation_id": "corr_01J1Z8K0Q6VTX6V6YQ0B42T9N5",
    "retryable": false,
    "details": {
      "mapping_type": "tax",
      "action": "review_mapping_exception"
    }
  }
}

Error messages expose safe business context only. They never include credentials, webhook URLs, raw payloads, SQL, Odoo tracebacks, or another tenant's identifiers.

Status Meaning
400 Malformed JSON, unsupported property, invalid cursor
401 Missing/invalid platform identity
403 Identity lacks tenant/role/action permission
404 Resource absent or concealed by isolation
409 Idempotency mismatch, stale version, conflicting active operation
422 Valid shape but failed business/accounting invariant
429 Tenant/user/provider rate limit
502 Definite upstream rejection before mutation
503 Dependency unavailable before mutation
202 Durable asynchronous command accepted

Compatibility

  • Additive optional response/event properties are backward compatible within a major version.
  • New required command properties, changed amount/sign/identity semantics, removed fields, or changed state meanings require a new major contract.
  • Producers publish the current and previous compatible event schema during rollout where consumers need migration time.
  • Consumers reject unsupported major versions and quarantine invalid payloads.
  • Schemas are registered by immutable (event_type, schema_version, sha256) and validated in CI and at ingress.

Acceptance criteria

  • OpenAPI and JSON Schemas validate all examples and reject unknown command properties.
  • Tenant and role negative tests cover every resource and command.
  • Idempotency replay, stale If-Match, pagination stability, and version negotiation are tested.
  • Event producer/consumer tests cover duplicate, delay, out-of-order, invalid schema, and unsupported version.
  • No HTTP or event payload permits arbitrary Odoo/source execution.