Pre-release contract · traffic remains off

Your supply network.
One Reach-X contract.

Stage catalogue, supplier offers and stock. Receive routed orders. Report fulfilment and returns. Reach-X controls moderation, checkout, payment, customer remedies and publication as merchant of record.

Contract statusv1
Canonical API host
api.reach-x.co.za
planned, not live
Authentication
HMAC-SHA256
Mutations
Idempotent by contract
Publishing
Reach-X moderated
Current environment
UAT only
Published contract
31 paths · 33 operations

Quickstart

From sandbox to a moderation-ready item

Partners submit authoritative source facts to Reach-X. Valid data enters staff moderation; it never bypasses the remaining commercial, media and customer-facing controls or creates a live listing on its own.
  1. 01

    Certify the sandbox

    Receive your issued API base URL, scoped test credential and synthetic supplier/location bindings.

  2. 02

    Verify context

    Sign GET /partner/v1/context and confirm the tenant, sandbox environment, scopes and bounds.

  3. 03

    Stage the catalogue

    Upsert product, variant and offer revisions with stable external IDs and retained idempotency keys.

  4. 04

    Send absolute stock

    Post ordered inventory observations, then reconcile gaps or conflicts before checkout availability.

  5. 05

    Submit for moderation

    Submit dependency-complete revisions. Reach-X completes commercial, media and customer-facing readiness.

  6. 06

    Operate the order

    Poll or receive notifications, acknowledge the order and append bounded fulfilment or return facts.

Integration kit

Send a real signed staging request, safely

Import the dependency-ordered Postman journey or use the exact curl signer. Both are generated from the same Bazaar contract, contain synthetic values only and stop at the Reach-X moderation boundary.

Kit 1.0.0-pre-release

8 downloads · 4 payload examples

manifest 86a319384668

Postman certification journey

Eight requests in dependency order: verify context, stage and submit product, variant and offer evidence, then report absolute stock. The collection signs the exact expanded bytes and captures revision IDs for you.

The environment starts with a non-routable host, blank IDs and an empty secret. Never export it after entering a credential.

Dependency-free curl signer

Give the Node helper an uppercase method, canonical partner path, exact JSON file and retained idempotency key. It prints a curl command by default and never prints the HMAC secret.

Remote sends require HTTPS and explicit --send. Ambiguous paths, query order, repeated keys and invalid replay IDs fail locally.

Schema-valid request bodies

Synthetic examples for inspection and adaptation. Replace every onboarding identifier before approved UAT.

Staging only · never publishes

Authentication

Sign the exact request you send

Each request carries a public key ID, UTC timestamp, single-use UUIDv4 nonce, body digest and an unpadded base64url HMAC-SHA256 signature. Mutations also require an idempotency key.

Canonical request

  1. 1reachx-partner-v1
  2. 2UPPERCASE_METHOD
  3. 3/partner/v1/path
  4. 4canonical=query&in=order
  5. 5lowercase_body_sha256
  6. 6idempotency_key_or_empty_for_reads
  7. 7lowercase_public_key_id
  8. 8UTC_TIMESTAMP
  9. 9lowercase_uuid_v4_nonce

Join these nine fields with newline characters and no trailing newline. Canonical query encoding is strict RFC 3986; duplicate or ambiguous parameters are rejected.

Node.js
import { createHash, createHmac, randomUUID } from "node:crypto";

const {
  REACHX_API_BASE,
  REACHX_KEY_ID,
  REACHX_SECRET,
  REACHX_SUPPLIER_ID,
  REACHX_SUPPLIER_LINK_ID,
} = process.env;
if (!REACHX_API_BASE || !REACHX_KEY_ID || !REACHX_SECRET ||
    !REACHX_SUPPLIER_ID || !REACHX_SUPPLIER_LINK_ID) {
  throw new Error("Missing issued Reach-X environment credentials");
}

const payload = {
  resource_type: "product",
  source_system: "your_connector",
  external_id: "PRODUCT-1001",
  supplier_id: REACHX_SUPPLIER_ID,
  partner_supplier_link_id: REACHX_SUPPLIER_LINK_ID,
  source_sequence: 1,
  facts: { name: "Example product", category_reference: "your-category" },
};
const method = "PUT";
const path = "/partner/v1/catalogue/items";
const query = "";
const body = JSON.stringify(payload); // preserve these exact UTF-8 bytes
const bodyHash = createHash("sha256").update(body).digest("hex");
const idempotencyKey = randomUUID(); // retain for an unchanged retry
const timestamp = new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
const nonce = randomUUID().toLowerCase();

const canonical = [
  "reachx-partner-v1",
  method,
  path,
  query,
  bodyHash,
  idempotencyKey,
  REACHX_KEY_ID.toLowerCase(),
  timestamp,
  nonce,
].join("\n");

const signature = createHmac("sha256", REACHX_SECRET)
  .update(canonical)
  .digest("base64url");

const response = await fetch(`${REACHX_API_BASE}${path}`, {
  method,
  headers: {
    "Content-Type": "application/json",
    "Idempotency-Key": idempotencyKey,
    "X-ReachX-Key-Id": REACHX_KEY_ID,
    "X-ReachX-Timestamp": timestamp,
    "X-ReachX-Nonce": nonce,
    "X-ReachX-Content-SHA256": bodyHash,
    "X-ReachX-Signature": signature,
  },
  body,
});

Safe retries

Use a fresh timestamp, nonce and signature, but retain the same idempotency key, body, method and target.

Fail closed

Stale lifecycle epochs, revoked bounds, signature replay and changed idempotent requests are rejected.

Stable v1

The /partner/v1 contract does not change when infrastructure moves from UAT to a future production estate.

API reference

The partner machine surface

These routes accept scoped machine credentials. Console-only credential and webhook administration use a separate human session and are intentionally not presented as machine endpoints here; the downloadable OpenAPI document contains the complete partner surface.

OpenAPI 3.1 · 1.0.0-pre-release

Bazaar source d86b637e79dd · artifact ce8848dd5ed6

Download JSON

Context

1 endpoint

GET/partner/v1/context

Verify the tenant, environment, lifecycle epochs, scopes and resource bounds behind this credential.

partner:context:read

Catalogue & offers

4 endpoints

PUT/partner/v1/catalogue/items

Stage an idempotent product or variant revision in your source namespace.

partner:catalogue:write
PUT/partner/v1/offers

Stage price, tax, availability and fulfilment evidence without publishing it.

partner:offers:write
GET/partner/v1/catalogue/revisions/{revisionID}

Read one immutable, tenant-scoped staged revision.

partner:catalogue:read or partner:offers:read
POST/partner/v1/catalogue/revisions/{revisionID}/submit

Submit a validated revision to Reach-X moderation. This never publishes it.

partner:catalogue:write or partner:offers:write

Inventory

4 endpoints

POST/partner/v1/inventory/updates

Send an absolute, ordered stock observation for a staff-bound offer and location.

partner:inventory:write
GET/partner/v1/inventory/positions

Reconcile your observations with the resource-bounded Reach-X position.

partner:inventory:read
GET/partner/v1/inventory/reconciliation-cases

List gaps, conflicts and operator work without reaching into the database.

partner:reconciliation:read
POST/partner/v1/inventory/reconciliation-cases/{caseID}/acknowledge

Attach bounded partner evidence to an open reconciliation case.

partner:reconciliation:write

Orders & fulfilment

7 endpoints

GET/partner/v1/orders

Poll the minimum PII-free order projection routed to your supply relationship.

partner:orders:read
GET/partner/v1/orders/{orderID}

Read reservation-bound lines and, when separately authorized, shipment evidence.

partner:orders:read
POST/partner/v1/orders/{orderID}/acknowledge

Accept or reject an order once. Rejection opens a Reach-X remedy; it never refunds the customer.

partner:orders:write
POST/partner/v1/orders/{orderID}/shipments

Create a quantity-bounded package for an accepted order.

partner:fulfilment:write
POST/partner/v1/shipments/{shipmentID}/events

Append ordered packed, dispatched, delivered or delivery-failed facts.

partner:fulfilment:write
GET/partner/v1/orders/{orderID}/remedies

Read cancellation or return work for one authorized order.

partner:returns:read
POST/partner/v1/remedies/{remedyID}/events

Append an ordered cancellation or physical-return fact for Reach-X resolution.

partner:returns:write

Outbound webhooks

Notifications you can verify and replay safely

Reach-X signs the exact stored body for each ordered delivery. Your receiver deduplicates by delivery ID, rejects stale timestamps and compares signatures in constant time.
  • order.available
  • order.cancellation.requested
  • order.cancellation.resolved
  • order.return.requested
  • order.return.resolved

Endpoint security

HTTPS only, redirects disabled, public-destination verification on every connection, signed challenge activation, bounded key overlap, ordered retries and immutable dead-letter evidence.

Verification response

For an endpoint.verification event, return any 2xx status and copy data.challenge exactly into the ReachX-Webhook-Challenge response header.

Node.js
import { createHash, createHmac, timingSafeEqual } from "node:crypto";

function requireHeader(headers, name) {
  const value = headers[name];
  if (typeof value !== "string" || value.includes(",,") || value.length > 512) {
    throw new Error(`Missing or malformed ${name}`);
  }
  return value;
}

const deliveryId = requireHeader(headers, "reachx-webhook-id");
const timestamp = requireHeader(headers, "reachx-webhook-timestamp");
const signatures = requireHeader(headers, "reachx-webhook-signature");
if (!/^\d{10}$/.test(timestamp) ||
    Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
  throw new Error("Reject stale webhook");
}
const bodyHash = createHash("sha256").update(rawBody).digest("hex");
const signed = `v1.${deliveryId}.${timestamp}.${bodyHash}`;
// Keep only the current secret and an unexpired rotation predecessor.
const expected = webhookSecrets.map((secret) =>
  createHmac("sha256", secret).update(signed).digest("hex")
);

const valid = signatures
  .split(",")
  .map((value) => value.trim())
  .filter((value) => value.startsWith("v1="))
  .some((value) => {
    const received = Buffer.from(value.slice(3), "hex");
    return expected.some((digest) => {
      const wanted = Buffer.from(digest, "hex");
      return received.length === wanted.length && timingSafeEqual(received, wanted);
    });
  });
if (!valid) throw new Error("Reject invalid webhook");

// Persist deliveryId before applying side effects; return 2xx for duplicates.

Operating contract

The boundaries that keep commerce safe

A partner supplies catalogue and operational facts. Reach-X remains responsible for the commercial customer journey.

Partner authority

  • Submit source catalogue, offers and absolute stock
  • Acknowledge routed supplier orders
  • Report package, tracking, cancellation and return facts
  • Reconcile and retain stable external identifiers

Reach-X authority

  • Moderate and publish customer-facing listings
  • Control checkout, payment and commercial order state
  • Resolve cancellations, returns and customer refunds
  • Calculate commission, supplier payables and settlement

Certification first

No credentials or endpoints are live yet.

UAT access, exact environment base URLs and test credentials are issued during controlled onboarding. Never infer an environment hostname or send real customer data to a sandbox.

Activation pending