Appearance
REST API
The Boxture REST API lets another system work with Boxture directly: create orders, follow their progress, read inventory, register products and update shipments. It is the connection of choice when a customer has its own ERP, web shop or middleware and wants to integrate without going through one of the ready-made third party integrations.
This page is written for the developer building that connection, but the first sections are useful for anyone who needs to decide whether the API fits.
At a glance
| Base address | /api/v1 on your Boxture address, for example https://oms.example.com/api/v1 |
| Format | JSON in, JSON out |
| Authentication | OAuth2 bearer tokens (see OAuth applications) |
| Permissions | OAuth scopes per resource, plus the roles of the user who authorized the application |
| Pagination | page and limit query parameters, at most 250 records per page |
| Specification | OpenAPI (Swagger) document at /api/v1/specification.json |
What the API can and cannot see
Every API call runs as a Boxture user: the user who authorized the OAuth application. The API applies exactly the same rules as the user interface:
- The user's account, and the accounts below it, determine which orders, products, inventory and other records are visible. A record that exists but falls outside the user's scope returns "not found".
- The user's roles determine which actions are allowed. For example, to pack an order through the API the user must be an admin, warehouse associate or packer, just as in the user interface. See Users and roles.
- The record's state determines what is possible. You cannot pick an order that is not allocated, in the API or in the user interface.
On top of that, the scopes granted to the token limit which resources the application can touch at all (see Scopes).
TIP
Create a dedicated Boxture user for each integration and give it only the roles it needs. Everything the integration does is then recorded under that user in the transaction log.
Authentication
The API uses OAuth2. In short:
- A Boxture admin registers an OAuth application under Admin › Integration › OAuth › Applications and gives the developer the application ID and secret. See OAuth applications.
- The integration sends a Boxture user to the authorization page, where the user signs in and clicks Authorize.
- Boxture returns an authorization code, which the integration exchanges for an access token and a refresh token.
- Every API request carries the access token in the
Authorizationheader.
http
GET /api/v1/orders HTTP/1.1
Host: oms.example.com
Authorization: Bearer 09t0BODe-q9aH0FgsAcNQPlO7RLeGN9EnNay17b_F-I
Accept: application/jsonAccess tokens are valid for one day. Use the refresh token to get a new access token without asking the user to sign in again. The complete token flow, with example requests, is on the OAuth page.
The only address that works without a token is the specification document, /api/v1/specification.json.
Scopes
A scope is a permission for one area of the API. Each resource has a read/write scope and, for most resources, separate read-only and write-only scopes. An endpoint accepts any of the scopes listed for it.
| Resource | Scopes |
|---|---|
| Accounts | accounts, accounts:read, accounts:write |
| Billing | billing, billing:write |
| Channels | channels, channels:read |
| Containers | containers, containers:read, containers:write |
| Customers | customers, customers:read, customers:write |
| Inventory and inventory items | inventory, inventory:read, inventory:write |
| Locations and bin locations | locations, locations:read, locations:write |
| Orders | orders, orders:read, orders:write |
| Packing materials | packing_materials, packing_materials:read, packing_materials:write |
| Products | products, products:read, products:write |
| Reasons | reasons, reasons:read, reasons:write |
| Service types | service_types, service_types:read |
| Shipments | shipments, shipments:read, shipments:write |
| Shipping methods | shipping_methods, shipping_methods:read, shipping_methods:write |
| Totes | totes, totes:read, totes:write |
| Vendors | vendors, vendors:read, vendors:write |
A read endpoint (GET) accepts the plain scope or the :read scope; a write endpoint (POST, PUT, PATCH, DELETE) accepts the plain scope or the :write scope. The application only receives scopes that are listed on its OAuth application, so ask the admin to add the scopes you need there.
Interactive documentation
Every endpoint, parameter and response is described in the OpenAPI specification at /api/v1/specification.json. Load it into any OpenAPI tool (Swagger UI, Postman, Insomnia) to browse it and generate a client. Each endpoint in the specification includes an example response.
On test and staging environments Boxture also serves an interactive Swagger page at /api/v1/docs. Use its Authorize button to sign in and try requests directly from the browser. This page is not available on production.
Requests and responses
Request format
Send JSON with Content-Type: application/json. Request bodies wrap the record in a key named after the resource, in the singular:
json
{
"vendor": {
"name": "Acme Supplies",
"contacts": [
{
"kind": "vendor",
"company_name": "Acme Supplies",
"address_lines": ["Industrieweg 12"],
"postal_code": "1234 AB",
"city": "Amsterdam",
"country_code": "NL"
}
]
}
}Response format
Responses use the same wrapping: a single record comes back under the singular key, a list under the plural key.
json
{
"orders": [
{ "id": "9052d4db-4585-5897-b155-473d7ef04b66", "type": "sales_order", "state": "confirmed" }
]
}Records are identified by their ID (a UUID). Most records also accept a free-form metadata object where you can store your own references; Boxture keeps it but does not use it in its processes.
Errors
| Status | Meaning |
|---|---|
400 | A parameter is missing or invalid, for example an unknown country code or a quantity of zero. The body explains which parameter. |
401 | No token, or the token is invalid, expired or revoked. |
403 | The token does not have a scope that this endpoint accepts. |
404 | The record does not exist or is outside the user's scope. |
422 | The record could not be saved. The body lists the validation errors per field, for example {"name": ["can't be blank"]}. |
500 | The action could not be carried out, for example because the order is not in a state that allows it or the user's role does not permit it. The body contains the reason. |
Error bodies are JSON. When there is a single message it is returned as {"error": "..."}.
Pagination
Every list endpoint is paginated with two query parameters:
| Parameter | Default | Allowed |
|---|---|---|
page | 1 | 1 or higher |
limit | 25 | 1 to 250 |
http
GET /api/v1/products?page=3&limit=100Keep requesting the next page until a page returns fewer records than the limit; that is the last page.
Filtering
List endpoints accept filters as query parameters. Filters described as a regular expression are matched case-insensitively, so name=^acme finds every name starting with "acme".
| Endpoint | Filters |
|---|---|
GET /orders | name (regular expression on the order name, the first part of the order ID as shown in Boxture), tote_name (orders in this tote) |
GET /products | account_id, number and name (comma-separated list; each value matches anywhere in the field), ean_or_upc (regular expression), state (default active), product_category (category name or ID) |
GET /customers | name (regular expression) |
GET /vendors | name (regular expression) |
GET /locations | name (regular expression), accessible_by_account_id (only locations this account may use) |
GET /bin_locations | location_id |
GET /containers | packing_material_id, location_id, bin_location_id |
GET /totes | location_id |
GET /packing_materials | account_id, level, number and name (regular expressions), state (default active) |
GET /shipping_methods | service_type_id, account_id, default_packing_material |
GET /inventories | account_id, product_account_id, location_id, bin_location_id, product_id, channel_id, status |
GET /inventory_items | location_id, bin_location_id, product_id, state, status (new or defective), lot, serial_number |
WARNING
GET /products and GET /packing_materials only return active records unless you pass state. To find a product that is still in concept, add state=concept.
Endpoints
All paths are relative to /api/v1.
Orders
Orders are the heart of the API. You can create every order type and drive it through its lifecycle. See Order lifecycle for what each step means.
| Method and path | What it does |
|---|---|
GET /orders | List orders |
GET /orders/:id | Get one order, including its lines, contacts, events and shipment IDs |
POST /orders | Create an order |
PUT /orders/:id | Update an order |
POST /orders/:id/confirm | Confirm a concept order |
POST /orders/:id/allocate | Allocate the order, or only the items you list |
POST /orders/:id/unallocate | Undo allocation, entirely or for the items you list |
POST /orders/:id/pick | Release the order for picking (creates pick lists) |
POST /orders/:id/unpick | Put picked items back from a tote into a bin location |
POST /orders/:id/pack | Pack items from totes into parcels; returns the shipment |
POST /orders/:id/unpack | Take items out of parcels back into a tote |
POST /orders/:id/receive | Receive items on an inbound order (purchase, return, transfer) |
POST /orders/:id/cancel | Cancel the order, or only the items you list |
POST /orders/:id/comments | Add a comment to the order |
The type of a new order is one of sales_order, purchase_order, return_order, transfer_order, scrap_order, kit_order, add_inventory_order or remove_inventory_order. See Order types.
Order lines identify the product in one of three ways: product_id, product_number or ean_or_upc. You do not need to know Boxture's product IDs; the product number (SKU) is enough.
Example: create a sales order.
http
POST /api/v1/orders
Authorization: Bearer 09t0BODe-q9aH0FgsAcNQPlO7RLeGN9EnNay17b_F-I
Content-Type: application/json
{
"order": {
"type": "sales_order",
"customer_reference_number": "WEB-100231",
"shipping_method": "DHL",
"contacts": [
{
"kind": "delivery",
"first_name": "John",
"last_name": "Doe",
"email": "john.doe@example.com",
"address_lines": ["First Street 1"],
"postal_code": "87654",
"city": "Metropolis",
"state_or_province_code": "FL",
"country_code": "US"
}
],
"order_lines": [
{ "product_number": "TSHIRT-RED-M", "quantity": 3 },
{ "product_number": "MUG-01", "quantity": 1, "price": { "value": 12.5, "currency": "eur" } }
]
}
}The response is the full order, with its state and an errors object. An order that is created without problems starts in concept; confirm it with POST /orders/:id/confirm (or let your settings confirm and allocate it automatically, see Allocation settings).
Other useful order fields: channel_id, business_model, shipping_method_id, customer_id, vendor_id (purchase orders), related_order_id, purchase_order_number, packing_instructions, gift_message, delivery_terms (EXW, FCA, DAP, DDP, ...), ship_earliest_on, ship_latest_on, ship_at, inbound_tracking_number, notes (visible in the user interface), metadata, and settings to override settings for this one order.
Allocate, unallocate, pick and cancel take an optional list of items per location. Leave the body out to act on the whole order:
json
{
"order_allocate": {
"allocations": [
{
"location_id": "955e3b90-923f-584a-b8f3-0d1c8fc34a58",
"items": [
{ "product_number": "TSHIRT-RED-M", "quantity": 2 }
]
}
]
}
}The body key matches the action: order_allocate.allocations, order_unallocate.unallocations, order_pick.picks, order_cancel.cancels. Each item has exactly one of product_id, product_number or ean_or_upc, and exactly one of quantity or serial_number.
Receive needs the location and the bin location the goods go to, optionally a container, and the items with quantities, lots, expiry dates and serial numbers as applicable:
json
{
"order_receive": {
"location_id": "955e3b90-923f-584a-b8f3-0d1c8fc34a58",
"bin_location_id": "9702a075-38dc-526e-a3e2-b0eb62918a24",
"items": [
{ "product_number": "MUG-01", "quantity": 48, "lot": "L2311", "expires_on": "2027-06-30" }
]
}
}Pack needs one entry per parcel, each with either an existing container_id or a packing_material_id to create a new parcel, the parcel weight, and the items taken from totes. All parcels in one call must be at the same location. Set add_to_open_shipment to add the parcels to an existing open shipment.
Shipments
| Method and path | What it does |
|---|---|
GET /shipments | List shipments |
GET /shipments/:id | Get one shipment with parcels, contacts and order lines |
PATCH /shipments/:id | Update carrier, service type, tracking numbers, weights, dimensions and customs values |
POST /shipments/:id/ship | Ship the shipment |
POST /shipments/:id/deliver | Mark as delivered with proof of delivery (deliver_info.name, deliver_info.pod_at) |
POST /shipments/:id/tracking_info | Add tracking events (status, created_at, notes) |
PATCH /shipments/:id is how an external label system reports back: send the tracking_number for the shipment and per parcel. See Shipments.
Inventory
| Method and path | What it does |
|---|---|
GET /inventories | Inventory totals per account, location, bin location, product and status, split into expected, available, allocated, picked, hold, expired and total |
POST /inventories | Add inventory to a bin location (location, bin location, product, quantity, status, serial number, lot, expiry date) |
DELETE /inventories | Remove inventory (same fields) |
POST /inventories/move | Move inventory or a container from one bin location to another |
GET /inventory_items | Individual inventory items, for example to look up a serial number or lot |
Pass channel_id to GET /inventories to get the quantities the way that sales channel sees them: the available quantity is reduced by the inventory reporting percentage for that channel, and rows with nothing to report are left out. This is the recommended way for a web shop to read its sellable stock.
Master data
| Resource | Endpoints | Extra actions (POST /:id/...) |
|---|---|---|
| Accounts | GET, GET /:id, POST, PUT /:id | activate, deactivate, disable |
| Customers | GET, GET /:id, POST, PUT /:id | activate, disable, anonymize |
| Vendors | GET, GET /:id, POST, PUT /:id | activate, disable |
| Products | GET, GET /:id, POST, PUT /:id | publish, expire, republish |
| Packing materials | GET, GET /:id, POST, PUT /:id | publish, expire, republish |
| Locations | GET, GET /:id, POST, PUT /:id | activate, disable |
| Bin locations | GET, GET /:id, POST, PUT /:id | |
| Containers | GET, GET /:id, POST, PATCH /:id | take_inventory, put_inventory |
| Totes | GET, GET /:id, POST, PUT /:id | |
| Reasons | GET, GET /:id, POST, PUT /:id | publish, expire, republish |
| Shipping methods | GET, GET /:id, POST, PUT /:id | |
| Service types | GET, GET /:id | |
| Channels | GET |
The extra actions are the same state changes as the buttons in the user interface: publishing a concept product makes it active, expiring it retires it, and so on. See Product lifecycle.
A new product needs at least a number (unique within the account) and a name:
json
{
"product": {
"number": "MUG-01",
"name": "Coffee mug, white",
"ean_or_upc": "8712345678906",
"weight": { "value": 0.35, "unit": "kg" },
"dimensions": { "length": 12, "width": 9, "height": 10, "unit": "cm" },
"price": { "value": 12.5, "currency": "eur" }
}
}Billing events
POST /billing/events registers a billable event for an account, for example work done outside Boxture. It needs source, event_type, account_id and quantity, and accepts location_id, metadata and created_at (to date the event in the past). It requires the billing or billing:write scope.
Receiving updates from Boxture
The REST API does not push notifications by itself. There are two ways to stay up to date:
- Poll the API, for example
GET /orders/:idorGET /shipments/:id, at a sensible interval. - Ask a Boxture admin to set up an integration flow that calls your endpoint when something happens, for example when a shipment is shipped or an order is put on hold. The message contains the same JSON as the API returns for that record. See Integration flows.
Good practice
- Store Boxture's IDs next to your own records after creating them; every later call needs them.
- Use
customer_reference_numberfor your own order number. It is shown in Boxture, searchable, and printed on documents. - Use
metadatafor references that only your system needs. - Refresh the access token before it expires instead of waiting for a
401. - Request only the scopes you need.