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 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 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.