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.
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
fims_sf_* keys). The Partner API is the back-office surface.
Quickstart
- In the Foundry admin: Settings → API Keys → Create. Pick the permission scopes your integration needs — the key gets exactly those, nothing more.
- Save the
fims_*key somewhere safe — it is shown once, and can be set to expire or be revoked anytime. - 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 roughly 100 KB. Individual field values are not limited — a single large JSON custom field is fine — but batching many records into one write is where you will hit this. Split large writes rather than growing one payload.
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 channelorder.status_changed— an order moves through its lifecycleorder.shipped— a shipment is created (carrier, tracking)inventory.availability_changed— available quantity shifts on a variantimport.completed— a supplier feed / CSV import finishes (the bulk-change signal)purchase_order.createdpurchase_order.status_changed—toStatus: "RECEIVED"is the goods-received momentproduct.createdproduct.updated— manual catalog edits, incl. archive/restore; trashing carriesdeleted: true. Import-driven changes signal viaimport.completedinsteadvariant.cost_changed— can burst during supplier imports; expect volumeinvoice.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.