Docs navigation
Home / Docs / Partner API

Partner API

Build integrations and apps against a Foundry organization's back office — accounting sync, EDI, analytics, custom tooling. The Partner API covers catalog, sales orders, purchase orders, stock, and outbound webhooks, authenticated with scoped admin API keys.

Open the interactive API explorer → Live OpenAPI reference with every endpoint, schema, and a “try it” console. Raw spec: partner-json (OpenAPI 3, for codegen or Postman).

What you get

  • • Full read/write access to products, variants, brands, and categories (scope-gated per key)
  • • Sales orders and inventory reservations across every connected sales channel
  • • Purchase orders, vendor invoices, and goods-received events
  • • Stock levels and adjustments per warehouse
  • • 11 signed outbound webhook events (Stripe-style HMAC) — react instead of polling
Building a storefront instead? Cart, checkout, and customer-facing catalog reads live on the separate Storefront API (channel-scoped fims_sf_* keys). The Partner API is the back-office surface.

Quickstart

  1. In the Foundry admin: Settings → API Keys → Create. Pick the permission scopes your integration needs — the key gets exactly those, nothing more.
  2. Save the fims_* key somewhere safe — it is shown once, and can be set to expire or be revoked anytime.
  3. Hit the API. All routes live under https://api.foundryims.com/api/v1.
curl https://api.foundryims.com/api/v1/products?limit=5 \
  -H "Authorization: Bearer fims_xxx"

Authentication & scopes

Every request needs Authorization: Bearer fims_xxx. Keys carry per-key permission scopes chosen at creation; a request must hold the scope listed on the endpoint's permission gate in the API explorer.

  • 401 — missing/malformed header, or invalid/revoked/expired key
  • 403 — valid key, but it lacks the scope this endpoint requires

Knowing which organization you are talking to

A key belongs to exactly one organization. GET /orgs/me returns it — including its id — and needs no particular permission, so any key can call it. Apps installed over OAuth also receive org_id in the token response.

Don't infer it from a catalog record: an organization with an empty catalog returns nothing to infer from, which is exactly the state of a merchant who installs a tool before importing their data.

Rate limits

300 requests/min per API key, across all endpoints. That ceiling is enforced per serving instance, so the effective limit is somewhat higher and varies — pace off the response headers rather than modelling the number. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (unix seconds). Exceeding the limit returns:

HTTP/1.1 429 Too Many Requests
Retry-After: 21
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1753142460

{ "statusCode": 429, "message": "Rate limit exceeded", "retryAfterSeconds": 21 }

Back off until the reset rather than retrying immediately. Need more headroom for a launch or backfill? Talk to us.

Request size

Request bodies are capped at 10 MB (JSON and form-encoded). That comfortably fits a full warehouse stock count or a several-thousand-line bulk write in one request; a larger payload gets a 413. Split writes beyond that rather than growing one payload — and for catalog loads, use an import, which streams files of any size.

Pagination

List endpoints take limit and offset query params (max page size 250) and return:

{
  "data": [ { ... }, { ... } ],
  "meta": { "total": 1284, "limit": 50, "offset": 0 }
}

Errors

Errors use the standard envelope — statusCode, message (a string, or an array of field problems on validation failures), error:

{
  "statusCode": 400,
  "message": [ "sku must be a string", "price must not be less than 0" ],
  "error": "Bad Request"
}

Some domain errors add a machine-readable code field (e.g. DUPLICATE_SKU) plus contextual fields — treat unknown extra fields as informational.

Outbound webhooks

Skip polling. Create an endpoint with POST /webhooks (the signing secret whsec_* is returned once) and subscribe to any of the events below — GET /webhooks/events always lists the live catalog.

Events

  • order.created — a sales order lands from any connected channel
  • order.status_changed — an order moves through its lifecycle
  • order.shipped — a shipment is created (carrier, tracking)
  • inventory.availability_changed — available quantity shifts on a variant
  • import.completed — a supplier feed / CSV import finishes (the bulk-change signal)
  • purchase_order.created
  • purchase_order.status_changedtoStatus: "RECEIVED" is the goods-received moment
  • product.created
  • product.updated — manual catalog edits, incl. archive/restore; trashing carries deleted: true. Import-driven changes signal via import.completed instead
  • variant.cost_changed — can burst during supplier imports; expect volume
  • invoice.created — a vendor invoice is recorded against a PO

Delivery shape

POST <your webhook URL>
Headers:
  Content-Type:          application/json
  X-Foundry-Event:       purchase_order.status_changed
  X-Foundry-Delivery-Id: dlv_abc123
  X-Foundry-Signature:   t=1753142400,v1=<hex hmac sha256>
Body:
  { "event": "purchase_order.status_changed", "data": { ... }, "delivery_id": "dlv_abc123", "attempt": 1 }

Verifying signatures

Compute HMAC-SHA256 over {t}.{rawBody} with your endpoint secret (the t comes from the signature header itself) and constant-time-compare against v1. Reject timestamps older than ~5 minutes.

import crypto from "crypto";

function verify(req, secret) {
  const sig = req.headers["x-foundry-signature"]; // "t=1753142400,v1=abc..."
  const t        = sig.match(/t=(\d+)/)?.[1];
  const received = sig.match(/v1=([0-9a-f]+)/)?.[1];
  if (!t || !received) return false;
  // Reject stale timestamps (~5 min) to prevent replay
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${req.rawBody}`)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(received, "hex"),
  );
}

Retries & reliability

Respond 2xx within 10 seconds. Failed deliveries retry with backoff — up to 8 attempts over ~2 days — and an endpoint that fails 5 consecutive deliveries is auto-disabled. Inspect and replay via GET /webhooks/:id/deliveries and POST /webhooks/deliveries/:deliveryId/replay; rotate the secret with POST /webhooks/:id/rotate-secret. Always dedup on X-Foundry-Delivery-Id.

Versioning & breaking changes

All routes live under /api/v1. Breaking changes bump the API's major version, are listed under a Breaking heading in the changelog, and get an announced migration window. Additive changes — new fields, new endpoints, new webhook events — ship without notice, so build clients that tolerate unknown fields.

Building an app for Foundry customers?

Partner apps get one-click installs: Foundry provisions a scoped API key and webhook endpoint per organization automatically, and your app appears in every customer's admin. We work with integration partners directly — book a call and tell us what you're building.