Systems
Brightpearl integration with your online store: orders in, stock per warehouse out
Brightpearl answers 503 Too Busy above 200 API requests a minute per account and discards the request, and its webhooks default to at-most-once delivery. We build the integration around those rules and your warehouses, price lists and channels, then host and run it.
What Brightpearl is and who runs it
Brightpearl calls itself a retail operating system: order management, inventory, warehouse, purchasing and accounting for multichannel merchants and wholesalers. It is part of Sage Group plc. Its customers sell through a web store, marketplaces, a wholesale channel and sometimes their own shops, and run stock from one or more warehouses.
Each account lives in a datacenter, and the datacenter is part of every API address:
https://euw1.brightpearlconnect.com/public-api/{account}/order-service/sales-order
https://use1.brightpearlconnect.com/public-api/{account}/warehouse-service/product-availability/1006-1008
The API is split into services: contact, order, product, pricing, warehouse, accounting and integration. An account ID such as mycompany sits between the domain and the service.
What connects to Brightpearl
Brightpearl owns products, price lists, stock per warehouse and the accounting side of an order. The store owns the checkout.
| Flow | Direction | Brightpearl resource |
|---|---|---|
| Orders | store to Brightpearl | order-service/sales-order with externalRef and channelId |
| Customers | store to Brightpearl | contact-service/contact |
| Payments | store to Brightpearl | accounting-service/customer-payment |
| Stock per warehouse | Brightpearl to store | warehouse-service/product-availability |
| Products and prices | Brightpearl to store | product-service/product, price lists |
| Shipments and tracking | Brightpearl to store | goods-out notes, order shipping status |
| Returns | store to Brightpearl | order-service/sales-credit |
A sales order carries a customer.id, a warehouseId for fulfilment, a channelId that separates the web store from marketplace orders, and a priceListId. Without a price list, Brightpearl uses the customer's assigned one. priceModeCode is INC or EXC and overrides whether the price list includes tax, which has to match how the store sends its line prices. Every row needs a taxCode, and net and tax take at most 4 decimal places.
How Brightpearl exposes data
Authentication
An integration connects through an OAuth instance app created in a Brightpearl developer account. The authorization code flow returns an access token with its expires_in, a refresh token, an api_domain and an installation_instance_id. The authorization code itself expires after 2 minutes. When Brightpearl moves an account, the next call returns 401 and the refresh response carries the new domain, so the integration refreshes and updates the domain without a code change.
A private app, with brightpearl-app-ref and brightpearl-account-token headers, is meant for one-off scripts. Brightpearl's help centre says it should not be used to integrate another system, and private system apps are being sunsetted.
200 requests a minute, then 503
Brightpearl throttles at 200 requests a minute per account. Private apps on the account share that pool; a public app has its own pool per account and developer. Over the limit, requests in the rolling minute get 503 Too Busy, not 429, and Brightpearl discards them. Every response carries the budget:
HTTP/1.1 200 OK
brightpearl-requests-remaining: 37
brightpearl-next-throttle-period: 18450
The first header runs from 199 to 0, the second is milliseconds to the next period. After a 503 the only certain wait is 60 seconds. The API throttling and backoff note covers the pattern.
Worked through for a peak hour of 1,500 web orders at 3 calls each (contact lookup, sales order, customer payment): 4,500 calls. At Brightpearl's own safe spacing of 60 / 200 = 0.3 seconds a call, that is 1,350 seconds, 22 minutes 30 seconds of the account's whole capacity in that hour. If a 3PL connector and a marketplace app run as private apps on the same pool, they lose those 22 minutes too. We read brightpearl-requests-remaining on every response, keep orders ahead of stock reads in the queue, and move bulk reads to ID sets.
ID sets
Most GET messages accept an ID set instead of one ID: a range 1000-1248, an ordered list 1000,1200,1247, an unordered list with dots, or a mix. IDs that do not exist are skipped without an error. A stock read for products 1000 to 1248 is one request, not 249. Writes that take an ID set return 207 when some resources fail, and the integration reads the result per resource.
Webhooks and quality of service
A webhook is a template: subscribeTo names the resource, uriTemplate and bodyTemplate describe the call. Two settings decide reliability. idSetAccepted sends several IDs in one event and is recommended on. qualityOfService is 0 by default, at-most-once, retried only when the receiver answers 503. Set to 1, delivery is at-least-once and retried unless the receiver answers 4xx. On the default, an event whose delivery times out is not retried. We register with 1 and run a reconciliation poll next to it.
Where a Brightpearl integration gets stuck
Duplicate orders after a timeout. A 503 is safe to resend because Brightpearl discarded it. A timeout is not: the order may exist. externalRef together with installedIntegrationInstanceId is a unique key, and a second POST with the same pair returns 200 as a duplicate and changes nothing.
The store order number goes into externalRef on every order. Two integration instances posting the same store, for example an old and a new one during a switch, do not share that key, so the old one is stopped per channel before the new one writes. More in duplicate customers and orders.
Which stock counts. product-availability returns inStock, onHand, allocated and inTransit per warehouse, and per location with includeOptional=breakDownByLocation. In Brightpearl's examples onHand is inStock minus allocated: 10 in stock with 1 allocated gives 9 on hand. Only stock-tracked products appear; an ID for a non-stock product is ignored, so a missing product in the response is not zero. The integration sums the warehouses each store may sell from. See multi-location inventory.
Quantities and bundles. A row quantity must be a whole number for stock-tracked products and bundles. A store that sells by the meter or in decimal weights needs a unit rule that turns the store quantity into whole Brightpearl units before the order is posted.
Platforms we integrate with Brightpearl
The integration is built on your account: warehouses, channels, price lists and tax codes. What the store side needs is on each platform page:
- Shopify: orders per Shopify store as a channel, stock per Shopify location from Brightpearl warehouses.
- Magento: inventory sources against warehouses, customer groups against price lists.
- BigCommerce and WooCommerce: orders with tax inclusive or exclusive prices matched to
priceModeCode.
For the wider ERP picture see e-commerce ERP integration and Shopify ERP integration; for another inventory system, Cin7. We build the integration for a fixed price, then host, run and monitor it for a monthly fee, with a dashboard, logs and a per-run diff. Before go-live it runs as a dry run that writes nothing to Brightpearl. Tell us which channels and warehouses run on your Brightpearl.
Frequently asked questions
Is the 200 requests a minute shared with our other apps?
It depends on how the apps connect. All private apps on an account share one pool of 200 requests a minute, so a reporting script can throttle the order integration. A public app built on an OAuth instance has its own pool per account and developer. We connect through an instance app, so other developers' traffic does not take from our budget.
Can we connect with a private app token?
Brightpearl's help centre says a private app is for simple, one-off hits on the API and should not be used to integrate another system; integrations should use an OAuth instance app. Private system apps are being sunsetted. A private staff app still works for a script that reads data once.
What happens to an order when Brightpearl is busy?
Above 200 requests in a minute Brightpearl answers 503 Too Busy and discards the request, so nothing is half-created. The order stays in our queue and is sent again in the next minute. Each order carries the store order number as externalRef, so a resend after a timeout cannot create a second order.
Does Brightpearl send webhooks for orders and stock?
Yes, for resources such as orders, products and goods-out notes. A webhook is created with a quality of service: 0 is at-most-once and the default, 1 is at-least-once and retries everything except 4xx answers. We register with 1 and idSetAccepted true, and still run a poll next to them, as described in webhooks are not a guarantee.
Which stock number goes to the store?
Brightpearl returns inStock, onHand, allocated and inTransit per warehouse, and per location on request. In Brightpearl's own examples onHand is inStock minus allocated, which is what can still be sold. Which warehouses count for which store, and whether a buffer comes off, is set per channel. See which stock counts.
Tell us what you need
Tell us which systems you use and what needs to work better. We reply personally within one working day. We help you define the work, then agree the price and timing before you commit.
- You know the price and the timeline before you commit to anything
- We work out the connection that takes the work off your hands
- We handle the build, hosting, monitoring and maintenance
- Your team has less information to retype and fewer discrepancies to chase