Connect an ERP
The commonest integration: a system of record on one side, Forge on the other. Stock and prices flow in; orders flow out.
The shape
Section titled “The shape” ERP ──(commands)──▶ Forge stock levels, prices, catalogue ERP ◀──(events)─── Forge orders, payments, shipmentsTwo directions, two mechanisms, and they are not interchangeable. Pushing is a command; hearing is an event.
1. A key for the integration, and only for it
Section titled “1. A key for the integration, and only for it”Issue one named for the system — ERP Nimbus — with the scopes it actually needs. See
issue & rotate API keys.
Do not reuse a key that something else also uses. Rate limits are per credential, revocation is per key, and the audit trail is only useful if the name means one thing.
2. Push with the SDK
Section titled “2. Push with the SDK”import { createClient } from '@forgecommerce/sdk';
const forge = createClient({ baseUrl: process.env.FORGE_URL!, token: process.env.FORGE_TOKEN! });
const result = await forge.call( 'inventory.set_level', { sku_id: 'sku_…', warehouse_id: 'wh_…', quantity: 42 }, { idempotencyKey: `stock-${sku}-${syncRunId}` },);
if (!result.ok) { // 'conflict' — the state will not have it; retrying unchanged changes nothing // 'forbidden' — the key lacks the scope; a different key, not a retry // 'internal' — retry with backoff}3. Hear with events
Section titled “3. Hear with events”Every command declares the events it emits, and they are written in the same transaction as the change. If the order exists, the event exists. See events and the outbox.
Your consumer will see the same event twice. Delivery is at-least-once — after a retry, a redeploy, a timeout that succeeded on the far side. So make the handler idempotent: key your side-effect on something in the event, not on the fact that you received it.
async function onOrderPaid(event) { if (await erp.hasInvoiceFor(event.order_id)) return; // seen it await erp.createInvoice(event.order_id);}4. Rate limits are a contract, not a surprise
Section titled “4. Rate limits are a contract, not a surprise”Every authenticated response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset — on
success, not only on a 429. Read them in your client and slow down before you are told to.
The subject is the credential per tenant, so your ERP’s budget is its own: a nightly sync running hot cannot take the storefront’s other integrations down.
5. What not to do
Section titled “5. What not to do”Do not poll for changes you could hear as events. It costs your rate budget and it is always behind.
Do not write around the port. There is no direct database access, and there is no second write path to find. Everything an integration needs is a command; if something seems to be missing, it is a conversation rather than a workaround.
Do not put business rules in the ERP that the kernel should ask about. If Forge needs to know something only your system can answer — which warehouse serves this order, whether this customer gets this price — that is a decision, answered inside the transaction, not a value you sync ahead of time and hope is current.
Where to look when it breaks
Section titled “Where to look when it breaks”- The audit trail in the admin, filtered by the key’s name. Every call is there, refusals included.
whoami— settles “does this key still carry what I think”.- The errors page — six codes, and each one means a different thing to do.