Inventory API
The Inventory endpoints expose the records that Bookkeep Inventory keeps for a Shopify store. They let a partner app or an in-house integration create purchase orders, read what was ordered and received, and follow the bills that result.
Objects
Supplier. Who the store buys from. Suppliers carry contact details and addresses, so treat them as personal data.
Purchase order. What was ordered from one supplier, as line items with quantities and costs. See Purchase order statuses for how its status moves. The API creates and edits drafts and can cancel; receiving happens in Bookkeep Inventory.
Receipt. What arrived against a purchase order, with any landed-cost fees. Receiving a purchase order moves the stock from incoming to available in Shopify. API keys see committed receipts only.
Bill. The vendor bill built from a receipt and posted to the store's accounting platform. Bills are read-only and are listed per receipt.
A supplier is the business the merchant orders stock from, as set up in Bookkeep Inventory. A vendor is the matching record in the merchant's accounting platform, which a bill is posted to.
Purchase order statuses
A purchase order's status is one of nine values.
| Status | Meaning |
|---|---|
draft | Being prepared. Purchase orders created through the API start here, and only a draft's lines and dates can be updated through the API. |
ordered | Placed with the supplier. Bookkeep adds the ordered stock as incoming in Shopify. |
partial | Some of the ordered quantity has been received (or rejected) on receipts. |
received | All of the ordered quantity has been received (or rejected) on receipts, but at least one receipt has not finished updating stock in Shopify. |
committed | All of the ordered quantity has been received, and every receipt has finished updating stock in Shopify. |
cancelled | Cancelled before any stock was received. |
in_transit | Stores that receive through Shopify transfers: the order was sent to Shopify as transfers and the stock is on its way to the stores. |
partially_transferred | Stores that receive through Shopify transfers: some stock has been received at the stores. |
transferred | Stores that receive through Shopify transfers: every transfer is closed in Shopify and stock was received. |
received and committed differ only in whether the receipts have finished. API keys see committed receipts only, so a received purchase order can return fewer receipts than it has, or none, until they commit. A receipt whose stock update in Shopify failed stays uncommitted, and hidden from API keys, until it is retried and succeeds. A voided receipt can move a purchase order back, for example from committed to partial.
bill_status and bills_count answer different questions. bill_status is unbilled, partially_billed or billed depending on how many of the purchase order's committed receipts are on a bill that is posted to the accounting platform. bills_count counts the purchase order's bills in any state. If its only bill failed to post, a purchase order is unbilled while bills_count is 1.
API keys and scopes
Inventory endpoints take an API key, not the publishable and secret key pair the rest of this API uses. A merchant creates the key under Settings → API keys in the Bookkeep section of the Bookkeep app and chooses its scopes. Each key belongs to one Shopify store and only ever sees that store's data.
Send the key with HTTP Basic authentication: the key is the username, and the password is ignored, so leave it empty or use a placeholder such as x. Requests must use HTTPS.
curl -L 'https://api.bookkeep.com/v1/entities/<entity_id>/purchase_orders' -u '<api-key>:x'
| Scope | Allows |
|---|---|
purchase_orders:read | List purchase orders and fetch one by uuid, in any status, with their line items and supplier. |
purchase_orders:write | Create draft purchase orders, update a draft's line items and dates, and cancel a purchase order. |
purchase_order_receipts:read | List a purchase order's receipts and fetch a receipt by uuid. Also needed, together with purchase_orders:read, for include=receipts on the purchase order read endpoints. Only committed receipts are returned. |
suppliers:read | List suppliers and fetch one by uuid, including contact details. |
bills:read | List the bills for a receipt and fetch a bill by uuid. |
Entity ID
Every Inventory path starts with /v1/entities/{entity_id}. Use the Entity ID shown under Settings → Account in the Bookkeep section of the Bookkeep app. The key's store must be linked to that entity.
Query parameters
The list endpoints use two styles of query parameter.
Purchase orders use filter[...], fields[...] and include:
filter[<field>_<predicate>]=<value>filters on any field of the purchase order itself, includingbill_status,bills_countand text fields such asnote(filter[note_eq]=...). Common predicates areeq,not_eq,in(send a list, for examplefilter[status_in][]=ordered&filter[status_in][]=partial),gt,gteq,lt,lteq,cont(contains, ignoring case),start,end,nullandpresent. Line item, receipt and supplier fields cannot be filtered. An unsupported filter or predicate returns 400 with anerrorthat names it, for exampleUnsupported filter: line_items_sku_eq.include=line_items,receipts,supplieradds related records. An unknownincludevalue returns 400.fields[purchase_order]=uuid,status,totalreturns only the listed fields.fields[line_item],fields[receipt]andfields[supplier]work the same way but need the matchinginclude. Unknown field names are dropped without an error.
Suppliers, receipts and bills take simple parameters: hidden, search and updated_after on suppliers, and status on receipts and bills (on bills it filters the bill's state). They also accept the same filter[...], fields[...] and include parameters on their own fields, for example filter[bill_number_eq]=BILL-1001 on bills.
updated_after works on Suppliers only. Receipts and Bills ignore it and return every record; use filter[updated_at_gteq]=<date> on those instead.
Other query parameters that an endpoint does not take are ignored rather than rejected. For example, ?status=ordered on the purchase orders list returns every status; use filter[status_eq]=ordered.
Pagination
List responses carry meta.page, meta.per_page and meta.total. Request further pages with page.
| Endpoint | Default per_page | Maximum per_page |
|---|---|---|
| Purchase orders | 100 | 100 |
| Suppliers, receipts, bills | 50 | 250 |
A per_page above the maximum is treated as the maximum, and one below 1 as the default; the response's meta.per_page shows the value used. A page below 1, or one that is not a number, is treated as 1.
Postman collection
You can download a Postman collection for these endpoints from Settings → API keys in the Bookkeep section of the Bookkeep app. Add your API key to the collection's key variable to start sending requests.
Rate limit
The Inventory endpoints share one rate limit: 60 requests per minute per source IP address, counted across Purchase Orders, Suppliers, Receipts and Bills. Over the limit, requests return 429 until the next minute starts. No Retry-After header is sent. Send Accept: application/json to get the 429 as JSON; without it the body is an HTML page. If you sync many stores from one server, they all share that server's limit.
Errors
Every Inventory endpoint can return 403 when the key does not hold the scope the endpoint needs, or when the key's store is not linked to {entity_id}.
Inventory endpoints return errors as {"error": "..."}, or {"errors": [...]} when a validation failure has several messages. This differs from the {"error": {"message", "type"}} shape used by the other endpoints in this API, so handle both if you use both.
A 422 from the purchase order write endpoints can have either shape. Request validation, such as a missing field, an unmatched SKU or a reused Idempotency-Key, returns {"errors": [...]}. A refusal because of the purchase order's status, such as updating one that is not a draft or cancelling one that cannot be cancelled, returns {"error": "..."}.
The read endpoints wrap a single record in a key named after it ({"purchase_order": {...}}) and a list in its plural with meta. The create, update and cancel endpoints return the purchase order itself, unwrapped.
A parent that is not yours returns 404 on the nested lists: listing receipts for an unknown or deleted purchase order, or bills for a receipt that is unknown or not committed. A parent that is yours but has nothing under it returns an empty list.
/v1/entities/{entity_id}/bills/ with no bill uuid is not an Inventory endpoint, and an API key gets 401 {"error": "Invalid API key"} there. List bills per receipt instead.
Path identifiers must be well formed: {entity_id} contains only letters and digits, a purchase order {uuid} only letters, digits, - and _, and a supplier, receipt or bill {uuid} must be a UUID. A malformed path does not reach these endpoints and does not return the JSON error shape; usually it is an HTML 404 page.
When the service behind these endpoints does not answer in time, they return 504. A read is safe to retry; for a write, fetch the purchase order first, or retry a create with the same Idempotency-Key.
Endpoints
Questions about Inventory API access go to inventory@bookkeep.com.