Skip to content

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
FormatJSON in, JSON out
AuthenticationOAuth2 bearer tokens (see OAuth applications)
PermissionsOAuth scopes per resource, plus the roles of the user who authorized the application
Paginationpage and limit query parameters, at most 250 records per page
SpecificationOpenAPI (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:

  1. A Boxture admin registers an OAuth application under Admin › Integration › OAuth › Applications and gives the developer the application ID and secret. See OAuth applications.
  2. The integration sends a Boxture user to the authorization page, where the user signs in and clicks Authorize.
  3. Boxture returns an authorization code, which the integration exchanges for an access token and a refresh token.
  4. Every API request carries the access token in the Authorization header.
http
GET /api/v1/orders HTTP/1.1
Host: oms.example.com
Authorization: Bearer 09t0BODe-q9aH0FgsAcNQPlO7RLeGN9EnNay17b_F-I
Accept: application/json

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

ResourceScopes
Accountsaccounts, accounts:read, accounts:write
Billingbilling, billing:write
Channelschannels, channels:read
Containerscontainers, containers:read, containers:write
Customerscustomers, customers:read, customers:write
Inventory and inventory itemsinventory, inventory:read, inventory:write
Locations and bin locationslocations, locations:read, locations:write
Ordersorders, orders:read, orders:write
Packing materialspacking_materials, packing_materials:read, packing_materials:write
Productsproducts, products:read, products:write
Reasonsreasons, reasons:read, reasons:write
Service typesservice_types, service_types:read
Shipmentsshipments, shipments:read, shipments:write
Shipping methodsshipping_methods, shipping_methods:read, shipping_methods:write
Totestotes, totes:read, totes:write
Vendorsvendors, 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 ​

StatusMeaning
400A parameter is missing or invalid, for example an unknown country code or a quantity of zero. The body explains which parameter.
401No token, or the token is invalid, expired or revoked.
403The token does not have a scope that this endpoint accepts.
404The record does not exist or is outside the user's scope.
422The record could not be saved. The body lists the validation errors per field, for example {"name": ["can't be blank"]}.
500The 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:

ParameterDefaultAllowed
page11 or higher
limit251 to 250
http
GET /api/v1/products?page=3&limit=100

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

EndpointFilters
GET /ordersname (regular expression on the order name, the first part of the order ID as shown in Boxture), tote_name (orders in this tote)
GET /productsaccount_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 /customersname (regular expression)
GET /vendorsname (regular expression)
GET /locationsname (regular expression), accessible_by_account_id (only locations this account may use)
GET /bin_locationslocation_id
GET /containerspacking_material_id, location_id, bin_location_id
GET /toteslocation_id
GET /packing_materialsaccount_id, level, number and name (regular expressions), state (default active)
GET /shipping_methodsservice_type_id, account_id, default_packing_material
GET /inventoriesaccount_id, product_account_id, location_id, bin_location_id, product_id, channel_id, status
GET /inventory_itemslocation_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 pathWhat it does
GET /ordersList orders
GET /orders/:idGet one order, including its lines, contacts, events and shipment IDs
POST /ordersCreate an order
PUT /orders/:idUpdate an order
POST /orders/:id/confirmConfirm a concept order
POST /orders/:id/allocateAllocate the order, or only the items you list
POST /orders/:id/unallocateUndo allocation, entirely or for the items you list
POST /orders/:id/pickRelease the order for picking (creates pick lists)
POST /orders/:id/unpickPut picked items back from a tote into a bin location
POST /orders/:id/packPack items from totes into parcels; returns the shipment
POST /orders/:id/unpackTake items out of parcels back into a tote
POST /orders/:id/receiveReceive items on an inbound order (purchase, return, transfer)
POST /orders/:id/cancelCancel the order, or only the items you list
POST /orders/:id/commentsAdd 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 pathWhat it does
GET /shipmentsList shipments
GET /shipments/:idGet one shipment with parcels, contacts and order lines
PATCH /shipments/:idUpdate carrier, service type, tracking numbers, weights, dimensions and customs values
POST /shipments/:id/shipShip the shipment
POST /shipments/:id/deliverMark as delivered with proof of delivery (deliver_info.name, deliver_info.pod_at)
POST /shipments/:id/tracking_infoAdd 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 pathWhat it does
GET /inventoriesInventory totals per account, location, bin location, product and status, split into expected, available, allocated, picked, hold, expired and total
POST /inventoriesAdd inventory to a bin location (location, bin location, product, quantity, status, serial number, lot, expiry date)
DELETE /inventoriesRemove inventory (same fields)
POST /inventories/moveMove inventory or a container from one bin location to another
GET /inventory_itemsIndividual 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 ​

ResourceEndpointsExtra actions (POST /:id/...)
AccountsGET, GET /:id, POST, PUT /:idactivate, deactivate, disable
CustomersGET, GET /:id, POST, PUT /:idactivate, disable, anonymize
VendorsGET, GET /:id, POST, PUT /:idactivate, disable
ProductsGET, GET /:id, POST, PUT /:idpublish, expire, republish
Packing materialsGET, GET /:id, POST, PUT /:idpublish, expire, republish
LocationsGET, GET /:id, POST, PUT /:idactivate, disable
Bin locationsGET, GET /:id, POST, PUT /:id
ContainersGET, GET /:id, POST, PATCH /:idtake_inventory, put_inventory
TotesGET, GET /:id, POST, PUT /:id
ReasonsGET, GET /:id, POST, PUT /:idpublish, expire, republish
Shipping methodsGET, GET /:id, POST, PUT /:id
Service typesGET, GET /:id
ChannelsGET

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/:id or GET /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_number for your own order number. It is shown in Boxture, searchable, and printed on documents.
  • Use metadata for 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.

Boxture Help