Skip to Content
APILanded Cost Route Contract

Landed Cost Route Contract

Status: Active
Version: v1
Last updated: 2026-04-16

This document is the normative contract for POST /api/trade/landed-cost.

Route-level request/response coverage now appears in the versioned public OpenAPI artifact (docs/api/openapi/rgl8r-public-api-v1.3.0.yaml) and in the public API contract pack (docs/api/public-api-contract-v1.md); route semantics are documented separately outside the versioned public API contract pack. This document remains the route-semantics companion because it captures quantity-aware behavior, provider-comparison rules, and configured-corridor gating that the machine spec summarizes but does not explain operationally.

Route and auth

  • Route: POST /api/trade/landed-cost
  • Auth: Clerk bearer token or integration auth (Authorization: Bearer ... or compatibility x-api-key + x-org-id)
  • In legacy mode, Customer-facing authority source is destination-aware: OPENCLAW for US/default, OPENCLAW_CBSA for Canada, OPENCLAW_HMRC for UK, OPENCLAW_TARIC for EU member states.
  • In configured quote mode, the route resolves tenant corridor policy before calling the landed-cost engine. Exact (originCountry, destinationCountry) corridor match wins; wildcard-origin (*, destinationCountry) is the fallback.
  • Catalog refresh uses the same destination-aware source selector as the public landed-cost route.

Configured-corridor precheck

If the tenant has one or more quote-corridor rows, the route is in configured mode and fails closed on corridors that do not have an enabled exact-or-wildcard match.

When that happens, POST /api/trade/landed-cost returns a route-local business outcome:

{ "authority": "internal", "status": "unconfigured_corridor", "breakdown": null, "reason": "no_enabled_matching_corridor", "metadata": { "policyMode": "configured", "originCountry": "CN", "destinationCountry": "US", "corridorConfigured": false } }

Important boundary:

  • unconfigured_corridor is a route/business-layer result, not a landed-cost engine status.
  • The shared engine contract remains authoritative | rate_not_available.
  • The public route records warning-style observability for this precheck failure, but does not record billable landed-cost usage when it happens.

Request contract

Required fields:

  • productValue
  • hsCode
  • originCountry
  • destinationCountry

Optional fields:

  • shippingCost
  • insurance
  • currency
  • quantity
  • quantityUnit
  • provider
  • description

Quantity-aware rules:

  • quantity is a positive decimal representing the total customs quantity for the shipment line matched to the HS code.
  • quantityUnit must be one of: KGM, LTR, DZN, TNE.
  • quantity and quantityUnit must be provided together
  • Quantity is preserved as provided in the first slice; no rounding or unit conversion occurs.

Currency rules:

  • currency is an optional 3-letter ISO 4217 code (e.g. EUR, USD, GBP).
  • When provided, the engine evaluates de minimis thresholds for the destination country. Without currency, de minimis evaluation is skipped.
  • If currency does not match the threshold currency for the destination, the response includes deMinimis.skipped: 'currency_mismatch' (no exemption applied, threshold still reported).
  • For EU destinations, currency also drives the low-value trust gate: EUR values at or below the €150 IOSS boundary return trust.level: 'indicative'; non-EUR currencies return trust.level: 'undetermined'.

Provider comparison rules:

  • provider enables tenant-scoped external comparison for legacy value-only requests.
  • provider comparison does not support quantity-aware landed-cost requests yet
  • Quantity-aware requests with provider return 400 INVALID_REQUEST.
  • Provider comparison only runs when currency is explicitly supplied and equals 'USD'. Omitting currency or supplying any non-USD value skips provider comparison (the response includes the internal result with providerSkipReason: 'non_usd_corridor').

Legacy field-name aliases (soft deprecation):

  • Unknown request fields are silently dropped by the schema. To catch common misnamings before they cause wrong responses, the route detects a known list of legacy aliases and emits a deprecation signal.
  • Detected aliases (all → canonical): shipping, freight, shipping_cost → shippingCost; insurance_cost → insurance; currencyCode, currency_code → currency; destination, destCountry, destinationCountryCode → destinationCountry; origin, originCountryCode → originCountry.
  • Behavior when any alias is detected:
    • Response includes an X-Deprecation-Hint header with a semicolon-separated list of use 'canonical' not 'alias' entries.
    • A warn-level structured log is emitted server-side with the request id, tenant id, and the detected aliases.
    • The request is not rejected. The schema still drops the aliased field, so the caller will get the canonical field’s default (e.g. shippingCost: 0) — the deprecation hint is the signal that the input was misnamed.
  • An alias is not flagged when the canonical field is also present (the canonical wins during parsing; the alias is just noise).
  • This is a warning, not a breaking change. A versioned strict request contract is tracked as a follow-up in docs/BACKLOG.md.

Example requests

Value-only request:

{ "productValue": 100.0, "hsCode": "6109.10.00", "originCountry": "CN", "destinationCountry": "US", "shippingCost": 50.0, "insurance": 10.0 }

Quantity-aware request:

{ "productValue": 100.0, "hsCode": "2207.20.00", "originCountry": "CN", "destinationCountry": "CA", "shippingCost": 50.0, "insurance": 10.0, "quantity": 2.0, "quantityUnit": "KGM" }

Provider comparison request:

{ "productValue": 100.0, "hsCode": "6109.10.00", "originCountry": "CN", "destinationCountry": "US", "provider": "zonos", "description": "Cotton knit t-shirt" }

Response notes

  • Authoritative and rate_not_available responses report the customer-facing source selected for the request destination.
  • unconfigured_corridor means the tenant is in configured quote mode but the requested corridor has no enabled exact-or-wildcard match. It is configuration debt, not missing tariff data.
  • Canada requests (destinationCountry=CA) report metadata.sourceCode = "OPENCLAW_CBSA" when the authoritative engine resolves through the CBSA source.
  • Quantity-aware duty-expression evaluation is supported for the first landed-cost CBSA formula slice only; unsupported expressions remain status: "rate_not_available" with metadata.unsupportedExpression = true.
  • Full quoteability in the advanced TRADE path requires both POST /api/compliance/check returning quoteReadiness = "quoteable" and POST /api/trade/landed-cost returning status = "authoritative". Compliance readiness and landed-cost authority are separate gates in Wave 1.

Contract boundaries

  • CLAUDE.md is a route index, not the full normative contract.
  • docs/postman/RGL8R-API.postman_collection.json is a first-party request example surface and must stay aligned with this document.
  • packages/mcp-server/src/tools/landed-cost.ts is a first-party tool surface and must stay aligned with this document.
  • apps/docs/content/public/... is generated from docs/ and must not be edited directly.