# Bolder Developers
> Developer docs for Bolder: storefronts (JS SDK, components, Liquid themes, CLI) and the REST API.
# Bolder API
Source: https://developers.onbolder.com/api
Welcome to the Bolder API documentation.
- [API v2 reference](/api/v2)
- [API v1 reference](/api/v1)
---
# Bolder API v1
Source: https://developers.onbolder.com/api/v1
The Bolder API is a hypermedia-driven REST API following the [HAL](https://stateless.group/hal_specification.html)
(Hypertext Application Language) specification.
### HAL conventions
Every response includes a `_links` object with related resources and actions. Links use the `btc:` CURIE
namespace — for example `btc:products` expands to `https://api.onbolder.com/docs/v1#products`.
Non-GET actions (POST, PUT, DELETE) are described by links that carry a `method` property.
Templated links (e.g. pagination, filters) carry `"templated": true` and use RFC 6570 URI Templates.
Embedded sub-resources live in `_embedded` and follow the same HAL structure.
### Paginated results
List endpoints return a paginated results envelope:
```json
{
"_links": {
"self": { "href": "…?page=1" },
"next": { "href": "…?page=2" },
"last": { "href": "…?page=10" }
},
"total_items": 500,
"per_page": 50,
"page": 1,
"_embedded": { "items": [ … ] }
}
```
### Authentication
All requests require a Bearer access token obtained via OAuth 2.0.
```
Authorization: Bearer YOUR-ACCESS-TOKEN
```
See the [Authentication](#authentication) section for the full list of supported OAuth 2.0 flows.
Version 1.0
Base URL: `https://api.onbolder.com/v1`
## Root
Entry point that returns the authenticated user's account and linked shops.
- [`GET /`](/api/v1/root/get-root) — Root endpoint.
## Shops
Shops owned by the authenticated account.
- [`GET /shops`](/api/v1/shops/list-shops) — List shops.
- [`GET /shops/{id}`](/api/v1/shops/get-shop) — Shop details.
## Products
Products catalogue management.
- [`GET /products`](/api/v1/products/list-products) — List all products.
- [`GET /products/{id}`](/api/v1/products/get-product) — Product details.
- [`DELETE /products/{id}`](/api/v1/products/delete-product) — Delete product.
- [`POST /shops/{shop_id}/products`](/api/v1/products/create-product) — Create product.
- [`PUT /shops/{shop_id}/products/{id}`](/api/v1/products/update-product) — Update product.
## Variants
Product variants (size, colour, etc.).
- [`GET /shops/{shop_id}/variants/{id}`](/api/v1/variants/get-variant) — Variant details.
- [`PUT /shops/{shop_id}/variants/{id}`](/api/v1/variants/update-variant) — Update variant.
- [`DELETE /shops/{shop_id}/variants/{id}`](/api/v1/variants/delete-variant) — Delete variant.
- [`POST /products/{product_id}/variants`](/api/v1/variants/create-variant) — Create variant.
## Product Images
Upload and delete product images.
- [`POST /products/{product_id}/assets`](/api/v1/product-images/create-product-asset) — Upload product image.
- [`DELETE /products/{product_id}/assets/{id}`](/api/v1/product-images/delete-product-asset) — Delete product image.
## Orders
Order management including creation, updates, and batch operations.
- [`GET /shops/{shop_id}/orders`](/api/v1/orders/list-orders) — List orders.
- [`POST /shops/{shop_id}/orders`](/api/v1/orders/create-order) — Create order.
- [`GET /shops/{shop_id}/orders/{code}`](/api/v1/orders/get-order) — Order details.
- [`PUT /shops/{shop_id}/orders/{code}`](/api/v1/orders/update-order) — Update order.
- [`POST /shops/{shop_id}/orders/updaters`](/api/v1/orders/batch-update-orders) — Batch update orders.
## Contacts
Shop contacts (customers).
- [`GET /shops/{shop_id}/contacts`](/api/v1/contacts/list-contacts) — List shop contacts.
## Webhooks
Subscribe to real-time event notifications via webhooks.
- [`GET /hub/{shop_id}`](/api/v1/webhooks/get-hub) — Webhooks hub.
- [`GET /hub/{shop_id}/subscriptions`](/api/v1/webhooks/list-subscriptions) — List subscriptions.
- [`POST /hub/{shop_id}/subscriptions`](/api/v1/webhooks/create-subscription) — Subscribe.
- [`DELETE /hub/{shop_id}/subscriptions/{id}`](/api/v1/webhooks/delete-subscription) — Unsubscribe.
## Themes
Shop theme templates and static assets.
- [`GET /shops/{shop_id}/theme`](/api/v1/themes/get-theme) — Theme details.
- [`PUT /themes/{id}`](/api/v1/themes/update-theme) — Update theme settings.
- [`POST /themes/{theme_id}/templates`](/api/v1/themes/create-template) — Create template.
- [`PUT /themes/{theme_id}/templates/{id}`](/api/v1/themes/update-template) — Update template.
- [`DELETE /themes/{theme_id}/templates/{id}`](/api/v1/themes/delete-template) — Delete template.
- [`POST /themes/{theme_id}/assets`](/api/v1/themes/create-theme-asset) — Upload theme asset.
- [`DELETE /themes/{theme_id}/assets/{id}`](/api/v1/themes/delete-theme-asset) — Delete theme asset.
---
# List shop contacts
Source: https://developers.onbolder.com/api/v1/contacts/list-contacts
Returns a paginated list of contacts (customers) for a shop. Requires `admin` scope.
`GET /shops/{shop_id}/contacts`
**Responses**
- `200` — Paginated contacts list
Response example, 200:
```json
{
"total_items": 0,
"page": 0,
"per_page": 0,
"_links": {
"self": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"next": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"last": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
}
},
"_embedded": {
"items": [
{
"id": 0,
"name": "string",
"email": "string",
"phone_number": "string",
"company": "string",
"opt_in": true,
"available_orders_count": 0,
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
]
}
}
```
---
# Batch update orders
Source: https://developers.onbolder.com/api/v1/orders/batch-update-orders
Updates one or more order attributes across multiple orders at once. Currently supports
updating `status`. The operation is not transactional — orders that fail validation are
reported in `invalid_items` while successfully updated ones appear in `valid_items`.
Requires `admin` scope.
`POST /shops/{shop_id}/orders/updaters`
**Request body** (`application/json`, required)
- `ids` (integer[], required) — Array of order IDs to update
- `additions` (object, required) — Attributes to apply to all orders
Request body example:
```json
{
"ids": [
577048,
12001
],
"additions": {
"status": "shipped"
}
}
```
**Responses**
- `200` — Batch update result
Response example, 200:
```json
{
"ids": [
0
],
"additions": {},
"removals": {},
"_embedded": {
"valid_items": [
{
"id": 0,
"shop_id": 0,
"code": "string",
"status": "checkout",
"net_total": 0,
"total": 0,
"shipping_total": 0,
"shipping_description": "string",
"discount_total": 0,
"tracking_code": "string",
"courier_name": "string",
"total_weight_in_grams": 0,
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"pending_on": "2019-08-24T14:15:22Z",
"closed_on": "2019-08-24T14:15:22Z",
"shipped_on": "2019-08-24T14:15:22Z",
"fulfillment": {
"fulfillment_method": "shipping"
},
"payment_info": {
"type": "string",
"name": "string",
"auth_code": "string",
"transaction_id": "string"
},
"_links": {}
}
],
"invalid_items": [
{}
]
}
}
```
---
# Create order
Source: https://developers.onbolder.com/api/v1/orders/create-order
Creates a new order in `checkout` state with the given line items, contact and shipping address.
Returns the new order including web checkout and cart links. Requires `admin` scope.
`POST /shops/{shop_id}/orders`
**Request body** (`application/json`, required)
- `line_items` (LineItemInput[])
- `contact` (ContactInput)
- `address` (AddressInput)
Request body example:
```json
{
"line_items": [
{
"variant_id": 1234,
"units": 2
}
],
"contact": {
"email": "joe@bloggs.com",
"name": "Joe Bloggs",
"phone_number": "12322222222"
},
"address": {
"street": "Av. Brasil 20",
"street_2": "4to piso",
"locality_name": "Valparaiso",
"region_name": "Valparaiso",
"country_code": "CL"
}
}
```
**Responses**
- `201` — Order created
- `422` — Validation errors
Response example, 201:
```json
{
"id": 0,
"shop_id": 0,
"code": "string",
"status": "checkout",
"net_total": 0,
"total": 0,
"shipping_total": 0,
"shipping_description": "string",
"discount_total": 0,
"tracking_code": "string",
"courier_name": "string",
"total_weight_in_grams": 0,
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"pending_on": "2019-08-24T14:15:22Z",
"closed_on": "2019-08-24T14:15:22Z",
"shipped_on": "2019-08-24T14:15:22Z",
"fulfillment": {
"fulfillment_method": "shipping"
},
"payment_info": {
"type": "string",
"name": "string",
"auth_code": "string",
"transaction_id": "string"
},
"_links": {},
"_embedded": {
"contact": {
"id": 0,
"name": "string",
"email": "string",
"phone_number": "string"
},
"address": {
"street": "string",
"street_2": "string",
"locality_name": "string",
"region_name": "string",
"country_name": "string",
"country_code": "string",
"postal_code": "string",
"lat_long": "string"
},
"line_items": [
{
"product_id": 0,
"product_title": "string",
"unit_price": 0,
"units": 0,
"discount_total": 0,
"net_total": 0,
"total": 0,
"variant_id": 0,
"variant_title": "string",
"variant_sku": "string",
"weight_in_grams": 0
}
],
"promotion": {
"name": "string",
"discount": 0,
"discount_type": "$",
"free_shipping": true
},
"discounts": [
{}
]
}
}
```
Response example, 422:
```json
{
"_class": [
"errors"
],
"_embedded": {
"errors": [
{
"_class": [
"error"
],
"field": "product.title",
"messages": [
"can't be blank"
]
}
]
}
}
```
---
# Order details
Source: https://developers.onbolder.com/api/v1/orders/get-order
Returns full details for a single order. Requires `admin` scope.
`GET /shops/{shop_id}/orders/{code}`
**Responses**
- `200` — Order resource
- `404` — Resource not found
Response example, 200:
```json
{
"id": 0,
"shop_id": 0,
"code": "string",
"status": "checkout",
"net_total": 0,
"total": 0,
"shipping_total": 0,
"shipping_description": "string",
"discount_total": 0,
"tracking_code": "string",
"courier_name": "string",
"total_weight_in_grams": 0,
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"pending_on": "2019-08-24T14:15:22Z",
"closed_on": "2019-08-24T14:15:22Z",
"shipped_on": "2019-08-24T14:15:22Z",
"fulfillment": {
"fulfillment_method": "shipping"
},
"payment_info": {
"type": "string",
"name": "string",
"auth_code": "string",
"transaction_id": "string"
},
"_links": {},
"_embedded": {
"contact": {
"id": 0,
"name": "string",
"email": "string",
"phone_number": "string"
},
"address": {
"street": "string",
"street_2": "string",
"locality_name": "string",
"region_name": "string",
"country_name": "string",
"country_code": "string",
"postal_code": "string",
"lat_long": "string"
},
"line_items": [
{
"product_id": 0,
"product_title": "string",
"unit_price": 0,
"units": 0,
"discount_total": 0,
"net_total": 0,
"total": 0,
"variant_id": 0,
"variant_title": "string",
"variant_sku": "string",
"weight_in_grams": 0
}
],
"promotion": {
"name": "string",
"discount": 0,
"discount_type": "$",
"free_shipping": true
},
"discounts": [
{}
]
}
}
```
---
# List orders
Source: https://developers.onbolder.com/api/v1/orders/list-orders
Returns a paginated list of orders for a shop. Requires `admin` scope.
Supports filtering by status, date ranges, amounts, and geo-location.
`GET /shops/{shop_id}/orders`
**Responses**
- `200` — Paginated orders list
Response example, 200:
```json
{
"total_items": 0,
"page": 0,
"per_page": 0,
"_links": {
"self": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"next": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"last": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
}
},
"_embedded": {
"items": [
{
"id": 0,
"shop_id": 0,
"code": "string",
"status": "checkout",
"net_total": 0,
"total": 0,
"shipping_total": 0,
"shipping_description": "string",
"discount_total": 0,
"tracking_code": "string",
"courier_name": "string",
"total_weight_in_grams": 0,
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"pending_on": "2019-08-24T14:15:22Z",
"closed_on": "2019-08-24T14:15:22Z",
"shipped_on": "2019-08-24T14:15:22Z",
"fulfillment": {
"fulfillment_method": "shipping"
},
"payment_info": {
"type": "string",
"name": "string",
"auth_code": "string",
"transaction_id": "string"
},
"_links": {}
}
]
}
}
```
---
# Update order
Source: https://developers.onbolder.com/api/v1/orders/update-order
Updates an order that is in `checkout` state. Use this to set or modify line items,
contact information, and shipping address. Requires `admin` scope.
The returned order includes `btc:web_checkout` and `btc:web_cart` links for directing
customers to complete payment.
**Line item tips:**
- Pass `units: -1` to remove a line item
- Contact is matched by `email`; new contacts require `name` and `phone_number` too
`PUT /shops/{shop_id}/orders/{code}`
**Request body** (`application/json`, required)
- `line_items` (LineItemInput[]) — Pass \`units: -1\` to remove a variant from the order
- `contact` (ContactInput)
- `address` (object)
Request body example:
```json
{
"line_items": [
{
"variant_id": 0,
"units": 0
}
],
"contact": {
"email": "string",
"name": "string",
"phone_number": "string",
"company": "string"
},
"address": {
"id": 0
}
}
```
**Responses**
- `200` — Order updated
- `422` — Validation errors
Response example, 200:
```json
{
"id": 0,
"shop_id": 0,
"code": "string",
"status": "checkout",
"net_total": 0,
"total": 0,
"shipping_total": 0,
"shipping_description": "string",
"discount_total": 0,
"tracking_code": "string",
"courier_name": "string",
"total_weight_in_grams": 0,
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"pending_on": "2019-08-24T14:15:22Z",
"closed_on": "2019-08-24T14:15:22Z",
"shipped_on": "2019-08-24T14:15:22Z",
"fulfillment": {
"fulfillment_method": "shipping"
},
"payment_info": {
"type": "string",
"name": "string",
"auth_code": "string",
"transaction_id": "string"
},
"_links": {},
"_embedded": {
"contact": {
"id": 0,
"name": "string",
"email": "string",
"phone_number": "string"
},
"address": {
"street": "string",
"street_2": "string",
"locality_name": "string",
"region_name": "string",
"country_name": "string",
"country_code": "string",
"postal_code": "string",
"lat_long": "string"
},
"line_items": [
{
"product_id": 0,
"product_title": "string",
"unit_price": 0,
"units": 0,
"discount_total": 0,
"net_total": 0,
"total": 0,
"variant_id": 0,
"variant_title": "string",
"variant_sku": "string",
"weight_in_grams": 0
}
],
"promotion": {
"name": "string",
"discount": 0,
"discount_type": "$",
"free_shipping": true
},
"discounts": [
{}
]
}
}
```
Response example, 422:
```json
{
"_class": [
"errors"
],
"_embedded": {
"errors": [
{
"_class": [
"error"
],
"field": "product.title",
"messages": [
"can't be blank"
]
}
]
}
}
```
---
# Upload product image
Source: https://developers.onbolder.com/api/v1/product-images/create-product-asset
Uploads a new image to a product. The image data must be Base64-encoded and sent in the
`data` field along with the file name and MIME type. Requires `admin` scope.
`POST /products/{product_id}/assets`
**Request body** (`application/json`, required)
- `data` (string, required) — Base64-encoded image data URI (\`data:image/jpeg;base64,...\`)
- `file_name` (string, required)
- `title` (string)
- `description` (string)
Request body example:
```json
{
"data": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",
"file_name": "photo.jpg",
"title": "Front view"
}
```
**Responses**
- `201` — Image uploaded
- `422` — Validation errors
Response example, 201:
```json
{
"file_name": "string",
"title": "string",
"description": "string",
"_links": {
"thumbnail": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"small": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"medium": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"large": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"original": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"btc:delete_product_asset": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
}
}
}
```
Response example, 422:
```json
{
"_class": [
"errors"
],
"_embedded": {
"errors": [
{
"_class": [
"error"
],
"field": "product.title",
"messages": [
"can't be blank"
]
}
]
}
}
```
---
# Delete product image
Source: https://developers.onbolder.com/api/v1/product-images/delete-product-asset
Permanently removes an image from a product. Requires `admin` scope.
`DELETE /products/{product_id}/assets/{id}`
**Responses**
- `204` — Asset deleted
- `404` — Resource not found
---
# Create product
Source: https://developers.onbolder.com/api/v1/products/create-product
Creates a new product in the given shop. Requires `admin` scope.
`POST /shops/{shop_id}/products`
**Request body** (`application/json`, required)
- `title` (string) — Product title. Required on create.
- `price` (integer) — Price in cents
- `price_comparison` (integer) — "Was" price in cents
- `description` (string)
- `status` (string)
- `product_type_name` (string) — Name for new or existing product type
- `vendor` (string)
- `tags` (string[])
- `collections` (object[])
- `variants` (VariantInput[])
- `custom_attributes` (CustomAttributeInput[])
Request body example:
```json
{
"title": "iPhone 5",
"price": 20020,
"price_comparison": 20122,
"description": "iPhone 5 by Apple",
"status": "visible",
"product_type_name": "Smart phones",
"tags": [
"communications",
"handheld"
],
"vendor": "Acme",
"variants": [
{
"title": "White",
"sku": "1234R",
"available_if_no_stock": 1,
"stock": 102,
"weight_in_grams": 340
}
],
"collections": [
{
"id": 1234
},
{
"title": "A new collection"
}
]
}
```
**Responses**
- `201` — Product created
- `422` — Validation errors
Response example, 201:
```json
{
"id": "string",
"slug": "string",
"title": "string",
"status": "visible",
"price": 0,
"price_comparison": 0,
"discount_percentage": 0,
"price_per_unit": 0,
"currency_code": "string",
"vendor": "string",
"tags": [
"string"
],
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"_links": {},
"_embedded": {
"shop": {
"id": "string",
"name": "string",
"subdomain": "string",
"url": "string",
"description": "string",
"currency_code": "string",
"locale": "string",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"product_counts": {
"visible": 0,
"hidden": 0
},
"_links": {}
},
"image": {
"file_name": "string",
"title": "string",
"_links": {
"thumbnail": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"small": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"medium": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"large": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"original": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
}
}
},
"variants": [
{
"id": 0,
"title": "string",
"position": 0,
"sku": "string",
"price": 0,
"price_comparison": 0,
"stock": 0,
"available_if_no_stock": true,
"weight_in_grams": 0,
"_links": {}
}
],
"collections": [
{
"id": 0,
"title": "string",
"slug": "string",
"shop_id": 0
}
],
"images": [
{
"file_name": "string",
"title": "string",
"_links": {
"thumbnail": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"small": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"medium": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"large": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"original": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
}
}
}
],
"custom_attributes": [
{
"id": 0,
"name": "string",
"value": "string",
"field_type": "string",
"required": true,
"key": "string"
}
]
},
"description": "string"
}
```
Response example, 422:
```json
{
"_class": [
"errors"
],
"_embedded": {
"errors": [
{
"_class": [
"error"
],
"field": "product.title",
"messages": [
"can't be blank"
]
}
]
}
}
```
---
# Delete product
Source: https://developers.onbolder.com/api/v1/products/delete-product
Permanently deletes a product and all its variants and images. Requires `admin` scope.
`DELETE /products/{id}`
**Responses**
- `204` — Product deleted
- `404` — Resource not found
---
# Product details
Source: https://developers.onbolder.com/api/v1/products/get-product
Returns full details for a single product including embedded variants, images, collections and custom attributes.
`GET /products/{id}`
**Responses**
- `200` — Product resource
- `404` — Resource not found
Response example, 200:
```json
{
"id": "string",
"slug": "string",
"title": "string",
"status": "visible",
"price": 0,
"price_comparison": 0,
"discount_percentage": 0,
"price_per_unit": 0,
"currency_code": "string",
"vendor": "string",
"tags": [
"string"
],
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"_links": {},
"_embedded": {
"shop": {
"id": "string",
"name": "string",
"subdomain": "string",
"url": "string",
"description": "string",
"currency_code": "string",
"locale": "string",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"product_counts": {
"visible": 0,
"hidden": 0
},
"_links": {}
},
"image": {
"file_name": "string",
"title": "string",
"_links": {
"thumbnail": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"small": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"medium": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"large": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"original": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
}
}
},
"variants": [
{
"id": 0,
"title": "string",
"position": 0,
"sku": "string",
"price": 0,
"price_comparison": 0,
"stock": 0,
"available_if_no_stock": true,
"weight_in_grams": 0,
"_links": {}
}
],
"collections": [
{
"id": 0,
"title": "string",
"slug": "string",
"shop_id": 0
}
],
"images": [
{
"file_name": "string",
"title": "string",
"_links": {
"thumbnail": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"small": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"medium": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"large": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"original": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
}
}
}
],
"custom_attributes": [
{
"id": 0,
"name": "string",
"value": "string",
"field_type": "string",
"required": true,
"key": "string"
}
]
},
"description": "string"
}
```
---
# List all products
Source: https://developers.onbolder.com/api/v1/products/list-products
Returns a paginated list of products across all shops accessible to the token.
Supports rich filtering, full-text search, and sorting.
`GET /products`
**Responses**
- `200` — Paginated products list
Response example, 200:
```json
{
"total_items": 0,
"page": 0,
"per_page": 0,
"_links": {
"self": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"next": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"last": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
}
},
"_embedded": {
"items": [
{
"id": "string",
"slug": "string",
"title": "string",
"status": "visible",
"price": 0,
"price_comparison": 0,
"discount_percentage": 0,
"price_per_unit": 0,
"currency_code": "string",
"vendor": "string",
"tags": [
"string"
],
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"_links": {},
"_embedded": {
"shop": {
"id": "string",
"name": "string",
"subdomain": "string",
"url": "string",
"description": "string",
"currency_code": "string",
"locale": "string",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"product_counts": {
"visible": 0,
"hidden": 0
},
"_links": {}
},
"image": {
"file_name": "string",
"title": "string",
"_links": {
"thumbnail": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"small": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"medium": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"large": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"original": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
}
}
},
"variants": [
{
"id": 0,
"title": "string",
"position": 0,
"sku": "string",
"price": 0,
"price_comparison": 0,
"stock": 0,
"available_if_no_stock": true,
"weight_in_grams": 0
}
],
"collections": [
{
"id": 0,
"title": "string",
"slug": "string",
"shop_id": 0
}
]
}
}
]
}
}
```
---
# Update product
Source: https://developers.onbolder.com/api/v1/products/update-product
Updates an existing product. All fields are optional — only supplied fields are updated.
Requires `admin` scope.
`PUT /shops/{shop_id}/products/{id}`
**Request body** (`application/json`, required)
- `title` (string) — Product title. Required on create.
- `price` (integer) — Price in cents
- `price_comparison` (integer) — "Was" price in cents
- `description` (string)
- `status` (string)
- `product_type_name` (string) — Name for new or existing product type
- `vendor` (string)
- `tags` (string[])
- `collections` (object[])
- `variants` (VariantInput[])
- `custom_attributes` (CustomAttributeInput[])
Request body example:
```json
{
"title": "string",
"price": 0,
"price_comparison": 0,
"description": "string",
"status": "visible",
"product_type_name": "string",
"vendor": "string",
"tags": [
"string"
],
"collections": [
{
"id": 0,
"title": "string"
}
],
"variants": [
{
"title": "string",
"position": 0,
"sku": "string",
"stock": 1,
"available_if_no_stock": 0,
"weight_in_grams": 0,
"price": 0,
"price_comparison": 0
}
],
"custom_attributes": [
{
"name": "string",
"value": "string",
"field_type": "string",
"required": false
}
]
}
```
**Responses**
- `200` — Product updated
- `422` — Validation errors
Response example, 200:
```json
{
"id": "string",
"slug": "string",
"title": "string",
"status": "visible",
"price": 0,
"price_comparison": 0,
"discount_percentage": 0,
"price_per_unit": 0,
"currency_code": "string",
"vendor": "string",
"tags": [
"string"
],
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"_links": {},
"_embedded": {
"shop": {
"id": "string",
"name": "string",
"subdomain": "string",
"url": "string",
"description": "string",
"currency_code": "string",
"locale": "string",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"product_counts": {
"visible": 0,
"hidden": 0
},
"_links": {}
},
"image": {
"file_name": "string",
"title": "string",
"_links": {
"thumbnail": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"small": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"medium": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"large": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"original": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
}
}
},
"variants": [
{
"id": 0,
"title": "string",
"position": 0,
"sku": "string",
"price": 0,
"price_comparison": 0,
"stock": 0,
"available_if_no_stock": true,
"weight_in_grams": 0,
"_links": {}
}
],
"collections": [
{
"id": 0,
"title": "string",
"slug": "string",
"shop_id": 0
}
],
"images": [
{
"file_name": "string",
"title": "string",
"_links": {
"thumbnail": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"small": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"medium": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"large": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"original": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
}
}
}
],
"custom_attributes": [
{
"id": 0,
"name": "string",
"value": "string",
"field_type": "string",
"required": true,
"key": "string"
}
]
},
"description": "string"
}
```
Response example, 422:
```json
{
"_class": [
"errors"
],
"_embedded": {
"errors": [
{
"_class": [
"error"
],
"field": "product.title",
"messages": [
"can't be blank"
]
}
]
}
}
```
---
# Root endpoint
Source: https://developers.onbolder.com/api/v1/root/get-root
Returns the authenticated user's account information and an embedded list of their shops,
each carrying HAL links to all available actions (products, orders, contacts, theme, etc.).
`GET /`
**Responses**
- `200` — Authenticated root resource
Response example, 200:
```json
{
"_links": {
"curies": [
{
"name": "btc",
"href": "https://api.onbolder.com/docs/v1#{rel}",
"templated": true
}
],
"btc:all_products": {
"href": "https://api.onbolder.com/v1/products{?q,page,per_page,status,shop_subdomains,collections,shop_ids,tags,variants,sku,currency,price.gte,price.lte,sort}",
"templated": true,
"title": "All products, with search and filters"
}
},
"uid": 2,
"email": "user@site.com",
"user_name": "johnnie85",
"scopes": "admin,public",
"_embedded": {
"shops": [
{
"id": 47,
"subdomain": "acme",
"url": "acme.onbolder.com",
"name": "Acme",
"currency_code": "CLP"
}
]
}
}
```
---
# Shop details
Source: https://developers.onbolder.com/api/v1/shops/get-shop
Returns full details for a single shop, including HAL links for all available
actions (products, orders, contacts, theme, variants).
`GET /shops/{id}`
**Responses**
- `200` — Shop resource
- `404` — Resource not found
Response example, 200:
```json
{
"id": "string",
"name": "string",
"subdomain": "string",
"url": "string",
"description": "string",
"currency_code": "string",
"locale": "string",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"product_counts": {
"visible": 0,
"hidden": 0
},
"_links": {},
"analytics_id": "string",
"asset_count": 0,
"_embedded": {
"account": {
"status": "string",
"plan": "string",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
},
"branches": [
{
"id": 0,
"name": "string",
"type": "string",
"allow_local_pickup": true,
"_embedded": {
"address": {
"street": "string",
"street_2": "string",
"locality_name": "string",
"region_name": "string",
"country_name": "string",
"country_code": "string",
"postal_code": "string",
"lat_long": "string"
}
}
}
]
}
}
```
---
# List shops
Source: https://developers.onbolder.com/api/v1/shops/list-shops
Returns a paginated list of shops accessible to the authenticated account.
`GET /shops`
**Responses**
- `200` — Paginated shops list
Response example, 200:
```json
{
"total_items": 0,
"page": 0,
"per_page": 0,
"_links": {
"self": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"next": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"last": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
}
},
"_embedded": {
"items": [
{
"id": "string",
"name": "string",
"subdomain": "string",
"url": "string",
"description": "string",
"currency_code": "string",
"locale": "string",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"product_counts": {
"visible": 0,
"hidden": 0
},
"_links": {}
}
]
}
}
```
---
# Create template
Source: https://developers.onbolder.com/api/v1/themes/create-template
Adds a new HTML, CSS or JavaScript template to a theme. The `file_name` extension
determines the content type (`html`, `css`, `js`). Requires `admin` scope.
`POST /themes/{theme_id}/templates`
**Request body** (`application/json`, required)
- `file_name` (string, required) — File name including extension, e.g. \`about.html\`
- `body` (string, required) — Template content
Request body example:
```json
{
"file_name": "about.html",
"body": "
About Us
"
}
```
**Responses**
- `201` — Template created
Response example, 201:
```json
{
"id": "string",
"file_name": "string",
"name": "string",
"content_type": "html",
"body": "string",
"updated_on": "2019-08-24T14:15:22Z",
"_links": {}
}
```
---
# Upload theme asset
Source: https://developers.onbolder.com/api/v1/themes/create-theme-asset
Uploads a new static asset (image, font, etc.) to a theme. The file data must be
Base64-encoded. Requires `admin` scope.
`POST /themes/{theme_id}/assets`
**Request body** (`application/json`, required)
- `file_name` (string, required)
- `data` (string, required) — Base64-encoded file data URI
Request body example:
```json
{
"file_name": "logo.png",
"data": "data:image/png;base64,iVBORw0KGgoAAAANS..."
}
```
**Responses**
- `201` — Asset uploaded
Response example, 201:
```json
{
"id": "string",
"file_name": "string",
"content_type": "string",
"file_size": 0,
"digest": "string",
"updated_on": "2019-08-24T14:15:22Z",
"_links": {
"file": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
}
}
}
```
---
# Delete template
Source: https://developers.onbolder.com/api/v1/themes/delete-template
Removes a template from a theme. Some core templates (e.g. `index.html`) cannot be deleted.
Requires `admin` scope.
`DELETE /themes/{theme_id}/templates/{id}`
**Responses**
- `204` — Template deleted
- `404` — Resource not found
---
# Delete theme asset
Source: https://developers.onbolder.com/api/v1/themes/delete-theme-asset
Permanently removes a static asset from a theme. Requires `admin` scope.
`DELETE /themes/{theme_id}/assets/{id}`
**Responses**
- `204` — Asset deleted
- `404` — Resource not found
---
# Theme details
Source: https://developers.onbolder.com/api/v1/themes/get-theme
Returns the active theme for a shop, including all templates and static assets.
Requires `admin` scope.
`GET /shops/{shop_id}/theme`
**Responses**
- `200` — Theme resource
Response example, 200:
```json
{
"id": "string",
"title": "string",
"production": true,
"settings": {},
"updated_on": "2019-08-24T14:15:22Z",
"_links": {},
"_embedded": {
"templates": [
{
"id": "string",
"file_name": "string",
"name": "string",
"content_type": "html",
"body": "string",
"updated_on": "2019-08-24T14:15:22Z",
"_links": {}
}
],
"assets": [
{
"id": "string",
"file_name": "string",
"content_type": "string",
"file_size": 0,
"digest": "string",
"updated_on": "2019-08-24T14:15:22Z",
"_links": {
"file": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
}
}
}
]
}
}
```
---
# Update template
Source: https://developers.onbolder.com/api/v1/themes/update-template
Updates the body of an existing theme template. Requires `admin` scope.
`PUT /themes/{theme_id}/templates/{id}`
**Request body** (`application/json`, required)
- `file_name` (string, required) — File name including extension, e.g. \`about.html\`
- `body` (string, required) — Template content
Request body example:
```json
{
"file_name": "string",
"body": "string"
}
```
**Responses**
- `200` — Template updated
Response example, 200:
```json
{
"id": "string",
"file_name": "string",
"name": "string",
"content_type": "html",
"body": "string",
"updated_on": "2019-08-24T14:15:22Z",
"_links": {}
}
```
---
# Update theme settings
Source: https://developers.onbolder.com/api/v1/themes/update-theme
Updates the settings object for a theme. Requires `admin` scope.
`PUT /themes/{id}`
**Request body** (`application/json`, required)
- `settings` (object) — Theme settings key/value pairs (theme-specific)
Request body example:
```json
{
"settings": {}
}
```
**Responses**
- `200` — Theme updated
Response example, 200:
```json
{
"id": "string",
"title": "string",
"production": true,
"settings": {},
"updated_on": "2019-08-24T14:15:22Z",
"_links": {},
"_embedded": {
"templates": [
{
"id": "string",
"file_name": "string",
"name": "string",
"content_type": "html",
"body": "string",
"updated_on": "2019-08-24T14:15:22Z",
"_links": {}
}
],
"assets": [
{
"id": "string",
"file_name": "string",
"content_type": "string",
"file_size": 0,
"digest": "string",
"updated_on": "2019-08-24T14:15:22Z",
"_links": {
"file": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
}
}
}
]
}
}
```
---
# Create variant
Source: https://developers.onbolder.com/api/v1/variants/create-variant
Adds a new variant to an existing product. Requires `admin` scope.
`POST /products/{product_id}/variants`
**Request body** (`application/json`, required)
- `title` (string) — Required on create
- `position` (integer) — Ordering position (lower comes first). On a chained-variants product, omitting it on create defaults to the current highest position instead of 0.
- `sku` (string) — Must be unique within the shop
- `stock` (integer)
- `available_if_no_stock` (integer) — \`1\` = always available, \`0\` = unavailable when out of stock
- `weight_in_grams` (integer)
- `price` (integer) — Override product price for this variant (cents)
- `price_comparison` (integer) — Override comparison price for this variant (cents)
Request body example:
```json
{
"title": "Large",
"sku": "SKU-LG",
"stock": 10,
"available_if_no_stock": 0,
"weight_in_grams": 200
}
```
**Responses**
- `201` — Variant created
- `422` — Validation errors
Response example, 201:
```json
{
"id": 0,
"title": "string",
"position": 0,
"sku": "string",
"price": 0,
"price_comparison": 0,
"stock": 0,
"available_if_no_stock": true,
"weight_in_grams": 0,
"_links": {}
}
```
Response example, 422:
```json
{
"_class": [
"errors"
],
"_embedded": {
"errors": [
{
"_class": [
"error"
],
"field": "product.title",
"messages": [
"can't be blank"
]
}
]
}
}
```
---
# Delete variant
Source: https://developers.onbolder.com/api/v1/variants/delete-variant
Removes a variant from its product. Requires `admin` scope.
`DELETE /shops/{shop_id}/variants/{id}`
**Responses**
- `204` — Variant deleted
- `404` — Resource not found
---
# Variant details
Source: https://developers.onbolder.com/api/v1/variants/get-variant
Returns details for a single product variant.
`GET /shops/{shop_id}/variants/{id}`
**Responses**
- `200` — Variant resource
- `404` — Resource not found
Response example, 200:
```json
{
"id": 0,
"title": "string",
"position": 0,
"sku": "string",
"price": 0,
"price_comparison": 0,
"stock": 0,
"available_if_no_stock": true,
"weight_in_grams": 0,
"_links": {}
}
```
---
# Update variant
Source: https://developers.onbolder.com/api/v1/variants/update-variant
Updates an existing variant. You can also look up the variant by `sku` instead of `id`
by passing `?sku=YOUR_SKU` as a query parameter. Requires `admin` scope.
`PUT /shops/{shop_id}/variants/{id}`
**Request body** (`application/json`, required)
- `title` (string) — Required on create
- `position` (integer) — Ordering position (lower comes first). On a chained-variants product, omitting it on create defaults to the current highest position instead of 0.
- `sku` (string) — Must be unique within the shop
- `stock` (integer)
- `available_if_no_stock` (integer) — \`1\` = always available, \`0\` = unavailable when out of stock
- `weight_in_grams` (integer)
- `price` (integer) — Override product price for this variant (cents)
- `price_comparison` (integer) — Override comparison price for this variant (cents)
Request body example:
```json
{
"title": "string",
"position": 0,
"sku": "string",
"stock": 1,
"available_if_no_stock": 0,
"weight_in_grams": 0,
"price": 0,
"price_comparison": 0
}
```
**Responses**
- `200` — Variant updated
- `422` — Validation errors
Response example, 200:
```json
{
"id": 0,
"title": "string",
"position": 0,
"sku": "string",
"price": 0,
"price_comparison": 0,
"stock": 0,
"available_if_no_stock": true,
"weight_in_grams": 0,
"_links": {}
}
```
Response example, 422:
```json
{
"_class": [
"errors"
],
"_embedded": {
"errors": [
{
"_class": [
"error"
],
"field": "product.title",
"messages": [
"can't be blank"
]
}
]
}
}
```
---
# Subscribe
Source: https://developers.onbolder.com/api/v1/webhooks/create-subscription
Creates a new webhook subscription. After creation the API sends a verification POST to
your `url` containing a `X-Hook-Ping` header. Your endpoint must respond with a matching
`X-Hook-Pong` header to activate the subscription.
#### Subscription states
| Status | Description |
|--------|-------------|
| `pending` | Created, awaiting verification |
| `active` | Verified and receiving events |
| `failed_activation` | Verification was rejected |
| `failed` | Disabled after 3 consecutive delivery failures |
| `disabled` | Manually disabled or target returned `410` |
#### Available event topics
**Orders:** `orders`, `orders.created`, `orders.created.checkout`, `orders.updated`,
`orders.updated.placed`, `orders.updated.closed`, `orders.updated.shipped`,
`orders.updated.cancelled`, `orders.deleted`
**Products:** `products`, `products.created`, `products.updated`, `products.deleted`,
`products.updated.variants.created`, `products.updated.variants.updated`,
`products.updated.variants.deleted`, `products.updated.assets.created`,
`products.updated.assets.deleted`
`POST /hub/{shop_id}/subscriptions`
**Request body** (`application/json`, required)
- `topic` (string, required) — Event topic to subscribe to, e.g. \`orders.updated.placed\`
- `url` (string, required) — URL to POST event notifications to
- `notify_origin` (boolean) — Whether to notify the originating app
- `auth` (SubscriptionAuth)
Request body example:
```json
{
"topic": "orders.updated.placed",
"url": "https://www.myapp.com/events",
"notify_origin": true,
"auth": {
"type": "basic",
"username": "foo",
"password": "bar"
}
}
```
**Responses**
- `201` — Subscription created (pending verification)
- `422` — Validation errors
Response example, 201:
```json
{
"id": 0,
"status": "pending",
"topic": "string",
"url": "string",
"notify_origin": true,
"auth": {
"type": "none",
"username": "string",
"password": "string"
},
"error_count": 0,
"last_error": "string",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"_links": {}
}
```
Response example, 422:
```json
{
"_class": [
"errors"
],
"_embedded": {
"errors": [
{
"_class": [
"error"
],
"field": "product.title",
"messages": [
"can't be blank"
]
}
]
}
}
```
---
# Unsubscribe
Source: https://developers.onbolder.com/api/v1/webhooks/delete-subscription
Deletes a webhook subscription.
`DELETE /hub/{shop_id}/subscriptions/{id}`
**Responses**
- `204` — Subscription deleted
- `404` — Resource not found
---
# Webhooks hub
Source: https://developers.onbolder.com/api/v1/webhooks/get-hub
Returns the webhooks hub for a shop, including HAL links to subscribe, unsubscribe and
list subscriptions.
`GET /hub/{shop_id}`
**Responses**
- `200` — Hub resource
Response example, 200:
```json
{
"_links": {}
}
```
---
# List subscriptions
Source: https://developers.onbolder.com/api/v1/webhooks/list-subscriptions
Returns a paginated list of webhook subscriptions for a shop.
`GET /hub/{shop_id}/subscriptions`
**Responses**
- `200` — Paginated subscriptions list
Response example, 200:
```json
{
"total_items": 0,
"page": 0,
"per_page": 0,
"_links": {
"self": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"next": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
},
"last": {
"href": "string",
"method": "string",
"title": "string",
"type": "string",
"templated": true
}
},
"_embedded": {
"items": [
{
"id": 0,
"status": "pending",
"topic": "string",
"url": "string",
"notify_origin": true,
"auth": {
"type": "none",
"username": "string",
"password": "string"
},
"error_count": 0,
"last_error": "string",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"_links": {}
}
]
}
}
```
---
# Bolder API v2
Source: https://developers.onbolder.com/api/v2
### Getting Started
The Bolder API provides programmatic access to your Bolder Shop data through a
hypermedia-driven REST interface.
#### Fastest path — Personal Access Token
For scripts, CLI tools, and personal automation, skip the OAuth grant dance entirely:
1. Go to [https://start.onbolder.com/dev/credentials](https://start.onbolder.com/dev/credentials)
and sign in. The first visit auto-creates a default developer application for your
account — there's nothing to register.
2. Under **Personal Access Tokens**, create a new token: pick the scopes you need (see
[Token scopes](#token-scopes) below) and an expiry.
3. Copy the token — it's shown **once**.
```bash
export BOLDER_TOKEN=PASTE_YOUR_TOKEN_HERE
curl -H "Authorization: Bearer $BOLDER_TOKEN" https://api.onbolder.com/v2
```
All `curl` examples in this documentation use `$BOLDER_TOKEN`. Set it once in your
shell and every example works immediately. See [Personal Access Tokens](#personal-access-tokens-pats)
below for details on expiry and revocation.
#### Building a real integration — register an OAuth application
If you're building something you'll distribute to other Bolder merchants (rather than
automating your own account), register a dedicated application instead of using a PAT:
Go to [https://start.onbolder.com/dev/credentials](https://start.onbolder.com/dev/credentials), sign in, and create a new
application. You'll receive a `CLIENT_ID` and `CLIENT_SECRET`. The fastest way to get a
token from a terminal is the **Client Credentials** grant:
```bash
export BOLDER_TOKEN=$(curl -s \
-X POST https://auth.onbolder.com/oauth/token \
-d "grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&scope=products.read,orders.read,orders.write" \
| jq -r .access_token)
```
No `jq`? Use Python instead:
```bash
export BOLDER_TOKEN=$(curl -s \
-X POST https://auth.onbolder.com/oauth/token \
-d "grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&scope=products.read,orders.read,orders.write" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
```
> **Note:** Unlike PATs, these tokens are temporary. Re-run the request above when your
> token expires, or use the `refresh_token` grant described in the Authentication section
> below.
---
### Authentication
The Bolder API uses **OAuth 2.0**. The token **must** be sent in the
`Authorization` header — query-parameter tokens are not accepted by v2.
Five grant types are supported:
#### 1. Authorization Code (recommended for web apps)
Redirect the user to the authorization endpoint and exchange the returned `code`
for tokens on your server.
```
GET https://auth.onbolder.com/oauth/authorize
?client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.com/callback
&response_type=code
&scope=products.read,orders.read,orders.write
```
Then exchange the code:
```
POST https://auth.onbolder.com/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&redirect_uri=https://yourapp.com/callback
&code=RETURNED_CODE
```
#### 2. Implicit Grant (for client-side JavaScript apps)
```
GET https://auth.onbolder.com/oauth/authorize
?client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.com/callback
&response_type=token
&scope=products.read
```
The access token is returned in the URL fragment after the redirect.
#### 3. Client Credentials (for server-to-server / automated scripts)
No user involved — the application authenticates as itself.
```
POST https://auth.onbolder.com/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&scope=products.read,orders.read,orders.write
```
#### 4. Resource Owner Password Credentials (trusted apps only)
```
POST https://auth.onbolder.com/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=password
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&username=USER_EMAIL
&password=USER_PASSWORD
&scope=products.read,orders.read,orders.write
```
#### 5. JWT Bearer (refreshing expired tokens)
Exchange a `refresh_token` for a new access token:
```
POST https://auth.onbolder.com/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&refresh_token=YOUR_REFRESH_TOKEN
```
#### Token response
All flows return a JSON token response:
```json
{
"access_token": "eyJ...",
"token_type": "bearer",
"expires_in": 7200,
"refresh_token": "abc123...",
"scope": "products.read,orders.read,orders.write"
}
```
Access tokens are **temporary**. Implement refresh-token logic to obtain new
tokens before they expire.
Errors from `POST /oauth/token` are always JSON, `400` with `{"error": "invalid_grant", "error_description": "..."}`
(the standard OAuth error codes). A browser app can call the token endpoint directly: the response allows the
origin of the application's registered redirect URI (any port on `localhost`), and no other origin.
#### Token scopes
v2 uses **granular scopes**: request access to individual resource namespaces using
`{namespace}.read` / `{namespace}.write` scopes. This lets an integration ask for only
what it needs — e.g. an order-fulfillment tool can request `orders.read,orders.write`
without also getting product or customer write access.
| Namespace | Read scope | Write scope | Covers |
|-------------|--------------------|-----------------------|--------|
| `products` | `products.read` | `products.write` | Products, variants, collections |
| `orders` | `orders.read` | `orders.write` | Orders, documents, tracking |
| `customers` | `customers.read` | `customers.write` | Customer profiles |
| `shops` | `shops.read` | `shops.write` | Shop configuration |
| `hub` | `hub.read` | `hub.write` | Content (posts, pages) |
| `store` | `store.read` | `store.write` | Themes and assets |
| `sellers` | `sellers.read` | — (read-only) | Seller information |
| `batches` | `batches.read` | `batches.write` | Batch job status |
| `subscriptions` | `subscriptions.read` | `subscriptions.write` | Subscription and membership purchases and their seats |
Request them the same way as any other scope, comma-separated:
```
&scope=products.read,orders.read,orders.write
```
A user whose roles are all below *administrador* (for example a *vendedor*) can't be granted the legacy
`admin` scope: they can only authorize the granular scopes their role carries.
##### Own-records scopes
`orders.read_own` and `orders.write_own` (`write_own` includes reading) give the same access as
`orders.read` / `orders.write`, limited to the **orders of the user's own customers** (the customer's
`salesperson_id` is the user). They are carried by the *vendedor* role (which has no other orders or
customers scopes) and need a token issued to a user. With them you can list, get, create and update orders (`GET /orders`, `/orders/stats`,
`/orders/status_counts`, `GET`/`POST`/`PUT` on orders) and attach a document to one of their own orders
(`POST /orders/{order_id}/documents`, with `orders.write_own`; they cannot list, read, download or delete
documents); every other endpoint, including customers and refunds, needs the full scopes. An order of someone
else's customer answers `404`. When such a token creates an
order the `contact` is required: a new customer gets the user as salesperson, an existing one must already be the
user's (otherwise `422` on `$.contact`), and the order's `salesperson_id` is set to the user.
#### Personal Access Tokens (PATs)
For scripts and CLI tools where the standard OAuth grants (which require redirect flows or
a client secret exchange per session) are overkill, you can issue a **Personal Access
Token**: a long-lived Bearer JWT tied to your user account and a specific application,
created from the developer credentials page — see
[Fastest path — Personal Access Token](#fastest-path--personal-access-token) above for the
step-by-step. A few more details:
- Give the token a name, choose an expiry (30 days, 90 days, 1 year, or no expiry), and
select the scopes to grant — you can only grant scopes your own user role already has
(see [Token scopes](#token-scopes) above).
- The raw token is shown **once**, immediately after creation. Copy it then — it cannot be
retrieved again afterwards.
Use it exactly like any other access token:
```bash
curl -H "Authorization: Bearer $PERSONAL_ACCESS_TOKEN" https://api.onbolder.com/v2
```
PATs do not expire on a fixed short TTL like regular access tokens, so there is no
refresh-token flow — the token is valid until its chosen expiry (if any) or until revoked.
Revoke a PAT at any time from the same credentials page; revocation takes effect
immediately.
---
### Hypermedia (HAL)
All responses follow the [HAL](https://stateless.group/hal_specification.html)
(Hypertext Application Language) format. Every response contains:
- **`_links`** — navigation links to related resources and available actions.
- **`_embedded`** — inline sub-resources (e.g. variants inside a product).
- **`_class`** — array of type strings (e.g. `["results", "products"]`).
- **Properties** — the resource's own data fields at the top level.
#### CURIEs
Link relation types use **service-namespaced CURIEs** so clients can
resolve documentation for any relation:
```json
{
"_links": {
"curies": [
{ "name": "products", "href": "https://api.onbolder.com/docs/v2#products:{rel}", "templated": true },
{ "name": "orders", "href": "https://api.onbolder.com/docs/v2#orders:{rel}", "templated": true }
],
"self": { "href": "https://api.onbolder.com/v2/products/123" },
"products:update": { "href": "https://api.onbolder.com/v2/products/123", "method": "put" },
"products:variants":{ "href": "https://api.onbolder.com/v2/products/123/variants" }
}
}
```
**Clients should follow links rather than constructing URLs.** If a link is absent
from a response, the action is either unavailable or not permitted for the current
token. The presence of a link is itself an affordance.
#### URI Templates
Some links are [URI templates](https://www.rfc-editor.org/rfc/rfc6570) (marked
`"templated": true`). Expand them with the parameters shown in the template before
making a request:
```
products:list → https://api.onbolder.com/v2/products{?q,status,page,per_page,...}
Expanded: https://api.onbolder.com/v2/products?status=visible&page=2
```
---
### Pagination
List endpoints return a standard pagination envelope:
```json
{
"_class": ["results", "products"],
"total_items": 142,
"per_page": 20,
"page": 1,
"_links": {
"self": { "href": "https://api.onbolder.com/v2/products?page=1" },
"next": { "href": "https://api.onbolder.com/v2/products?page=2" },
"prev": { "href": "https://api.onbolder.com/v2/products?page=0" }
},
"_embedded": {
"items": [...]
}
}
```
Default page size is **20**, maximum is **200** (use `per_page` to override).
---
### Errors
| HTTP Status | Meaning |
|-------------|---------|
| `400` | Bad request — malformed JSON or missing required field |
| `401` | Unauthorized — missing or invalid access token |
| `403` | Forbidden — valid token but insufficient permissions |
| `404` | Not found |
| `422` | Unprocessable — validation failed; see `_embedded.errors` |
| `429` | Too many requests — rate limit exceeded |
| `500` | Server error |
Validation errors (`422`) embed a structured error list:
```json
{
"_embedded": {
"errors": [
{ "field": "name", "messages": ["can't be blank"] }
]
}
}
```
---
### HTTP caching
The API emits standard `ETag` and `Last-Modified` headers. Clients may send
`If-None-Match` / `If-Modified-Since` on subsequent requests; unchanged
resources return `304 Not Modified` with no body.
Version 2.0
Base URL: `https://api.onbolder.com/v2`
## Root
API entry point — embeds sellers, shops, and navigation links to every resource
- [`GET /`](/api/v2/root/get-root) — API root.
## Sellers
Seller entities — the business layer between an account and its shops.
An account can have multiple sellers, each owning one or more shops and their product catalog.
- [`GET /sellers`](/api/v2/sellers/list-sellers) — List sellers.
- [`GET /sellers/{id}`](/api/v2/sellers/get-seller) — Get a seller.
## Shops
Shops owned by the authenticated account. A shop is the customer-facing storefront
that belongs to a seller. Settings, admins, shipping tables, payment methods,
exchange rates, and other shop-level configuration are anchored to a specific shop.
- [`GET /events`](/api/v2/shops/list-events-flattened) — List events.
- [`GET /exchange_rates`](/api/v2/shops/list-exchange-rates-flattened) — List exchange rates.
- [`GET /payment_methods`](/api/v2/shops/list-payment-methods-flattened) — List payment methods.
- [`GET /shipping_tables`](/api/v2/shops/list-shipping-tables-flattened) — List shipping tables.
- [`POST /shipping_tables`](/api/v2/shops/create-shipping-table) — Create a shipping table.
- [`GET /shipping_tables/{id}`](/api/v2/shops/get-shipping-table-flattened) — Get a shipping table.
- [`PUT /shipping_tables/{id}`](/api/v2/shops/update-shipping-table) — Update a shipping table.
- [`GET /shipping_tables/{shipping_table_id}/shipping_options`](/api/v2/shops/list-shipping-options) — List shipping options (shipping costs).
- [`POST /shipping_tables/{shipping_table_id}/shipping_options`](/api/v2/shops/create-shipping-option) — Create a shipping option (shipping cost).
- [`GET /shipping_options/{id}`](/api/v2/shops/get-shipping-option) — Get a shipping option.
- [`PUT /shipping_options/{id}`](/api/v2/shops/update-shipping-option) — Update a shipping option.
- [`DELETE /shipping_options/{id}`](/api/v2/shops/delete-shipping-option) — Delete a shipping option.
- [`GET /shops`](/api/v2/shops/list-shops) — List shops.
- [`GET /shops/{id}`](/api/v2/shops/get-shop) — Get a shop.
- [`GET /shops/{id}/settings`](/api/v2/shops/get-shop-settings) — Get shop settings.
- [`GET /shops/{shop_id}/admins`](/api/v2/shops/list-shop-admins) — List shop admins.
- [`POST /shops/{shop_id}/admins`](/api/v2/shops/create-shop-admin) — Create a dealer user.
- [`GET /shops/{shop_id}/admins/{id}`](/api/v2/shops/get-shop-admin) — Get a shop admin.
- [`PUT /shops/{shop_id}/admins/{id}`](/api/v2/shops/update-shop-admin) — Update or disable a dealer user.
- [`GET /shops/{shop_id}/branches`](/api/v2/shops/list-shop-branches) — List branches.
- [`POST /shops/{shop_id}/branches`](/api/v2/shops/create-shop-branch) — Create a branch (sucursal).
- [`GET /shops/{shop_id}/branches/{id}`](/api/v2/shops/get-shop-branch) — Get a branch.
- [`PUT /shops/{shop_id}/branches/{id}`](/api/v2/shops/update-shop-branch) — Rename a branch.
- [`POST /shops/{shop_id}/branches/{id}/users`](/api/v2/shops/assign-shop-branch-user) — Assign a user to a branch.
- [`DELETE /shops/{shop_id}/branches/{id}/users/{user_id}`](/api/v2/shops/unassign-shop-branch-user) — Remove a user from a branch.
- [`GET /shops/{shop_id}/events`](/api/v2/shops/list-shop-events) — List shop events.
- [`GET /shops/{shop_id}/payment_methods`](/api/v2/shops/list-payment-methods) — List payment methods for a shop.
- [`GET /shops/{shop_id}/exchange_rates`](/api/v2/shops/list-exchange-rates) — List exchange rates for a shop.
- [`GET /shops/{shop_id}/order_tags`](/api/v2/shops/list-order-tags) — List order tags.
- [`GET /shops/{shop_id}/shipping_tables`](/api/v2/shops/list-shipping-tables) — List shipping tables for a shop.
- [`GET /shops/{shop_id}/shipping_tables/{id}`](/api/v2/shops/get-shipping-table) — Get a shipping table for a shop.
## Content
CMS-style content for a shop's storefront — blogs and their posts, static pages, and
contact/lead-capture forms. A shop can have several blogs, each with its own language;
a post in one language can point at the post it translates.
- [`GET /shops/{shop_id}/posts`](/api/v2/content/list-shop-posts) — List shop posts.
- [`POST /shops/{shop_id}/posts`](/api/v2/content/create-post) — Create a post.
- [`GET /posts/{id}`](/api/v2/content/get-post) — Get a post.
- [`PUT /posts/{id}`](/api/v2/content/update-post) — Update a post.
- [`DELETE /posts/{id}`](/api/v2/content/delete-post) — Delete a post.
- [`GET /shops/{shop_id}/blogs`](/api/v2/content/list-shop-blogs) — List shop blogs.
- [`GET /blogs/{id}`](/api/v2/content/get-blog) — Get a blog.
- [`GET /shops/{shop_id}/pages`](/api/v2/content/list-shop-pages) — List pages.
- [`POST /shops/{shop_id}/pages`](/api/v2/content/create-page) — Create a page.
- [`GET /pages/{id}`](/api/v2/content/get-page) — Get a page.
- [`PUT /pages/{id}`](/api/v2/content/update-page) — Update a page.
- [`DELETE /pages/{id}`](/api/v2/content/delete-page) — Delete a page.
- [`GET /shops/{shop_id}/forms`](/api/v2/content/list-shop-forms) — List forms.
- [`POST /shops/{shop_id}/forms`](/api/v2/content/create-form) — Create a form.
- [`GET /forms/{id}`](/api/v2/content/get-form) — Get a form.
- [`PUT /forms/{id}`](/api/v2/content/update-form) — Update a form.
- [`DELETE /forms/{id}`](/api/v2/content/delete-form) — Delete a form.
## Products
Catalog products, automatically scoped to the authenticated account's sellers.
Account-scoped tokens need no extra parameters; tokens without an account/seller
scope must supply `shop_subdomains`.
- [`GET /product_tags`](/api/v2/products/list-product-tags-flattened) — List product tags.
- [`GET /products`](/api/v2/products/list-products) — List products.
- [`POST /products`](/api/v2/products/create-product) — Create a product.
- [`GET /products/stats`](/api/v2/products/products-stats) — Products KPI strip.
- [`POST /products/updaters`](/api/v2/products/batch-update-products) — Batch update products.
- [`GET /products/{id}`](/api/v2/products/get-product) — Get a product.
- [`PUT /products/{id}`](/api/v2/products/update-product) — Update a product.
- [`DELETE /products/{id}`](/api/v2/products/delete-product) — Delete a product.
- [`GET /products/{id}/price_items`](/api/v2/products/get-product-price-items) — List product price items.
- [`POST /products/{product_id}/assets`](/api/v2/products/create-product-asset) — Create a product asset.
- [`DELETE /products/{product_id}/assets/{id}`](/api/v2/products/delete-product-asset) — Delete a product asset.
- [`POST /products/{product_id}/relations`](/api/v2/products/create-product-relation) — Create a product relation.
- [`GET /shops/{shop_id}/product_tags`](/api/v2/products/list-product-tags) — List product tags for a shop.
- [`GET /shops/{shop_id}/vendors`](/api/v2/products/list-shop-vendors) — List vendors.
- [`GET /shops/{shop_id}/custom_product_attributes`](/api/v2/products/list-custom-product-attributes) — List custom product attributes.
- [`GET /shops/{shop_id}/product_sets`](/api/v2/products/list-shop-product-sets) — List product sets.
## Variants
Product variants (size, colour, etc.). Nested under `/products/{product_id}/variants`.
Each variant holds its own SKU, price, stock level, and images.
- [`GET /products/{product_id}/variants`](/api/v2/variants/list-variants) — List variants.
- [`POST /products/{product_id}/variants`](/api/v2/variants/create-variant) — Create a variant.
- [`GET /products/{product_id}/variants/{id}`](/api/v2/variants/get-variant) — Get a variant.
- [`PUT /products/{product_id}/variants/{id}`](/api/v2/variants/update-variant) — Update a variant.
- [`DELETE /products/{product_id}/variants/{id}`](/api/v2/variants/delete-variant) — Delete a variant.
- [`POST /variants/updaters`](/api/v2/variants/batch-update-variants) — Batch update variants.
## Price Lists
Named price lists for B2B / wholesale pricing. Products can have per-variant price
overrides within a price list.
- [`GET /price_lists`](/api/v2/price-lists/list-price-lists) — List price lists.
- [`POST /price_lists`](/api/v2/price-lists/create-price-list) — Create a price list.
- [`GET /price_lists/{id}`](/api/v2/price-lists/get-price-list) — Get a price list.
- [`PUT /price_lists/{id}`](/api/v2/price-lists/update-price-list) — Update a price list.
- [`DELETE /price_lists/{id}`](/api/v2/price-lists/delete-price-list) — Delete a price list.
- [`GET /price_lists/{id}/products`](/api/v2/price-lists/get-price-list-products) — List products in a price list.
- [`GET /price_lists/{price_list_id}/items`](/api/v2/price-lists/list-price-items) — List price items.
- [`PUT /price_lists/{price_list_id}/items/{id}`](/api/v2/price-lists/update-price-item) — Update a price item.
- [`DELETE /price_lists/{price_list_id}/items/{id}`](/api/v2/price-lists/delete-price-item) — Delete a price item.
## Product Types
Product type definitions used to group and filter catalog products.
- [`GET /product_types`](/api/v2/product-types/list-product-types) — List product types.
- [`POST /product_types`](/api/v2/product-types/create-product-type) — Create a product type.
- [`PUT /product_types/{id}`](/api/v2/product-types/update-product-type) — Update or upsert a product type.
## Collections
Curated product collections (manually or rule-based). Products can belong to
multiple collections.
- [`GET /collections`](/api/v2/collections/list-collections) — List collections.
- [`GET /collections/{id}`](/api/v2/collections/get-collections-id) — Get a collection.
- [`DELETE /collections/{id}`](/api/v2/collections/delete-collection) — Delete a collection.
## Orders
Shop orders. Supports batch status updates and per-order document management.
- [`GET /order_tags`](/api/v2/orders/list-order-tags-flattened) — List order tags.
- [`GET /orders`](/api/v2/orders/list-orders) — List orders.
- [`POST /orders`](/api/v2/orders/create-order) — Create an order.
- [`GET /orders/stats`](/api/v2/orders/orders-stats) — Orders KPI strip.
- [`POST /orders/updaters`](/api/v2/orders/batch-update-orders) — Batch update orders.
- [`GET /orders/{id}`](/api/v2/orders/get-order) — Get an order.
- [`PUT /orders/{id}`](/api/v2/orders/update-order) — Update an order.
- [`POST /orders/{id}/refund`](/api/v2/orders/refund-order) — Refund an order.
- [`POST /orders/{id}/split`](/api/v2/orders/split-order) — Split an order.
- [`GET /orders/{order_id}/documents`](/api/v2/orders/list-order-documents) — List order documents.
- [`POST /orders/{order_id}/documents`](/api/v2/orders/create-order-document) — Create an order document.
- [`GET /orders/{order_id}/documents/{id}`](/api/v2/orders/get-order-document) — Get an order document.
- [`DELETE /orders/{order_id}/documents/{id}`](/api/v2/orders/delete-order-document) — Delete an order document.
- [`GET /orders/{order_id}/messages`](/api/v2/orders/list-order-messages) — List order messages.
- [`POST /orders/{order_id}/messages`](/api/v2/orders/create-order-message) — Send an order message or note.
- [`GET /orders/{order_id}/progress`](/api/v2/orders/get-order-progress) — Get the customer-facing progress of an order.
- [`GET /orders/{order_id}/documents/{id}/file`](/api/v2/orders/download-order-document-file) — Download an order document file.
- [`POST /orders/{order_id}/items`](/api/v2/orders/create-order-item) — Add a line item to an order.
- [`PUT /orders/{order_id}/items/{id}`](/api/v2/orders/update-order-item) — Update an order line item.
- [`DELETE /orders/{order_id}/items/{id}`](/api/v2/orders/delete-order-item) — Remove a line item from an order.
- [`POST /orders/{order_id}/payments`](/api/v2/orders/create-order-payment) — Record a manual payment on an order.
- [`POST /orders/{order_id}/checkout_session`](/api/v2/orders/create-checkout-session) — Start (or resume) checkout for an order.
- [`GET /checkout_sessions/{id}`](/api/v2/orders/get-checkout-session) — Get a checkout session.
- [`POST /checkout_sessions/{id}/view`](/api/v2/orders/view-checkout-session) — Mark a checkout session as viewed.
## Customers
Shop customers. Includes customer authentication and password-reset flows.
- [`GET /customer_tags`](/api/v2/customers/list-customer-tags) — List customer tags.
- [`GET /shops/{shop_id}/companies`](/api/v2/customers/list-shop-companies) — List companies (empresas).
- [`POST /shops/{shop_id}/companies`](/api/v2/customers/create-shop-company) — Create a company.
- [`GET /shops/{shop_id}/companies/{id}`](/api/v2/customers/get-shop-company) — Get a company.
- [`PUT /shops/{shop_id}/companies/{id}`](/api/v2/customers/update-shop-company) — Update a company.
- [`GET /shops/{shop_id}/customer_tags`](/api/v2/customers/list-contact-tags) — List customer tags for a shop.
- [`GET /contact_groups`](/api/v2/customers/list-contact-groups-flat) — List contact groups (flat).
- [`POST /contact_groups`](/api/v2/customers/create-contact-group-flat) — Create a contact group (flat).
- [`GET /shops/{shop_id}/customer_groups`](/api/v2/customers/list-contact-groups) — List contact groups (legacy shop-nested).
- [`POST /shops/{shop_id}/customer_groups`](/api/v2/customers/create-contact-group) — Create a contact group (legacy shop-nested).
- [`GET /contact_groups/{id}`](/api/v2/customers/get-contact-group) — Get a contact group.
- [`PUT /contact_groups/{id}`](/api/v2/customers/update-contact-group) — Update a contact group.
- [`DELETE /contact_groups/{id}`](/api/v2/customers/delete-contact-group) — Delete a contact group.
- [`GET /contact_groups/{id}/contacts`](/api/v2/customers/list-contact-group-contacts) — List contacts in a group.
- [`GET /customers`](/api/v2/customers/list-customers) — List customers.
- [`POST /customers`](/api/v2/customers/create-customer) — Create a customer.
- [`GET /customers/{id}`](/api/v2/customers/get-customer) — Get a customer.
- [`PUT /customers/{id}`](/api/v2/customers/update-customer) — Update a customer.
- [`GET /customers/{id}/last_ordered_products`](/api/v2/customers/get-customer-last-ordered-products) — Get last ordered products.
- [`GET /customers/{id}/activity`](/api/v2/customers/get-customer-activity) — Recent activity of a customer.
- [`POST /customers/{id}/activations`](/api/v2/customers/start-contact-activation) — Start contact activation.
- [`POST /customers/{id}/phone_validations`](/api/v2/customers/start-phone-validation) — Start phone validation.
- [`DELETE /customers/{id}/activation`](/api/v2/customers/demote-contact) — Demote a contact.
- [`POST /customers/sessions`](/api/v2/customers/authenticate-customer) — Authenticate a customer.
- [`POST /customers/reset_password`](/api/v2/customers/reset-customer-password) — Request a password reset.
- [`PUT /customers/activations/{token}`](/api/v2/customers/activate-customer) — Activate a customer account.
## Promotions
Promotional discount codes and automatic discounts applied at checkout.
- [`GET /promotions`](/api/v2/promotions/list-promotions) — List promotions.
- [`POST /promotions`](/api/v2/promotions/create-promotion) — Create a promotion.
- [`GET /promotions/{id}`](/api/v2/promotions/get-promotion) — Get a promotion.
- [`PUT /promotions/{id}`](/api/v2/promotions/update-promotion) — Update a promotion.
- [`DELETE /promotions/{id}`](/api/v2/promotions/delete-promotion) — Delete a promotion.
## Volume Discounts
Quantity-based discount rules (tiered pricing by units purchased).
- [`GET /volume_discounts`](/api/v2/volume-discounts/list-volume-discounts) — List volume discounts.
- [`POST /volume_discounts`](/api/v2/volume-discounts/create-volume-discount) — Create a volume discount.
- [`GET /volume_discounts/{id}`](/api/v2/volume-discounts/get-volume-discount) — Get a volume discount.
- [`PUT /volume_discounts/{id}`](/api/v2/volume-discounts/update-volume-discount) — Update a volume discount.
- [`DELETE /volume_discounts/{id}`](/api/v2/volume-discounts/delete-volume-discount) — Delete a volume discount.
## Themes
Hosted-storefront themes, templates, and assets. Applies only to shops with a
hosted storefront (`store:` namespace); absent for headless shops.
- [`GET /shops/{id}/themes`](/api/v2/themes/list-shop-themes) — List themes for a shop.
- [`POST /shops/{id}/themes`](/api/v2/themes/create-shop-theme) — Create a dev theme.
- [`GET /themes/{id}`](/api/v2/themes/get-theme) — Get a theme.
- [`PUT /themes/{id}`](/api/v2/themes/update-theme) — Update a theme.
- [`DELETE /themes/{id}`](/api/v2/themes/delete-theme) — Delete a dev theme.
- [`PUT /themes/{id}/publication`](/api/v2/themes/publish-theme) — Publish a dev theme.
- [`POST /themes/{theme_id}/templates`](/api/v2/themes/create-template) — Create a template.
- [`PUT /themes/{theme_id}/templates/settings`](/api/v2/themes/update-theme-settings) — Update theme settings.
- [`GET /themes/{theme_id}/templates/{id}`](/api/v2/themes/get-template) — Get a template.
- [`PUT /themes/{theme_id}/templates/{id}`](/api/v2/themes/update-template) — Update a template.
- [`DELETE /themes/{theme_id}/templates/{id}`](/api/v2/themes/delete-template) — Delete a template.
- [`POST /themes/{theme_id}/assets`](/api/v2/themes/create-theme-asset) — Upload a theme asset.
- [`DELETE /themes/{theme_id}/assets/{id}`](/api/v2/themes/delete-theme-asset) — Delete a theme asset.
## Webhooks
Event webhooks. Subscribe to events such as `order.created`, `product.updated`,
`customer.created`, etc.
Failed subscriptions can be reactivated, and the recent events of a subscription's topic
inspected with `GET /webhooks/{id}/history`.
Creating a subscription needs `hub.write` plus read access to the topic's resource
(`orders.read` for `order.*`, `products.read` for `product.*`, and so on); otherwise 403.
Subscriptions belong to the seller, not to a single shop.
###### Activation
A new subscription is activated by a `POST` to its `url` with an `X-Hook-Ping` header and
the body `{"topic": "activation", "id", "status", "url"}`. Your endpoint must answer `2xx`
with the same value in an `X-Hook-Pong` header; any other answer leaves the subscription in
`failed_activation` and it receives nothing. Deploy the endpoint before subscribing.
```js
const ping = req.get('X-Hook-Ping')
if (ping) return res.set('X-Hook-Pong', ping).sendStatus(200)
```
###### Authenticating deliveries
Deliveries are **not signed**. To check that a request comes from Bolder, create the
subscription with `auth: {"type": "basic", "username": "...", "password": "..."}`: every
request to the `url`, the activation included, then carries those credentials as HTTP
Basic auth (`Authorization: Basic ...`). Use an `https` URL.
###### Delivery
Each event is a `POST` with `Content-Type: application/json`:
| Field | |
|---|---|
| `topic` | The event topic, in the subscription's spelling (`order.updated.closed`). |
| `item_type`, `item_id` | The record the event is about (`order`, `123`). |
| `changes` | Changed fields as `{"field": [old, new]}`. |
| `payload` | Extra data for the event, if any. |
| `created_on` | When the event happened. |
| `shop_id`, `shop_subdomain` | The shop it happened in. |
| `user_id`, `user_name`, `app_name` | Who caused it, when known. |
| `channel_id` | `hook_`. |
| `_embedded.item` | The record, as the API returns it. |
By default a subscription does not receive events caused by the application that created
it (`notify_origin: false`); set `notify_origin: true` to receive them too.
###### Retries and failures
- Requests time out after 30 seconds. Answer `2xx` quickly and do slow work in the background.
- A failed delivery is retried up to 10 times over about 24 hours, so the same event can
arrive more than once, and events can arrive out of order.
- Answering `410 Gone` disables the subscription at once (`status: disabled`); create it again
to resume.
- After 100 failed attempts in a row (retries included) the subscription is marked `failed`
and stops receiving events. Timeouts and `502`, `512` and `523` answers do not count towards
that limit. A successful delivery resets the count.
- When a subscription stops, a `webhook.disabled` event is published (and the merchant gets
an alert). After fixing the endpoint, `PUT /webhooks/{id}/reactivate` resumes a `failed`
subscription and publishes `webhook.reactivated`.
###### Topic spelling
The v2 API spells topics in the **singular**: `customer.created`, `order.updated.item.added`,
`product.updated.variant.sold_out`. Only the nouns change; verbs and states
(`updated`, `sold_out`, `opted_out`) stay as they are. The singular spelling is used
everywhere in v2: the `topic` of a subscription, the `topic` of the notifications it
receives, and the `topic` of events returned by `/events`.
On input the plural spelling (`customers.created`) is accepted too, in the `topic` of a
new subscription and in the `topic` filter of `/events`. A subscription created through v2
always receives the singular spelling, whichever one you sent. Subscriptions created before
this change, and those created through the v1 API, keep receiving the plural spelling so
their consumers are not affected: every subscription reports the spelling it receives in
`topic_style` (`singular` or `plural`).
###### Topic hierarchy
Topics are dot-separated and hierarchical. A subscription to a topic also receives every
topic below it: `order` receives all order events, `order.updated` receives
`order.updated.placed`, `order.updated.closed`, and so on, while `order.updated.closed`
receives only that one. Sibling topics are independent.
###### Customer events
Customer event topics use the singular `customer`:
| Topic | Sent when |
|---|---|
| `customer.created` | A customer is created, whichever way it happens: the API, a storefront sign-up, a newsletter or contact form, checkout, social login, or the admin. |
| `customer.updated` | A customer's profile data changes (name, email, phone, company, custom fields, opt-in, ...). Automatic bookkeeping does not count: order totals and counts, last login, timestamps and integration sync state never trigger it. |
| `customer.opted_in` | An **existing** customer opts in to email marketing. Sent together with `customer.updated`. |
| `customer.opted_out` | An **existing** customer opts out of email marketing. Sent together with `customer.updated`. |
| `customer.deleted` | A customer is permanently deleted. The payload is minimal (the customer's `id`, since the record no longer exists). |
Customer payloads never include the customer's activation token or the links that embed it.
A brand-new customer who opts in at sign-up only produces `customer.created`; check
`opt_in` in its payload. Internal placeholder customers (for example the shared
"Venta Presencial" customer used for point-of-sale sales without an identified buyer)
never produce events.
The payload of a `customer.*` webhook is the customer representation, the same one
returned by `GET /customers/{id}` (see the `Contact` schema). Events are delivered
asynchronously, usually within seconds of the change, and delivery order between
different changes is not guaranteed: use `updated_on` to order updates to the same
customer.
Subscribe to `customer` to receive all customer events, or to a specific topic to
receive only that one. `customer.updated` does not include `customer.opted_in` or
`customer.opted_out`, which are separate topics; a subscription to `customer` receives
both events for an opt-in change.
**Change to existing `customers.created` / `customers.updated` webhooks:** their payload
now uses the v2 customer representation. The deprecated `company` property is gone
(use `company_name`) and links point to v2 URLs (`/v2/customers/{id}`). Their topic keeps
the plural spelling unless the subscription was created through v2 (see Topic spelling).
###### Product stock and price events
| Topic | Sent when |
|---|---|
| `product.sold_out` | A product is no longer available in a stock location (its last available variant ran out there). |
| `product.restocked` | A product is available again in a stock location. |
| `product.updated.variant.sold_out` | A single variant ran out in a stock location. |
| `product.updated.variant.restocked` | A single variant is available again in a stock location. |
| `product.updated.variant.low_stock` | A variant's stock fell to the account's minimum (`critical_stock_amount`) or below, without being sold out. Also sent when a sold out variant comes back with that little stock. |
| `product.updated.variant.replenished` | A variant's stock went back above the account's minimum. |
| `product.price_dropped` | A product's final price went down (its sale price if it has one, otherwise its regular price). |
| `product.price_raised` | A product's final price went up. |
| `product.updated.variant.price_dropped` | A variant's final price went down. |
| `product.updated.variant.price_raised` | A variant's final price went up. |
The old and new price are in the event's `changes`: `{"price": [1000, 800], "sale_price": [1000, 800]}`,
in the currency's smallest unit (for CLP, whole pesos). A change that leaves the final price
as it was, such as raising the regular price while a sale price applies, sends nothing.
A product's price derives from its variants, so one price change can produce both a variant
event and a product event. Which of the two you receive depends on how the change was made,
and a change made through the product form of a single-variant product may only produce one of them:
subscribe to both `product.price_dropped` and `product.updated.variant.price_dropped` (and the `raised`
pair) if you must not miss a price change.
###### Refund events
| Topic | Sent when |
|---|---|
| `refund.created` | A refund was accepted by the payment gateway (successful). A refund that failed first and later succeeded is announced when it succeeds. |
| `refund.updated` | A successful refund changed afterwards (for example its description). |
The payload is the refund transaction (`order_id`, negative `amount`, `absolute_amount`, `payment_method_type`,
`transaction_id`, ...). The raw gateway reply is never included. Refund topics are not part of the `order`
family: subscribe to `refund`. A token needs `orders.read`.
###### Subscription seat events
| Topic | Sent when |
|---|---|
| `subscription.seat_added` | A customer was given a seat on a membership subscription. |
| `subscription.seat_removed` | A customer's seat on a membership subscription was removed. |
The payload is the subscription purchase. The seat's customer is in the event's `changes`
as `contact_id` and `contact_email`.
###### Subscription purchase events
| Topic | Sent when |
|---|---|
| `subscription.started` | The purchase became active (its order was paid, a free or fixed-term purchase began, or a cancelled one was restarted). |
| `subscription.renewed` | A renewal payment succeeded (automatic charge, renewal order paid at checkout, or `PUT /subscription_purchases/{id}/renew`). |
| `subscription.cancelled` | The purchase was cancelled. |
| `subscription.completed` | The purchase ended: its last payment was made, or a free or fixed-term purchase reached its end date. |
| `subscription.end_date_changed` | The `end_date` changed without the purchase starting, cancelling or completing. |
The payload is the subscription purchase as `GET /subscription_purchases/{id}` returns it
(`state`, `end_date`, `seats`, `company_id`, `_embedded.plan`, `_embedded.fee`, `_embedded.company`, ...)
and the event's `changes` holds the old and new values of what changed. Each transition sends one
event, whichever app saved the purchase. Subscribe to `subscription` for all of them (and the seat events).
A token needs `subscriptions.read` or an admin token.
###### Webhook events
| Topic | Sent when |
|---|---|
| `webhook.disabled` | One of your webhooks stopped being delivered: the receiver answered 410 Gone, or too many deliveries failed. The payload names the host only. |
| `webhook.reactivated` | A stopped webhook was reactivated (`PUT /webhooks/:id/reactivate`) or subscribed again. |
###### Email recipient events
| Topic | Sent when |
|---|---|
| `recipient.bounced` | The mail server reported that an email recipient's address of the shop cannot receive mail. The event's title is the address and its info the reason. |
| `recipient.bounce_cleared` | A bounced recipient's address was changed. |
| `recipient.deleted` | A recipient that had bounced was removed. Recipients that never bounced produce no event. |
###### Payment method events
| Topic | Sent when |
|---|---|
| `payment_method.failing` | A payment method rejected the payments of several different orders in a row, with no confirmed payment in between (a broken configuration or a gateway outage, rather than individual declines). Sent once per failure. |
| `payment_method.recovered` | A payment method confirmed a payment after it was announced as failing. |
###### Billing events
| Topic | Sent when |
|---|---|
| `billing.payment_failed` | The charge of the account's subscription was rejected. Sent once per failure, for each seller of the account. |
| `billing.paid` | The subscription was charged successfully after a rejected charge. |
| `billing.overdue` | The account fell into arrears (the payment retries were exhausted) and will be disabled if it is not regularized. |
| `billing.restored` | An account in arrears is paid up again. |
###### Plan events
| Topic | Sent when |
|---|---|
| `plan.limit_reached` | A seller uses 85% or more of a limit of its plan (products, or the orders of the month), and again when it goes over it. The payload says the `metric`, `status` (`near` or `exceeded`), `used` and `limit`. |
| `plan.limit_ok` | A limit that was reported is back under 85%. |
###### Theme events
| Topic | Sent when |
|---|---|
| `theme.render_error` | A template of the published theme raised a Liquid error while a page was shown. The payload has the `templates` (up to 3), the `total` and the `message`. Sent again when a new template is reported. |
| `theme.slow_render` | A template kept taking too long to render (reported in 3 different 10-minute windows). |
| `theme.missing_asset` | A template references an asset that does not exist. |
| `theme.render_fixed`, `theme.render_fast`, `theme.asset_found` | The problem was not reported for 3 days (checked daily). |
###### Domain events
| Topic | Sent when |
|---|---|
| `domain.cert_failing` | The SSL certificate of the shop's own domain could not be issued or renewed, or the domain stopped pointing to Bolder. The payload has the `domain`, the `reason` (`renewal_failed`, `issuance_failed`, `points_elsewhere`) and, for a renewal, the `days_left`. A renewal that works sends nothing. |
| `domain.cert_ok` | The certificate of a domain that was failing is issued or renewed. |
###### Backlog events
| Topic | Sent when |
|---|---|
| `backlog.review_stale` | A seller has orders that have been in review for more than 24 hours. Sent daily when the count changes; the payload has the `count`. |
| `backlog.review_clear` | No order of the seller is waiting in review for more than 24 hours any more. |
| `backlog.transfers_stale` | A seller has bank transfer orders unconfirmed for more than 3 days (`count` in the payload). |
| `backlog.transfers_clear` | No bank transfer order of the seller is waiting more than 3 days any more. |
| `backlog.partial_stale` | A seller has partial-payment orders whose balance is past its due date and still unpaid (`count` in the payload). |
| `backlog.partial_clear` | No partial-payment order of the seller has an overdue unpaid balance any more. |
###### Cart events
| Topic | Sent when |
|---|---|
| `cart.updated` | The cart of an identified customer changed: items, quantities or total. |
| `cart.deleted` | That cart was emptied. |
A cart is a draft order, so the payload is the order representation. Cart events are only sent for
carts that belong to an identified customer (a logged-in customer on the storefront), and only when
something actually changed. Cart topics are not part of the `order` family: subscribing to `order`
does not include them, subscribe to `cart`. A token needs `orders.read` to subscribe.
###### Collection events
| Topic | Sent when |
|---|---|
| `collection.created` | A collection is created. |
| `collection.updated` | A collection's title, slug, description, visibility or other data changes. Adding or removing products (the product counter) does not count. |
| `collection.deleted` | A collection is permanently deleted. The payload is minimal (the `id`). |
Collections belong to the whole account, not to one shop, so these events are recorded under the
account's main shop. A token needs `products.read` to subscribe.
###### Integration events
| Topic | Sent when |
|---|---|
| `integration.misconfigured` | A shop's integration (an add-on such as a mailing list, analytics or Facebook) stopped working on the merchant's side: a required option is missing, or the provider rejected its credentials. Sent once per breakage, not once per failed call. |
| `product.updated.variant.low_stock` | A variant is down to the account's minimum stock (`critical_stock_amount`). | Its stock is back above it (`product.updated.variant.replenished`). |
| `webhook.disabled` | A webhook stopped being delivered (the receiver answered 410 Gone, or too many deliveries failed). | It is reactivated or subscribed again (`webhook.reactivated`). |
| `order.notifications.refund_failed` | An automatic refund of an order (changes to the order applied, for example approved by the buyer in checkout) could not be made. The event's info is the amount. | A refund of the order succeeds (`refund.created`). |
| `payment_method.failing` | A payment method rejected the payments of several orders in a row. | It confirms a payment (`payment_method.recovered`). |
| `recipient.bounced` | The address of an email recipient of the shop cannot receive mail. | Its address is changed (`recipient.bounce_cleared`) or it is removed (`recipient.deleted`). |
| `billing.payment_failed` | The charge of the account's subscription was rejected. | The subscription is charged (`billing.paid`). |
| `billing.overdue` | The account is in arrears. | It is paid up (`billing.restored`). |
| `plan.limit_reached` | The seller uses 85% or more of a limit of its plan, or goes over it. | Usage is back under the limit (`plan.limit_ok`). |
| `backlog.review_stale` | Orders have been in review for more than 24 hours (one alert with a count). | None is left (`backlog.review_clear`). |
| `backlog.transfers_stale` | Bank transfer orders are unconfirmed after 3 days (one alert with a count). | None is left (`backlog.transfers_clear`). |
| `backlog.partial_stale` | Partial-payment orders have an overdue balance that was not collected (one alert with a count). | None is left (`backlog.partial_clear`). |
| `order.notifications.bounced` | The email of an order could not be delivered to the customer. | It stays in the feed (one alert per order). |
| `theme.render_error` | Templates of the published theme raise errors when they are shown. | Nothing has reported it for 3 days (`theme.render_fixed`). |
| `theme.slow_render` | Templates of the published theme keep taking too long to render. | Nothing has reported it for 3 days (`theme.render_fast`). |
| `theme.missing_asset` | The published theme references files that do not exist. | Nothing has reported it for 3 days (`theme.asset_found`). |
| `domain.cert_failing` | The SSL certificate of the shop's own domain could not be issued or renewed, or the domain no longer points to Bolder. | It is issued or renewed (`domain.cert_ok`). |
| `integration.recovered` | That integration worked again after being misconfigured. |
The payload has no body; the event's `payload` carries the add-on key (`integration`, eg. `google_analytics`) and, for `integration.misconfigured`, the `reason` (`options` or `credentials`). Subscribe to `integration`
for both. Only an admin token can subscribe.
- [`GET /webhooks`](/api/v2/webhooks/list-webhooks-flattened) — List webhooks.
- [`POST /webhooks`](/api/v2/webhooks/create-webhook-flattened) — Create a webhook.
- [`GET /webhooks/{id}`](/api/v2/webhooks/get-webhook-flattened) — Get a webhook.
- [`DELETE /webhooks/{id}`](/api/v2/webhooks/delete-webhook-flattened) — Delete a webhook.
- [`PUT /webhooks/{id}/reactivate`](/api/v2/webhooks/reactivate-webhook-flattened) — Reactivate a failed webhook.
- [`GET /webhooks/{id}/history`](/api/v2/webhooks/webhook-history-flattened) — Get webhook delivery history.
- [`GET /sellers/{seller_id}/webhooks`](/api/v2/webhooks/list-seller-webhooks) — List webhooks for a seller.
- [`POST /sellers/{seller_id}/webhooks`](/api/v2/webhooks/create-seller-webhook) — Create a webhook for a seller.
## Alerts
The notification feed of the admin (the bell): things a merchant should look at or act on. The kinds are
defined by the platform, they are not configured per account. Each one is raised by a webhook topic:
| `topic` | Raised when | Leaves the feed when |
|---|---|---|
| `product.sold_out` | A product is no longer available. | It is restocked (`product.restocked`). |
| `integration.misconfigured` | An integration (add-on) stopped working on the merchant's side: a required option is missing, or the provider rejected its credentials. | The integration works again (`integration.recovered`). |
Every open alert is in the feed for every user who can see the seller. **Seen and read are per user**, like
a bell: opening the feed marks what it shows as *seen* (the badge, `unseen_count`), reading an alert is
explicit and can be undone. One user reading an alert does not change it for the others. Alerts are open
or resolved: only open ones are listed. Who is *emailed* about an alert is decided by the notification
profile of each recipient, which opts in to the topics it wants (`GET /alerts/topics` lists the ones it can opt in
to). Every endpoint needs `shops.read`; all but `/alerts/topics` also need a token issued to a user.
- [`GET /alerts`](/api/v2/alerts/list-alerts) — List the alert feed.
- [`GET /alerts/topics`](/api/v2/alerts/list-alert-topics) — List the alert topics a notification profile can opt in to.
- [`POST /alerts/seen`](/api/v2/alerts/mark-alerts-seen) — Mark the feed as seen.
- [`POST /alerts/{id}/read`](/api/v2/alerts/mark-alert-read) — Mark an alert as read.
- [`DELETE /alerts/{id}/read`](/api/v2/alerts/mark-alert-unread) — Mark an alert as unread.
## Subscriptions
Recurring product subscriptions. `SubscriptionFee` defines the recurring
payment terms attached to a product/variant; `SubscriptionPurchase` is a
customer's active subscription created from a recurring order.
###### Membership seats
A subscription purchase of a membership plan can share its access with other customers
of the same company, one **seat** each. The purchasing customer occupies the first seat
implicitly; `max_seats` on the purchase (copied from the plan when it is created) is the
total including that customer, and an unset or zero value means unlimited. Seats are
listed, added and removed with `/subscription_purchases/{id}/seats`; subscription
purchases of other products have no seats (404). Adding or removing a seat sends
`subscription.seat_added` / `subscription.seat_removed` (see Webhooks). All seat
endpoints need `subscriptions.read` (list) or `subscriptions.write` (add, remove).
###### Scopes
Reading purchases (`/subscription_purchases`, `/status_counts`, `/stats`, show and seats) needs
`subscriptions.read`; updating, renewing and managing seats needs `subscriptions.write`.
`orders.read` / `orders.write` still work for both, so existing tokens keep their access;
a token that only has `subscriptions.*` cannot read orders.
###### Company
A purchase has a nullable `company_id` (`_embedded.company` {id,name}). It is copied from the
purchasing customer's company when the purchase is created from an order, and can be set or cleared
with `PUT /subscription_purchases/{id}` (the company must belong to the same seller). It is
informational: seat eligibility still compares the **customers'** companies.
- [`GET /subscription_purchases`](/api/v2/subscriptions/list-subscription-purchases) — List subscription purchases.
- [`GET /subscription_purchases/status_counts`](/api/v2/subscriptions/subscription-purchase-status-counts) — Subscription counts per state.
- [`GET /subscription_purchases/stats`](/api/v2/subscriptions/subscription-purchase-stats) — Subscriptions KPI strip.
- [`GET /subscription_purchases/{id}`](/api/v2/subscriptions/get-subscription-purchase) — Get a subscription purchase.
- [`PUT /subscription_purchases/{id}`](/api/v2/subscriptions/update-subscription-purchase) — Update a subscription purchase.
- [`PUT /subscription_purchases/{id}/renew`](/api/v2/subscriptions/renew-subscription-purchase) — Record a manual renewal.
- [`GET /subscription_purchases/{id}/seats`](/api/v2/subscriptions/list-membership-seats) — List membership seats.
- [`POST /subscription_purchases/{id}/seats`](/api/v2/subscriptions/create-membership-seat) — Add a membership seat.
- [`DELETE /subscription_purchases/{id}/seats/{seat_id}`](/api/v2/subscriptions/delete-membership-seat) — Remove a membership seat.
- [`GET /membership_plans`](/api/v2/subscriptions/list-membership-plans) — List membership plans.
- [`POST /membership_plans`](/api/v2/subscriptions/create-membership-plan) — Create a membership plan.
- [`GET /membership_plans/{id}`](/api/v2/subscriptions/get-membership-plan) — Get a membership plan.
- [`PUT /membership_plans/{id}`](/api/v2/subscriptions/update-membership-plan) — Update a membership plan.
- [`DELETE /membership_plans/{id}`](/api/v2/subscriptions/delete-membership-plan) — Delete a membership plan.
- [`GET /subscription_fees`](/api/v2/subscriptions/list-subscription-fees) — List subscription fees.
- [`POST /subscription_fees`](/api/v2/subscriptions/create-subscription-fee) — Create a subscription fee.
- [`GET /subscription_fees/{id}`](/api/v2/subscriptions/get-subscription-fee) — Get a subscription fee.
- [`PUT /subscription_fees/{id}`](/api/v2/subscriptions/update-subscription-fee) — Update a subscription fee.
- [`DELETE /subscription_fees/{id}`](/api/v2/subscriptions/delete-subscription-fee) — Delete a subscription fee.
## Wallets
Points and store-credit wallets, one per contact per program. A seller runs
at most one active `PointsProgram` and one active `CreditProgram`, each
covering every shop under the seller. Use `register_purchase` /
`unregister_purchase` to award or reverse points for completed orders.
Credit wallets carry a spending limit and accrue payments toward it.
- [`GET /wallet_programs`](/api/v2/wallets/list-wallet-programs) — List wallet programs.
- [`GET /wallet_programs/{id}`](/api/v2/wallets/get-wallet-program) — Get a wallet program.
- [`PUT /wallet_programs/{id}`](/api/v2/wallets/update-wallet-program) — Update a wallet program's settings.
- [`GET /wallets`](/api/v2/wallets/list-wallets) — List wallets.
- [`POST /wallets`](/api/v2/wallets/create-wallet) — Create (or find) a wallet for a contact.
- [`GET /wallets/{id}`](/api/v2/wallets/get-wallet) — Get a wallet.
- [`GET /wallets/{wallet_id}/transfers`](/api/v2/wallets/list-wallet-transfers) — List transfers for a wallet.
- [`POST /wallets/{wallet_id}/transfers`](/api/v2/wallets/add-wallet-transfer) — Add a balance or payment transfer.
- [`POST /wallets/{id}/register_purchase`](/api/v2/wallets/register-wallet-purchase) — Register points earned from a completed purchase.
- [`POST /wallets/{id}/unregister_purchase`](/api/v2/wallets/unregister-wallet-purchase) — Reverse points earned from a purchase (e.g. on order cancellation).
## Catalog
- [`GET /vendors`](/api/v2/catalog/list-vendors) — List vendors.
- [`GET /product_sets`](/api/v2/catalog/list-product-sets) — List product sets.
- [`GET /product_groups`](/api/v2/catalog/list-product-groups) — List product groups.
## Store
- [`GET /themes`](/api/v2/store/list-themes-flattened) — List themes.
---
# List the alert topics a notification profile can opt in to
Source: https://developers.onbolder.com/api/v2/alerts/list-alert-topics
The topics that raise alerts, with the Spanish label and description to show them. The same for every
account, so it needs no user token. `profile_entry` is the value a notification profile entry holds to
opt a recipient in to that topic's alert emails (the canonical spelling, the one `Alert#kind` has);
`topic` is the same topic in the v2 spelling an alert shows. Topics that only resolve an alert
(`product.restocked`, `integration.recovered`) are not listed.
`GET /alerts/topics`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/alerts/topics"
```
**Responses**
- `200` — Alert topics
- `403` — The token has no \`shops.read\`
Response example, 200:
```json
{
"total_items": 2,
"_embedded": {
"items": [
{
"topic": "integration.misconfigured",
"profile_entry": "integrations.misconfigured",
"label": "Integración mal configurada",
"description": "Una integración de tu tienda dejó de funcionar: falta un dato requerido o el servicio rechazó las credenciales"
},
{
"topic": "product.sold_out",
"profile_entry": "products.sold_out",
"label": "Producto agotado",
"description": "Un producto se quedó sin stock"
}
]
}
}
```
---
# List the alert feed
Source: https://developers.onbolder.com/api/v2/alerts/list-alerts
The open alerts the signed-in user may see, most recently seen first (up to 50), with their own
`seen` / `read` state and the number of alerts they have not seen yet.
`GET /alerts`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/alerts?seller_id=1"
```
**Responses**
- `200` — Feed
- `403` — Not a user token, or no access to the seller
- `422` — \`seller\_id\` is required (the token has several sellers)
Response example, 200:
```json
{
"total_items": 1,
"unseen_count": 1,
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/alerts?seller_id=1"
},
"shops:mark_alerts_seen": {
"href": "https://api.onbolder.com/v2/alerts/seen?seller_id=1",
"method": "post"
}
},
"_embedded": {
"items": [
{
"id": 12,
"topic": "product.sold_out",
"severity": "warning",
"message": "Polera azul está agotado",
"count": 1,
"shop_id": 3,
"source_type": "Product",
"source_id": 458,
"first_seen_at": "2026-10-20T13:02:11Z",
"last_seen_at": "2026-10-20T13:02:11Z",
"seen": false,
"read": false,
"_links": {
"shops:mark_alert_read": {
"href": "https://api.onbolder.com/v2/alerts/12/read",
"method": "post"
}
}
}
]
}
}
```
---
# Mark an alert as read
Source: https://developers.onbolder.com/api/v2/alerts/mark-alert-read
Also counts as seen. Only for the signed-in user.
`POST /alerts/{id}/read`
**cURL**
```bash
curl -s -X POST -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/alerts/12/read"
```
**Responses**
- `200` — The alert, with \`read: true\`
- `403` — Not a user token, or no access to the seller
- `404` — The alert does not exist
---
# Mark an alert as unread
Source: https://developers.onbolder.com/api/v2/alerts/mark-alert-unread
Only for the signed-in user. It stays seen.
`DELETE /alerts/{id}/read`
**cURL**
```bash
curl -s -X DELETE -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/alerts/12/read"
```
**Responses**
- `200` — The alert, with \`read: false\`
- `403` — Not a user token, or no access to the seller
- `404` — The alert does not exist
---
# Mark the feed as seen
Source: https://developers.onbolder.com/api/v2/alerts/mark-alerts-seen
The user opened the feed: what it shows counts as seen by them (the badge goes to 0). Returns the feed.
`POST /alerts/seen`
**cURL**
```bash
curl -s -X POST -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/alerts/seen?seller_id=1"
```
**Responses**
- `200` — The feed, with \`unseen\_count\` 0
- `403` — Not a user token, or no access to the seller
---
# List product groups
Source: https://developers.onbolder.com/api/v2/catalog/list-product-groups
Returns aggregated product groups (collections, product types, vendors, tags) — a
cross-collection discovery search, not CRUD on a real "product group" table.
Filtering:
- **`shop_id`** — scopes to that shop's seller.
- **`seller_id`** — scopes to that seller.
- Neither given — account-scoped, across all of the token's sellers.
`q` (substring match on name/slug) and `id` + `group_type` (direct lookup of a single
group; `group_type` is one of `collection`, `product_type`, `vendor`, `tag`) work the same
way under all three filters. Tags are only included in the `q` and `id`/`group_type`
results — the unfiltered/default listing (most-recently-updated) never includes tags.
Not paginated — only `per_page` applies (default 10, max 25); there is no `page` param.
`GET /product_groups`
**Responses**
- `200` — Product groups list
Response example, 200:
```json
{
"_class": [
"results",
"product_groups"
],
"total_items": 3,
"per_page": 10,
"page": 1,
"_embedded": {
"items": [
{
"type": "collection",
"id": 1,
"name": "Summer",
"slug": "summer",
"products_count": 12,
"updated_at": "2026-07-01T10:00:00Z"
},
{
"type": "product_type",
"id": 2,
"name": "Clothing",
"slug": "clothing",
"products_count": 45,
"updated_at": "2026-06-15T09:30:00Z"
}
]
}
}
```
---
# List product sets
Source: https://developers.onbolder.com/api/v2/catalog/list-product-sets
Returns product sets across the account.
Optionally filter by `shop_id` or `seller_id` query parameter.
`GET /product_sets`
**Responses**
- `200` — Product sets list
---
# List vendors
Source: https://developers.onbolder.com/api/v2/catalog/list-vendors
Returns vendors across the account.
Optionally filter by `shop_id` or `seller_id` query parameter.
`GET /vendors`
**cURL**
```bash
# List all vendors for account
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/vendors"
# Filter by shop
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/vendors?shop_id=1"
# Filter by seller
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/vendors?seller_id=5"
```
**Responses**
- `200` — Vendors list
Response example, 200:
```json
{
"_class": [
"results",
"vendors"
],
"total_items": 2,
"_embedded": {
"items": [
{
"id": 1,
"name": "Acme Corp",
"slug": "acme-corp",
"products_count": 15
}
]
}
}
```
---
# Delete a collection
Source: https://developers.onbolder.com/api/v2/collections/delete-collection
Requires `products.write` scope on the collection's seller.
`DELETE /collections/{id}`
**Responses**
- `204` — Collection deleted
- `403` — Access denied
---
# Get a collection
Source: https://developers.onbolder.com/api/v2/collections/get-collections-id
`GET /collections/{id}`
**Responses**
- `200` — Collection details
Response example, 200:
```json
{
"id": 5,
"title": "Summer Collection",
"slug": "summer"
}
```
---
# List collections
Source: https://developers.onbolder.com/api/v2/collections/list-collections
Returns collections for a shop. `shop_id` is required.
`GET /collections`
**Responses**
- `200` — Paginated collections list
Response example, 200:
```json
{
"_class": [
"results",
"collections"
],
"total_items": 2,
"per_page": 20,
"page": 1,
"_embedded": {
"items": [
{
"id": 5,
"title": "Summer Collection",
"slug": "summer",
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/collections/5"
}
}
}
]
}
}
```
---
# Create a form
Source: https://developers.onbolder.com/api/v2/content/create-form
`POST /shops/{shop_id}/forms`
**Request body** (`application/json`, required)
- `title` (string, required)
- `body` (string) — Intro text shown above the form fields
- `slug` (string)
- `description` (string)
- `visible` (boolean)
- `format` (string) — \`markdown\` is accepted as input and converted to \`html\` before saving; the stored format will be \`html\`.
- `has_address_fields` (boolean)
- `success_message` (string)
Request body example:
```json
{
"title": "Contact Us",
"has_address_fields": false
}
```
**Responses**
- `201` — Form created
- `422` — Validation error
Response example, 201:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"shop_id": 0,
"title": "string",
"slug": "string",
"body": "string",
"description": "string",
"visible": true,
"format": "string",
"has_address_fields": true,
"success_message": "string",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
```
---
# Create a page
Source: https://developers.onbolder.com/api/v2/content/create-page
`POST /shops/{shop_id}/pages`
**Request body** (`application/json`, required)
- `title` (string, required)
- `body` (string)
- `slug` (string)
- `description` (string)
- `visible` (boolean)
- `format` (string) — \`markdown\` is accepted as input and converted to \`html\` before saving; the stored format will be \`html\`.
Request body example:
```json
{
"title": "About Us",
"body": "We are a small shop.
"
}
```
**Responses**
- `201` — Page created
- `422` — Validation error
Response example, 201:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"shop_id": 0,
"title": "string",
"slug": "string",
"body": "string",
"description": "string",
"visible": true,
"format": "string",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
```
---
# Create a post
Source: https://developers.onbolder.com/api/v2/content/create-post
Creates a post in the shop's default blog, or in the blog given by `blog_id` (see
[List shop blogs](/api/v2/content/list-shop-blogs)).
To publish a translation, create it in a blog of another language and set `translation_of` to the
original post's ID: its `translation_lang` is taken from that blog. An original post can have only one
translation per language, and a translation can't itself be translated.
`format: blocks` takes an EditorJS JSON document, serialized as a string, in `body`.
`POST /shops/{shop_id}/posts`
**cURL**
```bash
curl -s -X POST \
-H "Authorization: Bearer $BOLDER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Winter Sale","body":"Save big!
","published":true}' \
"https://api.onbolder.com/v2/shops/1/posts"
```
**Request body** (`application/json`, required)
- `title` (string, required)
- `body` (string, required)
- `slug` (string) — Auto-generated from title if blank
- `description` (string)
- `short_description` (string)
- `published` (boolean)
- `published_on` (string)
- `format` (string) — \`markdown\` is stored as is and rendered by the storefront.
- `blog_id` (integer) — Blog to publish in. Defaults to the shop's default blog. Must belong to this shop.
- `translation_of` (integer) — ID of the post this one translates. The post must belong to this shop, not be a translation itself, and the blog must have a language different from the original's.
Request body example, A post:
```json
{
"title": "Winter Sale",
"body": "Save big!
",
"published": true
}
```
Request body example, A draft translation, in blocks format, in a Spanish blog:
```json
{
"title": "Venta de invierno",
"format": "blocks",
"body": "{\"time\":1767225600000,\"blocks\":[{\"type\":\"paragraph\",\"data\":{\"text\":\"¡Ahorra en grande!\"}}]}",
"blog_id": 2,
"translation_of": 41,
"published": false
}
```
**Responses**
- `201` — Post created
- `422` — Validation error
Response example, 201:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"shop_id": 0,
"title": "string",
"slug": "string",
"body": "string",
"description": "string",
"short_description": "string",
"published": true,
"published_on": "2019-08-24T14:15:22Z",
"format": "html",
"blog_id": 0,
"translation_of": 0,
"translation_lang": "string",
"user_id": 0,
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"path": "string",
"url": "string"
}
```
---
# Delete a form
Source: https://developers.onbolder.com/api/v2/content/delete-form
`DELETE /forms/{id}`
**Responses**
- `204` — Form deleted
---
# Delete a page
Source: https://developers.onbolder.com/api/v2/content/delete-page
`DELETE /pages/{id}`
**Responses**
- `204` — Page deleted
---
# Delete a post
Source: https://developers.onbolder.com/api/v2/content/delete-post
`DELETE /posts/{id}`
**Responses**
- `204` — Post deleted
---
# Get a blog
Source: https://developers.onbolder.com/api/v2/content/get-blog
`GET /blogs/{id}`
**Responses**
- `200` — Blog entity
- `403` — Not allowed to see this blog
- `404` — Blog not found
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"shop_id": 0,
"title": "string",
"lang": "string",
"base_path": "string",
"default": true,
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
```
---
# Get a form
Source: https://developers.onbolder.com/api/v2/content/get-form
`GET /forms/{id}`
**Responses**
- `200` — Form entity
- `404` — Form not found
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"shop_id": 0,
"title": "string",
"slug": "string",
"body": "string",
"description": "string",
"visible": true,
"format": "string",
"has_address_fields": true,
"success_message": "string",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
```
---
# Get a page
Source: https://developers.onbolder.com/api/v2/content/get-page
`GET /pages/{id}`
**Responses**
- `200` — Page entity
- `404` — Page not found
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"shop_id": 0,
"title": "string",
"slug": "string",
"body": "string",
"description": "string",
"visible": true,
"format": "string",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
```
---
# Get a post
Source: https://developers.onbolder.com/api/v2/content/get-post
`GET /posts/{id}`
**Responses**
- `200` — Post entity
- `404` — Post not found
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"shop_id": 0,
"title": "string",
"slug": "string",
"body": "string",
"description": "string",
"short_description": "string",
"published": true,
"published_on": "2019-08-24T14:15:22Z",
"format": "html",
"blog_id": 0,
"translation_of": 0,
"translation_lang": "string",
"user_id": 0,
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"path": "string",
"url": "string"
}
```
---
# List shop blogs
Source: https://developers.onbolder.com/api/v2/content/list-shop-blogs
Returns the shop's blogs. A shop can have several, each with its own language; use a blog's `id` as
`blog_id` when creating posts, e.g. to publish a translation. Not paginated.
`GET /shops/{shop_id}/blogs`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/shops/1/blogs"
```
**Responses**
- `200` — Blogs list
- `403` — Not allowed to see this shop's blogs
Response example, 200:
```json
{
"_class": [
"results",
"blogs"
],
"total_items": 2,
"_embedded": {
"items": [
{
"id": 1,
"shop_id": 1,
"title": "Blog",
"lang": "en",
"base_path": "blog",
"default": true
},
{
"id": 2,
"shop_id": 1,
"title": "Blog en español",
"lang": "es",
"base_path": "blog-es",
"default": false
}
]
}
}
```
---
# List forms
Source: https://developers.onbolder.com/api/v2/content/list-shop-forms
Returns contact/lead-capture forms for the shop.
`GET /shops/{shop_id}/forms`
**Responses**
- `200` — Forms list
Response example, 200:
```json
{
"_class": [
"results",
"forms"
],
"total_items": 1,
"_embedded": {
"items": [
{
"id": 1,
"shop_id": 1,
"title": "Contact Us",
"slug": "contact-us",
"visible": true
}
]
}
}
```
---
# List pages
Source: https://developers.onbolder.com/api/v2/content/list-shop-pages
Returns static content pages for the shop.
`GET /shops/{shop_id}/pages`
**Responses**
- `200` — Pages list
Response example, 200:
```json
{
"_class": [
"results",
"pages"
],
"total_items": 3,
"_embedded": {
"items": [
{
"id": 1,
"shop_id": 1,
"title": "About Us",
"slug": "about-us",
"visible": true
}
]
}
}
```
---
# List shop posts
Source: https://developers.onbolder.com/api/v2/content/list-shop-posts
Returns blog/news posts for the shop.
`GET /shops/{shop_id}/posts`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/shops/1/posts"
```
**Responses**
- `200` — Posts list
Response example, 200:
```json
{
"_class": [
"results",
"posts"
],
"total_items": 5,
"_embedded": {
"items": [
{
"id": 1,
"shop_id": 1,
"title": "Summer Sale is Here",
"slug": "summer-sale",
"published": true,
"published_on": "2024-06-01T00:00:00Z",
"blog_id": 1,
"translation_of": null,
"translation_lang": null,
"created_on": "2024-06-01T00:00:00Z"
}
]
}
}
```
---
# Update a form
Source: https://developers.onbolder.com/api/v2/content/update-form
`PUT /forms/{id}`
**Request body** (`application/json`, required)
- `title` (string)
- `slug` (string)
- `description` (string)
- `format` (string) — \`markdown\` is accepted as input and converted to \`html\` before saving.
- `body` (string)
- `visible` (boolean)
- `success_message` (string)
- `has_address_fields` (boolean)
Request body example:
```json
{
"title": "string",
"slug": "string",
"description": "string",
"format": "html",
"body": "string",
"visible": true,
"success_message": "string",
"has_address_fields": true
}
```
**Responses**
- `200` — Form updated
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"shop_id": 0,
"title": "string",
"slug": "string",
"body": "string",
"description": "string",
"visible": true,
"format": "string",
"has_address_fields": true,
"success_message": "string",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
```
---
# Update a page
Source: https://developers.onbolder.com/api/v2/content/update-page
`PUT /pages/{id}`
**Request body** (`application/json`, required)
- `title` (string)
- `slug` (string)
- `description` (string)
- `format` (string) — \`markdown\` is accepted as input and converted to \`html\` before saving.
- `body` (string)
- `visible` (boolean)
Request body example:
```json
{
"title": "string",
"slug": "string",
"description": "string",
"format": "html",
"body": "string",
"visible": true
}
```
**Responses**
- `200` — Page updated
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"shop_id": 0,
"title": "string",
"slug": "string",
"body": "string",
"description": "string",
"visible": true,
"format": "string",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
```
---
# Update a post
Source: https://developers.onbolder.com/api/v2/content/update-post
`PUT /posts/{id}`
**Request body** (`application/json`, required)
- `title` (string)
- `body` (string)
- `slug` (string)
- `description` (string)
- `short_description` (string)
- `published` (boolean)
- `published_on` (string)
- `format` (string) — \`markdown\` is stored as is and rendered by the storefront.
- `blog_id` (integer) — Move the post to another blog of this shop. A translation takes its \`translation\_lang\` from the new blog.
- `translation_of` (integer | null) — Make this post a translation of another one (same rules as when creating), or send \`null\` to detach it and make it an original post again.
Request body example:
```json
{
"title": "string",
"body": "string",
"slug": "string",
"description": "string",
"short_description": "string",
"published": true,
"published_on": "2019-08-24T14:15:22Z",
"format": "html",
"blog_id": 0,
"translation_of": 0
}
```
**Responses**
- `200` — Post updated
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"shop_id": 0,
"title": "string",
"slug": "string",
"body": "string",
"description": "string",
"short_description": "string",
"published": true,
"published_on": "2019-08-24T14:15:22Z",
"format": "html",
"blog_id": 0,
"translation_of": 0,
"translation_lang": "string",
"user_id": 0,
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"path": "string",
"url": "string"
}
```
---
# Activate a customer account
Source: https://developers.onbolder.com/api/v2/customers/activate-customer
Completes the customer account activation flow by setting the initial
password. The `token` is sent to the customer's email after the
merchant triggers `customers:start_activation`.
`PUT /customers/activations/{token}`
**Request body** (`application/json`, required)
- `password` (string, required) — The new password for the account (min 8 chars).
Request body example:
```json
{
"password": "newSecureP@ss1"
}
```
**Responses**
- `200` — Account activated
- `422` — Invalid or expired token
Response example, 200:
```json
{
"id": 3001,
"email": "jane@example.com",
"status": "private",
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/customers/3001"
}
}
}
```
---
# Authenticate a customer
Source: https://developers.onbolder.com/api/v2/customers/authenticate-customer
Validates email + password credentials for a shop's customer account.
Returns the contact entity with a session token on success.
Requires a Bearer token with `customers.write` scope (the same token used for
all v2 requests) and `shop_id` in the request body.
`POST /customers/sessions`
**Request body** (`application/json`, required)
- `shop_id` (integer, required) — ID of the shop the customer belongs to.
- `email` (string, required)
- `password` (string, required)
Request body example:
```json
{
"shop_id": 1,
"email": "jane@example.com",
"password": "s3cr3tpassword"
}
```
**Responses**
- `200` — Authentication successful
- `422` — Invalid credentials
Response example, 200:
```json
{
"id": 3001,
"email": "jane@example.com",
"name": "Jane Smith",
"customer_status": "private",
"token": "eyJhbGciOiJIUzI1NiJ9...",
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/customers/3001"
}
}
}
```
---
# Create a contact group (legacy shop-nested)
Source: https://developers.onbolder.com/api/v2/customers/create-contact-group
Creates a customer group for the shop.
`POST /shops/{shop_id}/customer_groups`
**cURL**
```bash
curl -s -X POST \
-H "Authorization: Bearer $BOLDER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"VIP Customers"}' \
"https://api.onbolder.com/v2/shops/1/customer_groups"
```
**Request body** (`application/json`, required)
- `name` (string, required)
- `cart_requirements_list_id` (integer)
- `price_list_id` (integer)
- `allow_local_pickup` (boolean)
- `allow_shipping` (boolean)
Request body example:
```json
{
"name": "VIP Customers"
}
```
**Responses**
- `201` — Contact group created
- `422` — Validation error
---
# Create a contact group (flat)
Source: https://developers.onbolder.com/api/v2/customers/create-contact-group-flat
`seller_id` is required in the body.
`POST /contact_groups`
**cURL**
```bash
curl -s -X POST \
-H "Authorization: Bearer $BOLDER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"seller_id":5,"name":"VIP Customers"}' \
"https://api.onbolder.com/v2/contact_groups"
```
**Request body** (`application/json`, required)
- `seller_id` (integer, required)
- `name` (string, required)
- `cart_requirements_list_id` (integer)
- `price_list_id` (integer)
- `allow_local_pickup` (boolean)
- `allow_shipping` (boolean)
Request body example:
```json
{
"seller_id": 5,
"name": "VIP Customers"
}
```
**Responses**
- `201` — Contact group created
- `422` — Validation error
---
# Create a customer
Source: https://developers.onbolder.com/api/v2/customers/create-customer
If a contact with the given `email` already exists for this shop, it is updated rather than duplicated.
`POST /customers`
**cURL**
```bash
curl -s -X POST \
-H "Authorization: Bearer $BOLDER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"shop_id":1,"email":"newcustomer@example.com","name":"Jane Smith"}' \
"https://api.onbolder.com/v2/customers"
```
**Request body** (`application/json`, required)
- `email` (string, required) — Contact email address. Used as the unique identifier for find-or-create.
- `name` (string, required) — Full name (first and last).
- `phone_number` (string) — Required when creating a new contact.
- `company_name` (string) — Company name. Creates or finds a company record automatically.
- `company_id_number` (string) — Company tax/ID number (RUT, DNI, etc.), stored with the company record.
- `tax_id` (string) — The customer's own tax id (RUT, CUIT, NIT, etc.), any format; it is stored normalized (uppercase letters and digits). An empty string clears it. A Chilean one (\`tax\_id\_country\` CL) must have a valid check digit, else \`422\` on \`$.tax\_id\`.
- `tax_id_country` (string) — ISO 3166-1 alpha-2 country that issued the tax id. Defaults to the shop's country; \`422\` on \`$.tax\_id\_country\` if malformed.
- `tags` (string[]) — Tag names. Replaces all existing tags.
- `opt_in` (boolean) — Whether the contact accepts marketing emails/newsletters.
- `purchase_blocked` (boolean) — \`true\` blocks the customer from placing orders (moving them from draft/checkout to pending, in review, closed or completed; orders already placed can still be paid and fulfilled), \`false\` unblocks and clears reason, date and author. Admin tokens only: ignored on create and \`403\` on update for other tokens. The author is the token's user.
- `purchase_block_reason` (string) — Why the customer is blocked (staff only, never sent to webhooks). On an already blocked customer it only changes the reason.
- `contact_group_id` (integer) — Contact group ID to assign this contact to.
- `salesperson_id` (string) — Shop admin user ID
- `source` (string) — Origin of the contact record. Defaults to \`API\` if omitted on create.
- `custom_data` (object[]) — Custom field values for this contact.
- `shop_id` (integer, required)
Request body example:
```json
{
"shop_id": 1,
"email": "newcustomer@example.com",
"name": "Jane Smith"
}
```
**Responses**
- `201` — Customer created
- `422` — Validation error
Response example, 201:
```json
{
"id": 3001,
"shop_id": 1,
"email": "newcustomer@example.com",
"name": "Jane Smith",
"customer_status": "public",
"phone_validated": false,
"created_on": "2024-06-20T14:00:00Z",
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/customers/3001"
},
"orders": {
"href": "https://api.onbolder.com/v2/orders?shop_id=1&contact_email=newcustomer@example.com"
}
}
}
```
---
# Create a company
Source: https://developers.onbolder.com/api/v2/customers/create-shop-company
Needs `customers.write`. The name is unique per seller, and so are the tax id (validated as a Chilean RUT) and the
email domain. Unknown fields are rejected with `422`.
`POST /shops/{shop_id}/companies`
**Request body** (`application/json`, required)
- `name` (string, required)
- `id_number` (string) — Tax id (RUT), stored without dots and dash.
- `activity_code` (integer)
- `email_domain` (string) — New customers with this email domain join the company.
Request body example:
```json
{
"name": "string",
"id_number": "string",
"activity_code": 0,
"email_domain": "string"
}
```
**Responses**
- `201` — The created company
- `403` — Access denied
- `422` — Invalid or duplicate values, or unknown fields
---
# Delete a contact group
Source: https://developers.onbolder.com/api/v2/customers/delete-contact-group
`DELETE /contact_groups/{id}`
**Responses**
- `204` — Contact group deleted
- `403` — Access denied
---
# Demote a contact
Source: https://developers.onbolder.com/api/v2/customers/demote-contact
`DELETE /customers/{id}/activation`
**Responses**
- `204` — Contact demoted
---
# Get a contact group
Source: https://developers.onbolder.com/api/v2/customers/get-contact-group
`GET /contact_groups/{id}`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/contact_groups/1"
```
**Responses**
- `200` — Contact group entity
- `403` — Access denied
- `404` — Not found
Response example, 200:
```json
{
"id": 1,
"name": "VIP Customers",
"customers_count": 42
}
```
---
# Get a customer
Source: https://developers.onbolder.com/api/v2/customers/get-customer
`GET /customers/{id}`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/customers/3001"
```
**Responses**
- `200` — Customer entity
- `403` — Access denied
- `404` — Customer not found
Response example, 200:
```json
{
"id": 3001,
"shop_id": 1,
"email": "jane@example.com",
"name": "Jane Smith",
"customer_status": "private",
"phone_number": "+15551234567",
"phone_validated": true,
"created_on": "2024-03-10T11:00:00Z",
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/customers/3001"
},
"orders": {
"href": "https://api.onbolder.com/v2/orders?shop_id=1&contact_email=jane@example.com"
},
"customers:update": {
"href": "https://api.onbolder.com/v2/customers/3001",
"method": "put"
}
}
}
```
---
# Recent activity of a customer
Source: https://developers.onbolder.com/api/v2/customers/get-customer-activity
Orders placed, messages, last sign-in and purchase block, newest first. Items have `type` (order_placed, message, sign_in, purchase_blocked), `occurred_at` and type-specific ids.
`GET /customers/{id}/activity`
**Responses**
- `200` — Paginated activity events in \`\_embedded.items\`
---
# Get last ordered products
Source: https://developers.onbolder.com/api/v2/customers/get-customer-last-ordered-products
`GET /customers/{id}/last_ordered_products`
**Responses**
- `200` — Last ordered products
---
# Get a company
Source: https://developers.onbolder.com/api/v2/customers/get-shop-company
`GET /shops/{shop_id}/companies/{id}`
**Responses**
- `200` — The company
- `403` — Access denied
- `404` — Not a company of this shop's seller
---
# List contacts in a group
Source: https://developers.onbolder.com/api/v2/customers/list-contact-group-contacts
Returns all contacts belonging to this contact group.
`GET /contact_groups/{id}/contacts`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/contact_groups/1/contacts"
```
**Responses**
- `200` — Contacts in this group
Response example, 200:
```json
{
"_class": [
"results",
"customers"
],
"total_items": 42,
"_embedded": {
"items": [
{
"id": 3001,
"email": "jane@example.com",
"name": "Jane Smith"
}
]
}
}
```
---
# List contact groups (legacy shop-nested)
Source: https://developers.onbolder.com/api/v2/customers/list-contact-groups
Returns customer groups (segments) for the shop.
`GET /shops/{shop_id}/customer_groups`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/shops/1/customer_groups"
```
**Responses**
- `200` — Contact groups list
Response example, 200:
```json
{
"_class": [
"results",
"contactGroups"
],
"total_items": 2,
"_embedded": {
"items": [
{
"id": 1,
"name": "VIP Customers",
"customers_count": 42,
"allow_local_pickup": true,
"allow_shipping": true,
"allow_signups": false
}
]
}
}
```
---
# List contact groups (flat)
Source: https://developers.onbolder.com/api/v2/customers/list-contact-groups-flat
Returns customer groups (segments). Optionally filter by `shop_id` or `seller_id`; without either, returns groups across all of the account's sellers.
`GET /contact_groups`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/contact_groups?seller_id=5"
```
**Responses**
- `200` — Contact groups list
Response example, 200:
```json
{
"_class": [
"results",
"contactGroups"
],
"total_items": 2,
"_embedded": {
"items": [
{
"id": 1,
"name": "VIP Customers",
"customers_count": 42
}
]
}
}
```
---
# List customer tags for a shop
Source: https://developers.onbolder.com/api/v2/customers/list-contact-tags
Returns all tags currently applied to customers in this shop.
`GET /shops/{shop_id}/customer_tags`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/shops/1/customer_tags"
```
**Responses**
- `200` — Customer tags list
Response example, 200:
```json
{
"_class": [
"results",
"tags"
],
"_embedded": {
"items": [
{
"name": "vip"
},
{
"name": "newsletter"
}
]
}
}
```
---
# List customer tags
Source: https://developers.onbolder.com/api/v2/customers/list-customer-tags
Returns customer tags across the account.
Optionally filter by `shop_id` or `seller_id` query parameter.
`GET /customer_tags`
**Responses**
- `200` — Customer tags list
---
# List customers
Source: https://developers.onbolder.com/api/v2/customers/list-customers
Returns customers for a shop, or for a whole seller across all of its shops.
`shop_id` and `seller_id` are both optional. If the token covers exactly
one shop, that shop is auto-resolved and results are scoped to it. If the
token covers multiple shops belonging to a single seller, omitting both
params returns customers across all of that seller's shops. A token
covering multiple sellers (or a god token) must pass `shop_id` or
`seller_id` explicitly — there is no "all sellers" search.
`GET /customers`
**cURL**
```bash
# List all customers for a shop
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/customers?shop_id=1"
# List customers across all of a seller's shops
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/customers?seller_id=1"
# Search by name or email
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/customers?shop_id=1&q=jane"
```
**Responses**
- `200` — Paginated list of contacts
- `403` — Access denied
- `422` — \`shop\_id\` or \`seller\_id\` required — the token covers more than one shop and more than one seller (or is a god token), so neither can be auto-resolved.
Response example, 200:
```json
{
"_class": [
"results",
"customers"
],
"total_items": 1,
"per_page": 20,
"page": 1,
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/customers?shop_id=1"
},
"customers:authenticate": {
"href": "https://api.onbolder.com/v2/customers/sessions",
"method": "post"
},
"customers:reset_password": {
"href": "https://api.onbolder.com/v2/customers/reset_password",
"method": "post"
}
},
"_embedded": {
"items": [
{
"id": 3001,
"shop_id": 1,
"email": "jane@example.com",
"name": "Jane Smith",
"customer_status": "private",
"phone_validated": false,
"created_on": "2024-03-10T11:00:00Z",
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/customers/3001"
},
"orders": {
"href": "https://api.onbolder.com/v2/orders?shop_id=1&contact_email=jane@example.com"
}
}
}
]
}
}
```
---
# List companies (empresas)
Source: https://developers.onbolder.com/api/v2/customers/list-shop-companies
The seller's companies (b2b customers), sorted by name. Needs `customers.read` or any orders scope,
including `orders.read_own` (a dealer needs the ID of their empresa to set it on their orders).
`GET /shops/{shop_id}/companies`
**Responses**
- `200` — Paginated list under \`\_embedded.items\`
- `403` — Access denied
Response example, 200:
```json
{
"_class": [
"results",
"companies"
],
"total_items": 2,
"_embedded": {
"items": [
{
"id": 7,
"name": "BYD Chile",
"id_number": "76.086.428-5",
"activity_code": null,
"email_domain": "byd.cl"
}
]
}
}
```
---
# Request a password reset
Source: https://developers.onbolder.com/api/v2/customers/reset-customer-password
Sends a password-reset email to the customer's address.
Returns 200 even if the email is not found, to avoid user enumeration.
Use the 422 response only for missing `shop_id`.
`POST /customers/reset_password`
**Request body** (`application/json`, required)
- `shop_id` (integer, required)
- `email` (string, required)
Request body example:
```json
{
"shop_id": 1,
"email": "jane@example.com"
}
```
**Responses**
- `200` — Reset email sent (or silently ignored if not found)
- `422` — \`shop\_id\` required
Response example, 200:
```json
{
"message": "If that email is registered, a reset link has been sent."
}
```
---
# Start contact activation
Source: https://developers.onbolder.com/api/v2/customers/start-contact-activation
`POST /customers/{id}/activations`
**Responses**
- `201` — Activation started
---
# Start phone validation
Source: https://developers.onbolder.com/api/v2/customers/start-phone-validation
`POST /customers/{id}/phone_validations`
**Responses**
- `201` — Phone validation started
---
# Update a contact group
Source: https://developers.onbolder.com/api/v2/customers/update-contact-group
`PUT /contact_groups/{id}`
**cURL**
```bash
curl -s -X PUT \
-H "Authorization: Bearer $BOLDER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Premium Customers"}' \
"https://api.onbolder.com/v2/contact_groups/1"
```
**Request body** (`application/json`, required)
- `name` (string)
- `cart_requirements_list_id` (integer)
- `price_list_id` (integer)
- `allow_local_pickup` (boolean)
- `allow_shipping` (boolean)
Request body example:
```json
{
"name": "Premium Customers"
}
```
**Responses**
- `200` — Contact group updated
- `403` — Access denied
---
# Update a customer
Source: https://developers.onbolder.com/api/v2/customers/update-customer
`PUT /customers/{id}`
**Request body** (`application/json`, required)
- `email` (string, required) — Contact email address. Used as the unique identifier for find-or-create.
- `name` (string, required) — Full name (first and last).
- `phone_number` (string) — Required when creating a new contact.
- `company_name` (string) — Company name. Creates or finds a company record automatically.
- `company_id_number` (string) — Company tax/ID number (RUT, DNI, etc.), stored with the company record.
- `tax_id` (string) — The customer's own tax id (RUT, CUIT, NIT, etc.), any format; it is stored normalized (uppercase letters and digits). An empty string clears it. A Chilean one (\`tax\_id\_country\` CL) must have a valid check digit, else \`422\` on \`$.tax\_id\`.
- `tax_id_country` (string) — ISO 3166-1 alpha-2 country that issued the tax id. Defaults to the shop's country; \`422\` on \`$.tax\_id\_country\` if malformed.
- `tags` (string[]) — Tag names. Replaces all existing tags.
- `opt_in` (boolean) — Whether the contact accepts marketing emails/newsletters.
- `purchase_blocked` (boolean) — \`true\` blocks the customer from placing orders (moving them from draft/checkout to pending, in review, closed or completed; orders already placed can still be paid and fulfilled), \`false\` unblocks and clears reason, date and author. Admin tokens only: ignored on create and \`403\` on update for other tokens. The author is the token's user.
- `purchase_block_reason` (string) — Why the customer is blocked (staff only, never sent to webhooks). On an already blocked customer it only changes the reason.
- `contact_group_id` (integer) — Contact group ID to assign this contact to.
- `salesperson_id` (string) — Shop admin user ID
- `source` (string) — Origin of the contact record. Defaults to \`API\` if omitted on create.
- `custom_data` (object[]) — Custom field values for this contact.
Request body example:
```json
{
"name": "Janet Smith-Jones",
"phone_number": "+15559876543"
}
```
**Responses**
- `200` — Customer updated
Response example, 200:
```json
{
"id": 3001,
"email": "jane@example.com",
"name": "Janet Smith-Jones",
"phone_number": "+15559876543",
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/customers/3001"
}
}
}
```
---
# Update a company
Source: https://developers.onbolder.com/api/v2/customers/update-shop-company
Needs `customers.write`. Only the fields sent change; a blank `id_number` or `email_domain` clears it.
`PUT /shops/{shop_id}/companies/{id}`
**Request body** (`application/json`, required)
- `name` (string)
- `id_number` (string)
- `activity_code` (integer)
- `email_domain` (string)
Request body example:
```json
{
"name": "string",
"id_number": "string",
"activity_code": 0,
"email_domain": "string"
}
```
**Responses**
- `200` — The company
- `403` — Access denied
- `404` — Not a company of this shop's seller
- `422` — Invalid or duplicate values, or unknown fields
---
# Batch update orders
Source: https://developers.onbolder.com/api/v2/orders/batch-update-orders
Batch update multiple orders in a single request. `additions` accepts
order fields (e.g. `status`, `fulfillment_status`, `courier_name`,
`tracking_code`), plus `tags` to append.
`POST /orders/updaters`
**Request body** (`application/json`, required)
- `ids` (integer[], required) — Order IDs (or permalinks) to update
- `additions` (object) — Fields to set on every matching order (e.g. \`status\`, \`fulfillment\_status\`, \`courier\_name\`, \`tracking\_code\`), plus \`tags\` to append.
- `removals` (object)
- `replacements` (object)
- `decrement_stock` (boolean)
- `notify_status_change` (boolean)
- `notification_message` (string)
Request body example:
```json
{
"ids": [
5001,
5002
],
"additions": {
"status": "closed",
"tags": [
"batch-updated"
]
}
}
```
**Responses**
- `201` — Orders processed
- `422` — Validation error (e.g. missing \`ids\`, or neither \`additions\`/\`removals\`/\`replacements\` given)
Response example, 201:
```json
{
"ids": [
5001,
5002
],
"additions": {
"status": "closed"
},
"_embedded": {
"valid_items": [
{
"id": 5001,
"code": "ORD-5001",
"status": "closed"
}
],
"invalid_items": []
}
}
```
---
# Start (or resume) checkout for an order
Source: https://developers.onbolder.com/api/v2/orders/create-checkout-session
Starts, or returns the existing active, checkout session for an order. Idempotent while a session is still active — calling this repeatedly before expiry returns the same token.
`POST /orders/{order_id}/checkout_session`
**Request body** (`application/json`)
- `return_url` (string) — Where to redirect the buyer after a hosted-redirect-mode gateway round-trip. Its host must match the shop's own domain or one of its configured allowed embed origins — any other host is rejected with 422, to prevent open redirects. Only applied if the session doesn't already have a \`return\_url\` (first caller wins); ignored on later idempotent calls.
Request body example:
```json
{
"return_url": "https://mystore.com/order/confirmation"
}
```
**Responses**
- `200` — Checkout session
- `422` — Either this shop is not on the session-based checkout, or \`return\_url\` was given but its host isn't allowed for this shop
Response example, 200:
```json
{
"token": "string",
"status": "string",
"order_id": 0,
"return_url": "string",
"expires_at": "2019-08-24T14:15:22Z",
"last_viewed_at": "2019-08-24T14:15:22Z",
"view_count": 0,
"checkout_url": "string"
}
```
---
# Create an order
Source: https://developers.onbolder.com/api/v2/orders/create-order
Creates an order. **There is no `Idempotency-Key`-header-style replay
mechanism** — a retried request always executes again server-side, it
never returns a cached copy of the original response. If a create
request times out or the response is otherwise lost, pass an
`external_id` and check `GET /v2/orders?external_id={value}` before
retrying: the first successful create claims that `external_id`, and a
second create attempt with the same value is rejected with a 422
instead of producing a duplicate order. (The `id`/`code` field does
*not* give you this protection via `POST` — see below.)
Alternatively, `PUT /v2/orders/{id}` with `_upsert: true` and an
`external_id` in the body finds-or-creates by `external_id` directly
— it creates the order (with a system-generated `code`, not the URL
`:id`) if no order with that `external_id` exists yet, or updates it
in place if one does, so a retried request converges instead of
duplicating. See that operation's description for details.
A top-level body field the API doesn't know (for example `items` instead of
`line_items`) is rejected with `422` and a `$.` error ("is not a known
field") instead of being dropped silently. The same applies to `PUT /v2/orders/{id}`.
`POST /orders`
**cURL**
```bash
curl -s -X POST \
-H "Authorization: Bearer $BOLDER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"shop_id":1,"contact":{"name":"Jane Doe","email":"customer@example.com"},"line_items":[{"variant_id":101,"units":2}]}' \
"https://api.onbolder.com/v2/orders"
```
**Request body** (`application/json`, required)
- `shop_id` (integer, required) — Required — ID of the shop this order belongs to
- `id` (string) — Optional client-supplied order code (returned/queryable as \`code\`). \*\*Not a reliable idempotency key\*\*: if the code collides with an existing order, the collision is resolved silently by generating a new random code instead of rejecting the request — a retried create with the same \`id\` does not return the original order, it creates a new one with a different code. Use \`external\_id\` instead if you need safe dedup.
- `external_id` (string) — Optional client-supplied reference, unique per seller. A colliding value is rejected with a 422 (unlike \`id\`), so this is the field to use for retry-safe order creation: check \`GET /v2/orders?external\_id={value}\` before retrying a create that may have already succeeded.
- `purchase_order_number` (string) — The buyer's purchase order number.
- `salesperson_id` (integer) — The salesperson of the order: the ID of a user of the shop (\`422\` on \`$.salesperson\_id\` otherwise). Tokens limited to their own orders (\`orders.write\_own\`) always get themselves.
- `company_id` (integer) — The company (empresa, a customer company) the order is for: any company of the seller (\`422\` on \`$.company\_id\` otherwise). Returned as \`\_embedded.company\`. Tokens limited to their own orders can set it too.
- `from_quote` (boolean) — Makes the order a quote (quote flow: no stock, shipping or payment needed to place it; placing it moves it to \`in\_review\`, staff review it and it ends \`pending\`). Needs the shop checkout setting \`orders\_quote\_flow\`, else \`422\` on \`$.from\_quote\`.
- `allow_blocked` (boolean) — Admin tokens only. Lets an order of a customer blocked from purchasing move to pending/closed anyway. Without it such a transition answers \`422\` on \`$.contact\` ("Customer is blocked from purchasing"); ignored for other tokens.
- `source` (string)
- `contact` (object)
- `address` (Address)
- `line_items` (object[])
- `tags` (string[])
- `contact_data` (object[]) — Custom key/value fields of the order (e.g. \`RUT\`, \`sucursal\`), returned as \`custom\_data\` items. Keys are merged by \`key\`, so sending a key overwrites it and omitted keys are kept. Not searchable or filterable; to filter orders by a value use tags.
- `status` (string) — Order status to set on creation. Most values need \`\_policies.allow\_status: true\`, otherwise \`422\` on \`$.status\` ("Cannot change to X without allow\_status policy").
- `notify_status_change` (boolean) — See \`notify\_status\_change\` on the \`Order\` schema.
- `notification_message` (string)
- `courier_name` (string)
- `tracking_code` (string)
- `fulfillment_method` (string)
- `_policies` (object) — Flags controlling order processing behavior
Request body example:
```json
{
"shop_id": 1,
"contact": {
"name": "Jane Doe",
"email": "customer@example.com"
},
"line_items": [
{
"variant_id": "101",
"units": 2
},
{
"variant_id": "205",
"units": 1
}
]
}
```
**Responses**
- `201` — Order created
- `403` — Access denied to the given shop
- `422` — Validation error
Response example, 422:
```json
{
"id": 5002,
"shop_id": 1,
"code": "ORD-5002",
"status": "checkout",
"currency_code": "USD",
"total": 17997,
"created_on": "2024-06-20T14:30:00Z",
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/orders/ORD-5002"
},
"orders:update": {
"href": "https://api.onbolder.com/v2/orders/ORD-5002",
"method": "put"
}
}
}
```
---
# Create an order document
Source: https://developers.onbolder.com/api/v2/orders/create-order-document
Records a document (invoice, receipt, purchase order...) against an order.
Send JSON (metadata only) or `multipart/form-data` with a `file` (PDF, JPEG, PNG,
GIF or WebP, max 10 MB; `422` on `$.file` otherwise). The file is not exposed by
URL: the document gets an `orders:download_document` link. `uploader_id` and
`uploader_type` default to the token's user (`User`). The order must be `pending`,
`closed` or `completed` (`422` on `$.order` otherwise). Unknown fields are rejected
with `422`, one error per field (`{"field": "$.foo", "messages": ["is not a known field"]}`).
Attaching a document of type `purchase_order` emails the shop's staff: it is the
`order_purchase_order_notification` type of the notification profiles, so each profile
decides who receives it (and, with a cart requirements list, for which orders).
`POST /orders/{order_id}/documents`
**Request body** (`application/json`, required)
- `document_type` (string, required)
- `document_number` (string, required) — Unique per order and \`document\_type\`.
- `transaction_id` (string) — Associates the document with a specific payment/refund transaction
- `uploader_id` (string) — Defaults to the token's user.
- `uploader_type` (string) — Defaults to \`User\` (the token's user).
Request body example:
```json
{
"document_type": "invoice",
"document_number": "INV-0001"
}
```
**Responses**
- `201` — Document created
- `422` — Invalid file, unknown field, or order status that cannot have documents.
Response example, 201:
```json
{
"id": 0,
"document_type": "receipt",
"document_number": "string",
"file_url": "string",
"_links": {}
}
```
---
# Add a line item to an order
Source: https://developers.onbolder.com/api/v2/orders/create-order-item
Adds a single item to the order. Accepts the same fields as one element
of the `line_items` array on order create/update. If the variant is
already in the order, units are updated instead (upsert by variant).
`POST /orders/{order_id}/items`
**Request body** (`application/json`, required)
- `variant_id` (string)
- `variant_sku` (string) — Alternative to variant\_id, for linked items
- `units` (integer)
- `price` (integer) — Overrides the product's price, in cents
- `product_name` (string) — Required for detached items (no product record)
Request body example, Linked item (existing product):
```json
{
"variant_id": "123",
"units": 2
}
```
Request body example, Detached item (no product record):
```json
{
"product_name": "Custom Engraving",
"variant_sku": "ENG-01",
"price": 1500,
"units": 1
}
```
**Responses**
- `200` — Updated order (with the new line item)
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"shop_id": 0,
"code": "string",
"external_id": "string",
"tracking_token": "string",
"purchase_order_number": "string",
"status": "draft",
"fulfillment_status": "none",
"checkout_url": "string",
"archived": true,
"currency_code": "string",
"prices_include_tax": true,
"items_net_total": 0,
"items_discount_total": 0,
"items_total": 0,
"net_total": 0,
"discount_total": 0,
"cart_total": 0,
"surcharge_total": 0,
"shipping_total": 0,
"shipping_discount": 0,
"shipping_minus_discount": 0,
"tax_rate": 0,
"tax_total": 0,
"total": 0,
"total_paid": 0,
"total_refunded": 0,
"max_refundable_amount": 0,
"partial_payment_offered": true,
"paying_partially": true,
"partial_payment_percentage": 0,
"partial_payment_amount": 0,
"partial_payment_due_on": "2019-08-24",
"shipping_description": "string",
"tracking_code": "string",
"courier_name": "string",
"payment_info": {
"id": 0,
"name": "string",
"type": "string",
"data": {}
},
"requested_document_type": "string",
"tags": [
"string"
],
"tag_ids": [
0
],
"allowed_statuses": [
"string"
],
"allowed_fulfillment_statuses": [
"string"
],
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"checkout_on": "2019-08-24T14:15:22Z",
"pending_on": "2019-08-24T14:15:22Z",
"closed_on": "2019-08-24T14:15:22Z",
"shipped_on": "2019-08-24T14:15:22Z",
"_embedded": {
"credit": {
"limit_amount": 0,
"balance": 0,
"loan_term": "string",
"overdue_limit_amount": 0,
"state": "string"
},
"contact_group": {
"id": 0,
"name": "string"
},
"price_list": {
"id": 0,
"name": "string",
"tax_name": "string",
"tax_rate": 0,
"prices_include_tax": true
},
"cart_requirements": {
"id": 0,
"name": "string",
"entries": [
{
"key": "string",
"value": 0,
"optional_id": 0
}
],
"applies_to": {
"shipping_options": [
"string"
],
"branches": [
"string"
],
"payment_methods": [
"string"
]
}
},
"contact": {
"id": 0,
"name": "string",
"email": "string",
"phone_number": "string",
"tax_id": "string",
"tax_id_country": "string"
},
"company": {
"id": 0,
"name": "string",
"id_number": "string",
"activity_code": "string",
"address": {
"street": "string",
"street_2": "string",
"locality_name": "string",
"region_name": "string",
"country_name": "string",
"postal_code": "string"
}
},
"address": {
"street": "string",
"street_2": "string",
"locality_name": "string",
"region_name": "string",
"country_name": "string",
"postal_code": "string"
},
"billing_address": {
"street": "string",
"street_2": "string",
"locality_name": "string",
"region_name": "string",
"country_name": "string",
"postal_code": "string"
},
"line_items": [
{
"id": 0,
"product_id": 0,
"product_name": "string",
"variant_id": 0,
"variant_name": "string",
"variant_sku": "string",
"units": 0,
"unit_price": 0,
"net_total": 0,
"total": 0
}
],
"payments": [
{
"id": 0,
"order_id": 0,
"amount": 0,
"absolute_amount": 0,
"successful": true,
"card_token_id": 0,
"payment_method_type": "string",
"transaction_id": "string",
"authorization_code": "string",
"description": "string",
"created_at": "2019-08-24T14:15:22Z"
}
],
"refunds": [
{
"id": 0,
"order_id": 0,
"amount": 0,
"absolute_amount": 0,
"successful": true,
"card_token_id": 0,
"payment_method_type": "string",
"transaction_id": "string",
"authorization_code": "string",
"description": "string",
"created_at": "2019-08-24T14:15:22Z"
}
],
"documents": [
{
"id": 0,
"document_type": "receipt",
"document_number": "string",
"file_url": "string",
"_links": {}
}
]
}
}
```
---
# Send an order message or note
Source: https://developers.onbolder.com/api/v2/orders/create-order-message
Adds a message to the order's conversation, authored by the token's user (`422` on `$.base`
without one). External messages (default) are visible to the customer; `internal: true` adds a
staff note. `notify: true` (external only, `422` on `$.notify` otherwise) also emails the message
to the customer after it is saved. The order must have a contact (`422` on `$.order`). Unknown
fields answer `422`. Needs the full `orders.write` scope; the `_own` scopes get `403`.
`POST /orders/{order_id}/messages`
**Request body** (`application/json`, required)
- `body` (string, required)
- `internal` (boolean)
- `notify` (boolean)
Request body example:
```json
{
"body": "Tu visita quedo agendada para el martes.",
"notify": true
}
```
**Responses**
- `201` — Message created
- `422` — Invalid body, unknown field, \`notify\` on a note, no user on the token, or order without contact.
Response example, 201:
```json
{
"id": 0,
"body": "string",
"internal": true,
"sender": "admin",
"author": {
"id": 0,
"name": "string"
},
"created_on": "2019-08-24T14:15:22Z"
}
```
---
# Record a manual payment on an order
Source: https://developers.onbolder.com/api/v2/orders/create-order-payment
Records a manual payment (cash, bank transfer, external processor, etc.) against an order. Does **not** go through any payment gateway — the payment is recorded as-is and the order's paid amount is updated immediately.
`POST /orders/{order_id}/payments`
**Request body** (`application/json`, required)
- `amount` (integer, required) — Payment amount in cents
- `authorization_code` (string) — Reference / receipt number
- `description` (string)
- `transaction_id` (string) — External transaction identifier
- `payment_method_id` (integer) — ID of one of the seller's payment methods. Required when the order has none (orders created through the API have none): otherwise \`422\` on \`payment\_method\_id\`.
- `close_order` (boolean) — Close the order when this payment covers it, so membership items create their purchase. Not covered yet: nothing happens. If it cannot be closed (no customer, an item without a fee, an active purchase of the plan already exists) the answer is 422 on \`close\_order\` and the payment is not recorded.
Request body example:
```json
{
"amount": 5000,
"description": "Wire transfer ref. XYZ"
}
```
**Responses**
- `201` — Payment recorded
Response example, 201:
```json
{
"id": 0,
"order_id": 0,
"amount": 0,
"absolute_amount": 0,
"successful": true,
"card_token_id": 0,
"payment_method_type": "string",
"transaction_id": "string",
"authorization_code": "string",
"description": "string",
"created_at": "2019-08-24T14:15:22Z"
}
```
---
# Delete an order document
Source: https://developers.onbolder.com/api/v2/orders/delete-order-document
`DELETE /orders/{order_id}/documents/{id}`
**Responses**
- `204` — Document deleted
---
# Remove a line item from an order
Source: https://developers.onbolder.com/api/v2/orders/delete-order-item
Returns the updated order (200), not 204, so the caller sees the new totals immediately.
`DELETE /orders/{order_id}/items/{id}`
**Responses**
- `200` — Updated order
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"shop_id": 0,
"code": "string",
"external_id": "string",
"tracking_token": "string",
"purchase_order_number": "string",
"status": "draft",
"fulfillment_status": "none",
"checkout_url": "string",
"archived": true,
"currency_code": "string",
"prices_include_tax": true,
"items_net_total": 0,
"items_discount_total": 0,
"items_total": 0,
"net_total": 0,
"discount_total": 0,
"cart_total": 0,
"surcharge_total": 0,
"shipping_total": 0,
"shipping_discount": 0,
"shipping_minus_discount": 0,
"tax_rate": 0,
"tax_total": 0,
"total": 0,
"total_paid": 0,
"total_refunded": 0,
"max_refundable_amount": 0,
"partial_payment_offered": true,
"paying_partially": true,
"partial_payment_percentage": 0,
"partial_payment_amount": 0,
"partial_payment_due_on": "2019-08-24",
"shipping_description": "string",
"tracking_code": "string",
"courier_name": "string",
"payment_info": {
"id": 0,
"name": "string",
"type": "string",
"data": {}
},
"requested_document_type": "string",
"tags": [
"string"
],
"tag_ids": [
0
],
"allowed_statuses": [
"string"
],
"allowed_fulfillment_statuses": [
"string"
],
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"checkout_on": "2019-08-24T14:15:22Z",
"pending_on": "2019-08-24T14:15:22Z",
"closed_on": "2019-08-24T14:15:22Z",
"shipped_on": "2019-08-24T14:15:22Z",
"_embedded": {
"credit": {
"limit_amount": 0,
"balance": 0,
"loan_term": "string",
"overdue_limit_amount": 0,
"state": "string"
},
"contact_group": {
"id": 0,
"name": "string"
},
"price_list": {
"id": 0,
"name": "string",
"tax_name": "string",
"tax_rate": 0,
"prices_include_tax": true
},
"cart_requirements": {
"id": 0,
"name": "string",
"entries": [
{
"key": "string",
"value": 0,
"optional_id": 0
}
],
"applies_to": {
"shipping_options": [
"string"
],
"branches": [
"string"
],
"payment_methods": [
"string"
]
}
},
"contact": {
"id": 0,
"name": "string",
"email": "string",
"phone_number": "string",
"tax_id": "string",
"tax_id_country": "string"
},
"company": {
"id": 0,
"name": "string",
"id_number": "string",
"activity_code": "string",
"address": {
"street": "string",
"street_2": "string",
"locality_name": "string",
"region_name": "string",
"country_name": "string",
"postal_code": "string"
}
},
"address": {
"street": "string",
"street_2": "string",
"locality_name": "string",
"region_name": "string",
"country_name": "string",
"postal_code": "string"
},
"billing_address": {
"street": "string",
"street_2": "string",
"locality_name": "string",
"region_name": "string",
"country_name": "string",
"postal_code": "string"
},
"line_items": [
{
"id": 0,
"product_id": 0,
"product_name": "string",
"variant_id": 0,
"variant_name": "string",
"variant_sku": "string",
"units": 0,
"unit_price": 0,
"net_total": 0,
"total": 0
}
],
"payments": [
{
"id": 0,
"order_id": 0,
"amount": 0,
"absolute_amount": 0,
"successful": true,
"card_token_id": 0,
"payment_method_type": "string",
"transaction_id": "string",
"authorization_code": "string",
"description": "string",
"created_at": "2019-08-24T14:15:22Z"
}
],
"refunds": [
{
"id": 0,
"order_id": 0,
"amount": 0,
"absolute_amount": 0,
"successful": true,
"card_token_id": 0,
"payment_method_type": "string",
"transaction_id": "string",
"authorization_code": "string",
"description": "string",
"created_at": "2019-08-24T14:15:22Z"
}
],
"documents": [
{
"id": 0,
"document_type": "receipt",
"document_number": "string",
"file_url": "string",
"_links": {}
}
]
}
}
```
---
# Download an order document file
Source: https://developers.onbolder.com/api/v2/orders/download-order-document-file
Streams the uploaded file with `Content-Disposition: attachment`. Requires the same permission as reading the order's documents. `404` if the document has no uploaded file (legacy documents expose `file_url` instead).
`GET /orders/{order_id}/documents/{id}/file`
**Responses**
- `200` — The file
- `403` — Not enough permissions
- `404` — Document or file not found
Response example, 200:
```json
"string"
```
---
# Get a checkout session
Source: https://developers.onbolder.com/api/v2/orders/get-checkout-session
Resolves a checkout session by its token. Does not auto-rotate an expired session the way the hosted checkout page does — an expired session returns 410 with a `replacement` session in the body.
`GET /checkout_sessions/{id}`
**Responses**
- `200` — Checkout session
- `410` — Session expired
Response example, 200:
```json
{
"token": "string",
"status": "string",
"order_id": 0,
"return_url": "string",
"expires_at": "2019-08-24T14:15:22Z",
"last_viewed_at": "2019-08-24T14:15:22Z",
"view_count": 0,
"checkout_url": "string"
}
```
---
# Get an order
Source: https://developers.onbolder.com/api/v2/orders/get-order
Direct access to an order by ID or permalink. No `shop_id` needed in the URL —
authorization is verified via the token's account ownership.
Show-only additions: `cost_partial: true` when a non-bundle line item has no cost
(so `cost_total` understates the cost; detached items never have one), and
`_embedded.subscription_purchases` (`id`, `state`, `kind` `membership_plan`|`product`,
`plan_name`, `payment_interval`, `next_payment_on`) for the purchases this order
generated, only when there are any. Every line item has a boolean `detached`
(created without a product; it has no `product` link).
`GET /orders/{id}`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/orders/ORD-5001"
```
**Responses**
- `200` — Order entity
- `403` — Order belongs to a different account
- `404` — Order not found
Response example, 200:
```json
{
"id": 5001,
"shop_id": 1,
"code": "ORD-5001",
"status": "pending",
"fulfillment_status": "none",
"currency_code": "USD",
"total": 8499,
"total_paid": 8499,
"created_on": "2024-06-15T09:45:00Z",
"updated_on": "2024-06-15T09:45:00Z",
"_embedded": {
"contact": {
"id": 42,
"name": "Jane Doe",
"email": "customer@example.com",
"phone_number": "+15551234567"
},
"line_items": [
{
"id": 9001,
"product_id": 101,
"product_name": "T-Shirt",
"variant_id": 201,
"variant_name": "Medium / Blue",
"units": 2,
"unit_price": 2500,
"net_total": 5000,
"total": 5000
}
]
},
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/orders/ORD-5001"
},
"orders:update": {
"href": "https://api.onbolder.com/v2/orders/ORD-5001",
"method": "put"
},
"shops:show": {
"href": "https://api.onbolder.com/v2/shops/1"
}
}
}
```
---
# Get an order document
Source: https://developers.onbolder.com/api/v2/orders/get-order-document
`GET /orders/{order_id}/documents/{id}`
**Responses**
- `200` — Document
Response example, 200:
```json
{
"id": 0,
"document_type": "receipt",
"document_number": "string",
"file_url": "string",
"_links": {}
}
```
---
# Get the customer-facing progress of an order
Source: https://developers.onbolder.com/api/v2/orders/get-order-progress
The order's state, its history and the last comment for the customer. The state is the order tag
that starts with the shop setting `website.track_state_tag_prefix` (e.g. prefix `estado:`, tag
`estado:Visita agendada`, state "Visita agendada"). Without that setting `state` is `null` and
`timeline` empty. `comment` is the latest external admin message (never an internal note).
Needs the full `orders.read` scope; the `_own` scopes get `403`.
`GET /orders/{order_id}/progress`
**Responses**
- `200` — Order progress
Response example, 200:
```json
{
"state": {
"label": "Visita agendada",
"since": "2019-08-24T14:15:22Z"
},
"comment": {
"body": "string",
"created_on": "2019-08-24T14:15:22Z"
},
"timeline": [
{
"state": "string",
"at": "2019-08-24T14:15:22Z"
}
]
}
```
---
# List order documents
Source: https://developers.onbolder.com/api/v2/orders/list-order-documents
`GET /orders/{order_id}/documents`
**Responses**
- `200` — Documents list
Response example, 200:
```json
{
"_embedded": {
"items": [
{
"id": 0,
"document_type": "receipt",
"document_number": "string",
"file_url": "string",
"_links": {}
}
]
}
}
```
---
# List order messages
Source: https://developers.onbolder.com/api/v2/orders/list-order-messages
The order's conversation with the customer, oldest first: external messages (visible to the
customer) and internal staff notes. Needs the full `orders.read` scope; the `_own` scopes get `403`.
`GET /orders/{order_id}/messages`
**Responses**
- `200` — Messages
Response example, 200:
```json
{
"_embedded": {
"items": [
{
"id": 0,
"body": "string",
"internal": true,
"sender": "admin",
"author": {
"id": 0,
"name": "string"
},
"created_on": "2019-08-24T14:15:22Z"
}
]
}
}
```
---
# List order tags
Source: https://developers.onbolder.com/api/v2/orders/list-order-tags-flattened
Returns order tags across the account.
Optionally filter by `shop_id` or `seller_id` query parameter.
`GET /order_tags`
**Responses**
- `200` — Order tags list
---
# List orders
Source: https://developers.onbolder.com/api/v2/orders/list-orders
Returns orders for a shop, or for a whole seller across all of its shops.
`shop_id` and `seller_id` are both optional. If the token covers exactly
one shop, that shop is auto-resolved and results are scoped to it. If the
token covers multiple shops belonging to a single seller, omitting both
params returns orders across all of that seller's shops. A token covering
multiple sellers (or a god token) must pass `shop_id` or `seller_id`
explicitly — there is no "all sellers" search.
`GET /orders`
**cURL**
```bash
# List all orders for a shop
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/orders?shop_id=1"
# List orders across all of a seller's shops
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/orders?seller_id=1"
# Filter by status
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/orders?shop_id=1&status=open"
```
**Responses**
- `200` — Paginated list of orders
- `403` — Access denied to the given shop or seller
- `422` — \`shop\_id\` or \`seller\_id\` required — the token covers more than one shop and more than one seller (or is a god token), so neither can be auto-resolved.
Response example, 200:
```json
{
"_class": [
"results",
"orders"
],
"total_items": 1,
"per_page": 20,
"page": 1,
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/orders?shop_id=1"
},
"shops:show": {
"href": "https://api.onbolder.com/v2/shops/1"
}
},
"_embedded": {
"items": [
{
"id": 5001,
"shop_id": 1,
"code": "ORD-5001",
"status": "pending",
"fulfillment_status": "none",
"currency_code": "USD",
"total": 8499,
"total_paid": 8499,
"created_on": "2024-06-15T09:45:00Z",
"_embedded": {
"contact": {
"id": 42,
"name": "Jane Doe",
"email": "customer@example.com",
"phone_number": "+15551234567"
}
},
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/orders/ORD-5001"
},
"orders:update": {
"href": "https://api.onbolder.com/v2/orders/ORD-5001",
"method": "put"
}
}
}
]
}
}
```
---
# Orders KPI strip
Source: https://developers.onbolder.com/api/v2/orders/orders-stats
Returns `{ period, currency_code, orders_today, revenue, awaiting_fulfillment, cancelled }`. With `interval` or `series=true` also `series` and `previous_series` (the preceding period): `[{ date, orders, revenue }]`, zero-filled, oldest first, days in the seller's timezone (weeks end on today). Revenue (cents) and orders are the closed/completed orders by close date.
`GET /orders/stats`
**Responses**
- `200` — KPI numbers
---
# Refund an order
Source: https://developers.onbolder.com/api/v2/orders/refund-order
`POST /orders/{id}/refund`
**Request body** (`application/json`, required)
- `amount` (integer) — Refund amount in cents
- `description` (string)
- `reason` (string)
- `authorization_code` (string) — Reference code for manual refunds
- `manual` (boolean) — Skip the payment gateway and record the refund directly
Request body example:
```json
{
"amount": 2500,
"reason": "customer_request"
}
```
**Responses**
- `200` — Refund processed
- `422` — Order is not refundable, or amount exceeds max\_refundable\_amount
Response example, 200:
```json
{
"id": 0,
"order_id": 0,
"amount": 0,
"absolute_amount": 0,
"successful": true,
"card_token_id": 0,
"payment_method_type": "string",
"transaction_id": "string",
"authorization_code": "string",
"description": "string",
"created_at": "2019-08-24T14:15:22Z"
}
```
---
# Split an order
Source: https://developers.onbolder.com/api/v2/orders/split-order
Splits a subset of line items (in full or partial quantity) off the original order
into a new, separate order. The new order carries only the specified items and can
be transitioned through the fulfillment lifecycle independently of the remainder.
Typical use case: a partial return where some items are sent back while others are
kept, or fulfilling items from different warehouses on separate timelines.
`POST /orders/{id}/split`
**Request body** (`application/json`, required)
- `items` (object[], required) — Line items to move into the new order
Request body example:
```json
{
"items": [
{
"order_item_id": 12345,
"quantity": 1
},
{
"order_item_id": 12346,
"quantity": 2
}
]
}
```
**Responses**
- `201` — New order created from the split items
- `422` — Invalid split (e.g. quantity exceeds available units, no items given, or item not found on the order)
Response example, 201:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"shop_id": 0,
"code": "string",
"external_id": "string",
"tracking_token": "string",
"purchase_order_number": "string",
"status": "draft",
"fulfillment_status": "none",
"checkout_url": "string",
"archived": true,
"currency_code": "string",
"prices_include_tax": true,
"items_net_total": 0,
"items_discount_total": 0,
"items_total": 0,
"net_total": 0,
"discount_total": 0,
"cart_total": 0,
"surcharge_total": 0,
"shipping_total": 0,
"shipping_discount": 0,
"shipping_minus_discount": 0,
"tax_rate": 0,
"tax_total": 0,
"total": 0,
"total_paid": 0,
"total_refunded": 0,
"max_refundable_amount": 0,
"partial_payment_offered": true,
"paying_partially": true,
"partial_payment_percentage": 0,
"partial_payment_amount": 0,
"partial_payment_due_on": "2019-08-24",
"shipping_description": "string",
"tracking_code": "string",
"courier_name": "string",
"payment_info": {
"id": 0,
"name": "string",
"type": "string",
"data": {}
},
"requested_document_type": "string",
"tags": [
"string"
],
"tag_ids": [
0
],
"allowed_statuses": [
"string"
],
"allowed_fulfillment_statuses": [
"string"
],
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"checkout_on": "2019-08-24T14:15:22Z",
"pending_on": "2019-08-24T14:15:22Z",
"closed_on": "2019-08-24T14:15:22Z",
"shipped_on": "2019-08-24T14:15:22Z",
"_embedded": {
"credit": {
"limit_amount": 0,
"balance": 0,
"loan_term": "string",
"overdue_limit_amount": 0,
"state": "string"
},
"contact_group": {
"id": 0,
"name": "string"
},
"price_list": {
"id": 0,
"name": "string",
"tax_name": "string",
"tax_rate": 0,
"prices_include_tax": true
},
"cart_requirements": {
"id": 0,
"name": "string",
"entries": [
{
"key": "string",
"value": 0,
"optional_id": 0
}
],
"applies_to": {
"shipping_options": [
"string"
],
"branches": [
"string"
],
"payment_methods": [
"string"
]
}
},
"contact": {
"id": 0,
"name": "string",
"email": "string",
"phone_number": "string",
"tax_id": "string",
"tax_id_country": "string"
},
"company": {
"id": 0,
"name": "string",
"id_number": "string",
"activity_code": "string",
"address": {
"street": "string",
"street_2": "string",
"locality_name": "string",
"region_name": "string",
"country_name": "string",
"postal_code": "string"
}
},
"address": {
"street": "string",
"street_2": "string",
"locality_name": "string",
"region_name": "string",
"country_name": "string",
"postal_code": "string"
},
"billing_address": {
"street": "string",
"street_2": "string",
"locality_name": "string",
"region_name": "string",
"country_name": "string",
"postal_code": "string"
},
"line_items": [
{
"id": 0,
"product_id": 0,
"product_name": "string",
"variant_id": 0,
"variant_name": "string",
"variant_sku": "string",
"units": 0,
"unit_price": 0,
"net_total": 0,
"total": 0
}
],
"payments": [
{
"id": 0,
"order_id": 0,
"amount": 0,
"absolute_amount": 0,
"successful": true,
"card_token_id": 0,
"payment_method_type": "string",
"transaction_id": "string",
"authorization_code": "string",
"description": "string",
"created_at": "2019-08-24T14:15:22Z"
}
],
"refunds": [
{
"id": 0,
"order_id": 0,
"amount": 0,
"absolute_amount": 0,
"successful": true,
"card_token_id": 0,
"payment_method_type": "string",
"transaction_id": "string",
"authorization_code": "string",
"description": "string",
"created_at": "2019-08-24T14:15:22Z"
}
],
"documents": [
{
"id": 0,
"document_type": "receipt",
"document_number": "string",
"file_url": "string",
"_links": {}
}
]
}
}
```
---
# Update an order
Source: https://developers.onbolder.com/api/v2/orders/update-order
Setting `status` to most values requires `_policies.allow_status: true`
in the request body (otherwise `422` on `$.status`). With it, the API does not
enforce the transitions listed in the `transitions` map of `GET /v2/orders`: an
order can be moved to any status, and `fulfillment_status` set to any value, in
any order.
### Recommended pattern for retry-safe order creation
`POST /v2/orders` has no idempotency mechanism (see its description).
The recommended way to create an order safely under retry is
**`PUT /v2/orders/{id}` with `_upsert: true` and an `external_id`**
in the body:
- If no order exists yet with that `external_id` (scoped to the
seller resolved via `shop_id`), one is created (response status
`201`) using the same request body fields as `POST /v2/orders`
(`shop_id`, `contact`, `line_items`, etc). The order gets a normal
system-generated `code` — the URL `:id` is **not** used as, or
turned into, the order's code; a client cannot pick the public
order code this way. `:id` can be any placeholder (e.g. the same
`external_id` value, for readability), since matching is done by
`external_id`, not by the URL.
- If an order already exists with that `external_id`, it's updated
in place (response status `200`) — a retried request converges on
the same order instead of creating a duplicate.
- Without `_upsert: true`, `PUT` to a nonexistent `:id` returns `404`
as usual.
- `_upsert: true` without an `external_id` in the body returns `422`
— the flag requires `external_id` so a typo in the URL `:id` alone
can't silently create an unintended order.
This is the same `external_id` field documented on `Order` and on
`POST /v2/orders` — a collision there is rejected with `422`;
here, under `_upsert`, a "collision" is the intended match and
updates the existing order instead.
### Stock restoration on cancellation
Setting `status` to `cancelled` automatically restores/releases reserved
stock, but only when both of these hold:
- the order's `fulfillment_status` is still unsent (`queued`,
`preparing`, or `ready` — not yet `shipped`/`delivered`), and
- stock was actually reserved for the order in the first place (line
items on non-stock-tracked products, or without a `variant_id` at all,
never reserved anything, so there's nothing to restore for them).
Cancelling an order that has already shipped does **not** auto-restore
stock. Restoration is independent of payment state — it isn't affected by
whether the order was paid or refunded.
`PUT /orders/{id}`
**cURL**
```bash
curl -s -X PUT \
-H "Authorization: Bearer $BOLDER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"tracking_code":"1Z9999W99999999999","courier_name":"UPS"}' \
"https://api.onbolder.com/v2/orders/ORD-5001"
```
**Request body** (`application/json`, required)
- `_upsert` (boolean) — If \`true\`, requires \`external\_id\` in this same request body. Finds an order by \`external\_id\` (scoped to the seller); if none exists, creates one instead of returning 404 (with a system-generated \`code\`, not the URL \`:id\`). See "Recommended pattern for retry-safe order creation" above.
- `status` (string)
- `fulfillment_status` (string)
- `archived` (boolean)
- `external_id` (string) — Optional client-supplied reference, unique per seller. Can be set here if it wasn't given at create time; a colliding value is rejected with a 422.
- `purchase_order_number` (string) — The buyer's purchase order number (blank clears it). Also settable on pending orders.
- `company_id` (integer) — The company (empresa) of the order, any company of the seller (\`422\` on \`$.company\_id\` otherwise); blank clears it. Settable at any status.
- `from_quote` (boolean) — Turn the quote flow on or off. Only while the order is \`draft\`/\`checkout\`; later a different value answers \`422\` on \`$.from\_quote\`. Needs the shop checkout setting \`orders\_quote\_flow\` to turn it on.
- `allow_blocked` (boolean) — Admin tokens only. Lets an order of a customer blocked from purchasing move to pending/closed anyway; otherwise such a status change answers \`422\` on \`$.contact\`.
- `tracking_code` (string)
- `courier_name` (string)
- `fulfillment_method` (string)
- `shipping_amount` (integer)
- `shipping_discount` (integer)
- `shipping_description` (string)
- `tags` (string[])
- `notify_status_change` (boolean)
- `notification_message` (string)
- `_policies` (object)
Request body example:
```json
{
"tracking_code": "1Z9999W99999999999",
"courier_name": "UPS",
"fulfillment_status": "shipped"
}
```
**Responses**
- `200` — Order updated
Response example, 200:
```json
{
"id": 5001,
"code": "ORD-5001",
"status": "pending",
"fulfillment_status": "shipped",
"tracking_code": "1Z9999W99999999999",
"courier_name": "UPS",
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/orders/ORD-5001"
}
}
}
```
---
# Update an order line item
Source: https://developers.onbolder.com/api/v2/orders/update-order-item
Updates units, price and/or (detached items) weight on an existing line item. Order totals are recalculated.
`PUT /orders/{order_id}/items/{id}`
**Request body** (`application/json`, required)
- `units` (integer)
- `price` (integer) — Force a new price, in cents
- `weight_in_gr` (integer) — Unit weight in grams. Detached items only (422 otherwise).
Request body example:
```json
{
"units": 3
}
```
**Responses**
- `200` — Updated order
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"shop_id": 0,
"code": "string",
"external_id": "string",
"tracking_token": "string",
"purchase_order_number": "string",
"status": "draft",
"fulfillment_status": "none",
"checkout_url": "string",
"archived": true,
"currency_code": "string",
"prices_include_tax": true,
"items_net_total": 0,
"items_discount_total": 0,
"items_total": 0,
"net_total": 0,
"discount_total": 0,
"cart_total": 0,
"surcharge_total": 0,
"shipping_total": 0,
"shipping_discount": 0,
"shipping_minus_discount": 0,
"tax_rate": 0,
"tax_total": 0,
"total": 0,
"total_paid": 0,
"total_refunded": 0,
"max_refundable_amount": 0,
"partial_payment_offered": true,
"paying_partially": true,
"partial_payment_percentage": 0,
"partial_payment_amount": 0,
"partial_payment_due_on": "2019-08-24",
"shipping_description": "string",
"tracking_code": "string",
"courier_name": "string",
"payment_info": {
"id": 0,
"name": "string",
"type": "string",
"data": {}
},
"requested_document_type": "string",
"tags": [
"string"
],
"tag_ids": [
0
],
"allowed_statuses": [
"string"
],
"allowed_fulfillment_statuses": [
"string"
],
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"checkout_on": "2019-08-24T14:15:22Z",
"pending_on": "2019-08-24T14:15:22Z",
"closed_on": "2019-08-24T14:15:22Z",
"shipped_on": "2019-08-24T14:15:22Z",
"_embedded": {
"credit": {
"limit_amount": 0,
"balance": 0,
"loan_term": "string",
"overdue_limit_amount": 0,
"state": "string"
},
"contact_group": {
"id": 0,
"name": "string"
},
"price_list": {
"id": 0,
"name": "string",
"tax_name": "string",
"tax_rate": 0,
"prices_include_tax": true
},
"cart_requirements": {
"id": 0,
"name": "string",
"entries": [
{
"key": "string",
"value": 0,
"optional_id": 0
}
],
"applies_to": {
"shipping_options": [
"string"
],
"branches": [
"string"
],
"payment_methods": [
"string"
]
}
},
"contact": {
"id": 0,
"name": "string",
"email": "string",
"phone_number": "string",
"tax_id": "string",
"tax_id_country": "string"
},
"company": {
"id": 0,
"name": "string",
"id_number": "string",
"activity_code": "string",
"address": {
"street": "string",
"street_2": "string",
"locality_name": "string",
"region_name": "string",
"country_name": "string",
"postal_code": "string"
}
},
"address": {
"street": "string",
"street_2": "string",
"locality_name": "string",
"region_name": "string",
"country_name": "string",
"postal_code": "string"
},
"billing_address": {
"street": "string",
"street_2": "string",
"locality_name": "string",
"region_name": "string",
"country_name": "string",
"postal_code": "string"
},
"line_items": [
{
"id": 0,
"product_id": 0,
"product_name": "string",
"variant_id": 0,
"variant_name": "string",
"variant_sku": "string",
"units": 0,
"unit_price": 0,
"net_total": 0,
"total": 0
}
],
"payments": [
{
"id": 0,
"order_id": 0,
"amount": 0,
"absolute_amount": 0,
"successful": true,
"card_token_id": 0,
"payment_method_type": "string",
"transaction_id": "string",
"authorization_code": "string",
"description": "string",
"created_at": "2019-08-24T14:15:22Z"
}
],
"refunds": [
{
"id": 0,
"order_id": 0,
"amount": 0,
"absolute_amount": 0,
"successful": true,
"card_token_id": 0,
"payment_method_type": "string",
"transaction_id": "string",
"authorization_code": "string",
"description": "string",
"created_at": "2019-08-24T14:15:22Z"
}
],
"documents": [
{
"id": 0,
"document_type": "receipt",
"document_number": "string",
"file_url": "string",
"_links": {}
}
]
}
}
```
---
# Mark a checkout session as viewed
Source: https://developers.onbolder.com/api/v2/orders/view-checkout-session
Records `last_viewed_at`/`view_count`. Not a "checkout complete" signal — order status/payment state is never driven by this.
`POST /checkout_sessions/{id}/view`
**Responses**
- `200` — Checkout session
Response example, 200:
```json
{
"token": "string",
"status": "string",
"order_id": 0,
"return_url": "string",
"expires_at": "2019-08-24T14:15:22Z",
"last_viewed_at": "2019-08-24T14:15:22Z",
"view_count": 0,
"checkout_url": "string"
}
```
---
# Create a price list
Source: https://developers.onbolder.com/api/v2/price-lists/create-price-list
`POST /price_lists`
**Request body** (`application/json`, required)
- `seller_id` (integer, required) — The seller this price list belongs to.
- `name` (string, required) — Human-readable label for this price list.
- `prices_include_tax` (boolean) — Whether prices in this list already include tax.
- `use_price_comparison_for_vol_discounts` (boolean) — Use price comparison values when computing volume discounts.
- `contact_group_ids` (integer[]) — Contact groups this price list applies to. On update, this list is additive (existing group ids are not removed).
- `tax` (object)
Request body example:
```json
{
"seller_id": 7,
"name": "Wholesale EUR",
"prices_include_tax": false
}
```
**Responses**
- `201` — Price list created
Response example, 201:
```json
{
"id": 10,
"name": "Wholesale EUR",
"currency_code": "EUR",
"created_on": "2024-01-15T10:00:00Z",
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/price_lists/10"
},
"products:add_price_item": {
"href": "https://api.onbolder.com/v2/price_lists/10/items",
"method": "post"
}
}
}
```
---
# Delete a price item
Source: https://developers.onbolder.com/api/v2/price-lists/delete-price-item
Remove the price override for this variant from the price list.
`DELETE /price_lists/{price_list_id}/items/{id}`
**Responses**
- `204` — Price item deleted
---
# Delete a price list
Source: https://developers.onbolder.com/api/v2/price-lists/delete-price-list
`DELETE /price_lists/{id}`
**Responses**
- `204` — Price list deleted
---
# Get a price list
Source: https://developers.onbolder.com/api/v2/price-lists/get-price-list
`GET /price_lists/{id}`
**Responses**
- `200` — Price list entity
- `403` — Access denied
Response example, 200:
```json
{
"id": 10,
"name": "Wholesale EUR",
"currency_code": "EUR",
"contact_groups_count": 3,
"created_on": "2024-01-15T10:00:00Z",
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/price_lists/10"
},
"products:price_items": {
"href": "https://api.onbolder.com/v2/price_lists/10/items"
},
"products:add_price_item": {
"href": "https://api.onbolder.com/v2/price_lists/10/items",
"method": "post"
},
"products:delete_price_list": {
"href": "https://api.onbolder.com/v2/price_lists/10",
"method": "delete"
}
}
}
```
---
# List products in a price list
Source: https://developers.onbolder.com/api/v2/price-lists/get-price-list-products
Returns all products that have a price override within this price list.
`GET /price_lists/{id}/products`
**Responses**
- `200` — Products in this price list
- `404` — Price list not found
Response example, 200:
```json
{
"_class": [
"results",
"priceListProducts"
],
"total_items": 3,
"_embedded": {
"items": [
{
"id": 101,
"product_id": 101,
"name": "Summer Dress",
"slug": "summer-dress",
"price": 3999,
"retail_price": 4999,
"_embedded": {
"price_items": [
{
"id": 201,
"variant_id": 201,
"price": 3999
}
]
}
}
]
}
}
```
---
# List price items
Source: https://developers.onbolder.com/api/v2/price-lists/list-price-items
Returns all price item overrides within the given price list.
`GET /price_lists/{price_list_id}/items`
**Responses**
- `200` — Price items list
Response example, 200:
```json
{
"_class": [
"results",
"priceItems"
],
"total_items": 2,
"_embedded": {
"items": [
{
"id": 201,
"variant_id": 201,
"product_id": 101,
"name": "Summer Dress",
"sku": "SD-001",
"price": 3999,
"retail_price": 4999,
"visible": true
}
]
}
}
```
---
# List price lists
Source: https://developers.onbolder.com/api/v2/price-lists/list-price-lists
Returns price lists for the authenticated account. A price list lets
you override variant prices for specific customer groups or channels.
`GET /price_lists`
**Responses**
- `200` — Paginated list of price lists
Response example, 200:
```json
{
"_class": [
"results",
"price_lists"
],
"total_items": 1,
"per_page": 20,
"page": 1,
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/price_lists"
},
"products:create_price_list": {
"href": "https://api.onbolder.com/v2/price_lists",
"method": "post"
}
},
"_embedded": {
"items": [
{
"id": 10,
"name": "Wholesale EUR",
"currency_code": "EUR",
"created_on": "2024-01-15T10:00:00Z",
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/price_lists/10"
}
}
}
]
}
}
```
---
# Update a price item
Source: https://developers.onbolder.com/api/v2/price-lists/update-price-item
Set or update the price override for a variant in this price list.
`PUT /price_lists/{price_list_id}/items/{id}`
**Request body** (`application/json`, required)
- `price` (integer) — In cents
Request body example:
```json
{
"price": 3499
}
```
**Responses**
- `200` — Price item updated
Response example, 200:
```json
{
"id": 0,
"variant_id": 0,
"product_id": 0,
"name": "string",
"sku": "string",
"price": 0,
"price_comparison": 0,
"retail_price": 0,
"cart_quantity_multiple": 0,
"visible": true,
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
```
---
# Update a price list
Source: https://developers.onbolder.com/api/v2/price-lists/update-price-list
`PUT /price_lists/{id}`
**Request body** (`application/json`, required)
- `name` (string)
- `prices_include_tax` (boolean)
- `use_price_comparison_for_vol_discounts` (boolean)
- `contact_group_ids` (integer[]) — Additive — existing group ids on the price list are not removed.
- `tax` (object)
Request body example:
```json
{
"name": "Wholesale EUR - Revised"
}
```
**Responses**
- `200` — Price list updated
Response example, 200:
```json
{
"id": 10,
"name": "Wholesale EUR - Revised",
"currency_code": "EUR",
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/price_lists/10"
}
}
}
```
---
# Create a product type
Source: https://developers.onbolder.com/api/v2/product-types/create-product-type
`POST /product_types`
**Request body** (`application/json`, required)
- `shop_id` (integer, required)
- `name` (string, required)
- `slug` (string)
- `description` (string)
- `cart_quantity_multiple` (integer)
- `unit_of_measure` (string)
- `unit_display_quantity` (integer)
Request body example:
```json
{
"shop_id": 1,
"name": "Electronics",
"slug": "electronics"
}
```
**Responses**
- `201` — Product type created
Response example, 201:
```json
{
"id": 2,
"slug": "electronics",
"name": "Electronics",
"cart_max_quantity_per_product": null,
"cart_quantity_multiple": null
}
```
---
# List product types
Source: https://developers.onbolder.com/api/v2/product-types/list-product-types
Returns product types for a shop. `shop_id` is required.
`GET /product_types`
**Responses**
- `200` — Product types list
Response example, 200:
```json
{
"_class": [
"results",
"productTypes"
],
"total_items": 3,
"_embedded": {
"items": [
{
"id": 1,
"name": "Clothing",
"slug": "clothing"
}
]
}
}
```
---
# Update or upsert a product type
Source: https://developers.onbolder.com/api/v2/product-types/update-product-type
If no product type with this `id` exists for the account's seller, one is created instead (upsert), returning 201.
`PUT /product_types/{id}`
**Request body** (`application/json`, required)
- `name` (string)
- `slug` (string)
- `cart_quantity_multiple` (integer)
- `unit_of_measure` (string)
Request body example:
```json
{
"name": "Electronics Updated"
}
```
**Responses**
- `200` — Product type updated
- `201` — Product type did not exist — created instead
---
# Batch update products
Source: https://developers.onbolder.com/api/v2/products/batch-update-products
Updates multiple **existing** products in a single request (does not create new ones). `tags` is the only field that supports add/remove batch operations — there is no way to bulk-set `status`, price, or other fields through this endpoint.
`POST /products/updaters`
**Request body** (`application/json`, required)
- `ids` (integer[], required)
- `additions` (object)
- `removals` (object)
Request body example:
```json
{
"ids": [
101,
102
],
"additions": {
"tags": [
"clearance"
]
}
}
```
**Responses**
- `201` — Products processed
- `422` — Validation error (e.g. missing \`ids\`, or neither \`additions\`/\`removals\` given)
Response example, 201:
```json
{
"ids": [
101,
102
],
"additions": {
"tags": [
"clearance"
]
},
"_embedded": {
"valid_items": [
{
"id": 101,
"name": "Product A"
}
],
"invalid_items": []
}
}
```
---
# Create a product
Source: https://developers.onbolder.com/api/v2/products/create-product
Creates a product in the specified shop. `shop_id` must be a shop belonging to the authenticated account.
`POST /products`
**cURL**
```bash
curl -s -X POST \
-H "Authorization: Bearer $BOLDER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"shop_id":1,"name":"Organic Cotton Tee","status":"visible","sale_price":2999,"regular_price":3999}' \
"https://api.onbolder.com/v2/products"
```
**Request body** (`application/json`, required)
- `shop_id` (integer, required) — ID of the shop (required on create)
- `name` (string, required) — Product name.
- `slug` (string) — URL slug. Auto-generated from name if omitted.
- `status` (string)
- `class` (string) — Product class. Defaults to \`Product\`.
- `description` (string)
- `short_description` (string)
- `deliverable` (boolean) — Whether the product requires physical delivery. Defaults to \`true\`. Set to \`false\` for digital goods, event tickets, subscriptions, etc. Returned as \`is\_deliverable\` in responses.
- `tracks_stock` (boolean) — Whether stock is tracked for this product's variants.
- `has_prices_per_variant` (boolean) — Required when variants have different prices. Must be \`true\` if any variant sets its own \`regular\_price\`; the API returns 422 otherwise.
- `has_weights_per_variant` (boolean) — Whether variants carry individual weights.
- `regular_price` (integer) — Regular price in cents, applied to all variants (for non-bundle products). Use per-variant \`regular\_price\` inside \`variants\` when \`has\_prices\_per\_variant\` is true.
- `sale_price` (integer) — Sale price in cents, applied to all variants.
- `sale_price_starts_at` (string) — ISO 8601 datetime when the sale price activates.
- `sale_price_ends_at` (string) — ISO 8601 datetime when the sale price expires.
- `product_type_name` (string) — Product type name. Created automatically if it doesn't exist.
- `vendor_name` (string) — Vendor name. Created automatically if it doesn't exist.
- `tags` (string[]) — Tag names. Replaces all existing tags unless \`\_policies.keep\_attributes\` is set.
- `cart_quantity_multiple` (integer) — Enforces cart quantities to be multiples of this value.
- `variant_options` (object) — Names for up to 3 variant option axes (e.g. "Color", "Size").
- `variants` (VariantInput[]) — Variant definitions. By default, variants not present in the array are removed; set \`\_policies.keep\_variants: true\` to preserve them.
- `collections` (object[]) — Collections to assign the product to. Pass \`id\` to reference an existing collection, or \`title\` to create/find by name. Replaces all existing collections unless \`\_policies.keep\_collections: true\`.
- `custom_attributes` (object[]) — Custom meta-field attributes.
- `volume_discount_id` (integer) — Assigns an existing volume discount to this product.
- `relations` (object[]) — Related products (e.g. "goes well with"). Replaces all existing relations unless \`\_policies.keep\_relations: true\`.
- `custom_variant_options` (object) — Custom variant option metadata (id/name/type) for up to 3 option axes, keyed by \`option1\`/\`option2\`/\`option3\`.
- `bundle_options` (object[]) — Configurable bundle option groups (customer picks items from each group). Only applies to \`class: ProductBundle\`.
- `bundle_items` (object[]) — Fixed bundle items (always included, no customer choice). Only applies to \`class: ProductBundle\`.
- `_policies` (object) — Control update behaviour. All flags default to \`false\` (destructive replace). Set to \`true\` to preserve existing data when the corresponding field is not included in the request.
Request body example:
```json
{
"shop_id": 1,
"name": "Organic Cotton Tee",
"slug": "organic-cotton-tee",
"status": "visible",
"sale_price": 2999,
"regular_price": 3999
}
```
**Responses**
- `201` — Product created
- `403` — Not authorized to create products in this shop
- `422` — Validation error or missing shop\_id
Response example, 201:
```json
{
"id": 103,
"name": "Organic Cotton Tee",
"slug": "organic-cotton-tee",
"status": "visible",
"price": 2999,
"regular_price": 3999,
"currency_code": "USD",
"created_on": "2024-06-20T14:22:00Z",
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/products/103"
},
"products:update": {
"href": "https://api.onbolder.com/v2/products/103",
"method": "put"
},
"products:delete": {
"href": "https://api.onbolder.com/v2/products/103",
"method": "delete"
}
}
}
```
---
# Create a product asset
Source: https://developers.onbolder.com/api/v2/products/create-product-asset
`POST /products/{product_id}/assets`
**Request body** (`application/json`, required)
- `asset_type` (string)
- `src` (string)
Request body example:
```json
{
"asset_type": "string",
"src": "string"
}
```
**Responses**
- `201` — Asset created
---
# Create a product relation
Source: https://developers.onbolder.com/api/v2/products/create-product-relation
`POST /products/{product_id}/relations`
**Request body** (`application/json`, required)
- `related_product_id` (integer)
- `relation_type` (string)
Request body example:
```json
{
"related_product_id": 0,
"relation_type": "string"
}
```
**Responses**
- `201` — Relation created
---
# Delete a product
Source: https://developers.onbolder.com/api/v2/products/delete-product
`DELETE /products/{id}`
**cURL**
```bash
curl -s -X DELETE \
-H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/products/101"
```
**Responses**
- `204` — Product deleted
- `403` — Access denied
- `404` — Product not found
---
# Delete a product asset
Source: https://developers.onbolder.com/api/v2/products/delete-product-asset
`DELETE /products/{product_id}/assets/{id}`
**Responses**
- `204` — Asset deleted
---
# Get a product
Source: https://developers.onbolder.com/api/v2/products/get-product
`GET /products/{id}`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/products/101"
```
**Responses**
- `200` — Product entity
- `403` — Access denied (hidden product of another account)
- `404` — Product not found
Response example, 200:
```json
{
"id": 101,
"name": "Wireless Headphones",
"slug": "wireless-headphones",
"status": "visible",
"regular_price": 11999,
"sale_price": 8999,
"price": 8999,
"currency_code": "USD",
"description": "Premium wireless headphones with noise cancellation.",
"tags": [
"electronics",
"audio"
],
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/products/101"
},
"products:update": {
"href": "https://api.onbolder.com/v2/products/101",
"method": "put"
},
"products:delete": {
"href": "https://api.onbolder.com/v2/products/101",
"method": "delete"
}
}
}
```
---
# List product price items
Source: https://developers.onbolder.com/api/v2/products/get-product-price-items
Returns price items for a specific product.
`GET /products/{id}/price_items`
**Responses**
- `200` — Price items list
- `404` — Product not found
Response example, 200:
```json
{
"_class": [
"results",
"priceItems"
],
"total_items": 3
}
```
---
# List custom product attributes
Source: https://developers.onbolder.com/api/v2/products/list-custom-product-attributes
Returns all unique custom attribute names used across products in this shop.
`GET /shops/{shop_id}/custom_product_attributes`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/shops/1/custom_product_attributes"
```
**Responses**
- `200` — Custom attributes list
Response example, 200:
```json
{
"_class": [
"results",
"customAttributes"
],
"_embedded": {
"items": [
{
"name": "material"
},
{
"name": "origin"
}
]
}
}
```
---
# List product tags for a shop
Source: https://developers.onbolder.com/api/v2/products/list-product-tags
Returns all tags currently applied to products in this shop.
`GET /shops/{shop_id}/product_tags`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/shops/1/product_tags"
```
**Responses**
- `200` — Product tags list
Response example, 200:
```json
{
"_class": [
"results",
"tags"
],
"_embedded": {
"items": [
{
"name": "sale"
},
{
"name": "new-arrival"
},
{
"name": "featured"
}
]
}
}
```
---
# List product tags
Source: https://developers.onbolder.com/api/v2/products/list-product-tags-flattened
Returns product tags across the account.
Optionally filter by `shop_id` or `seller_id` query parameter.
`GET /product_tags`
**Responses**
- `200` — Product tags list
---
# List products
Source: https://developers.onbolder.com/api/v2/products/list-products
Returns products scoped to the authenticated account.
**Scope behaviour:**
- Tokens with `products.read` (or `products.write`) scoped to a seller/account:
automatically scoped to the account's own sellers — no extra params needed.
- Tokens without an account/seller scope: `shop_subdomains` is **required**
(prevents enumeration).
`GET /products`
**cURL**
```bash
# List all visible products (account-scoped token)
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/products"
# Search with filters
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/products?q=shirt&status=visible&per_page=50"
```
**Responses**
- `200` — Paginated list of products
- `401` — Invalid or missing token
- `403` — Access denied
- `422` — Missing required parameter (e.g. \`shop\_subdomains\` for tokens without an account/seller scope)
Response example, 200:
```json
{
"_class": [
"results",
"products"
],
"total_items": 2,
"per_page": 20,
"page": 1,
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/products"
},
"shops:show": {
"href": "https://api.onbolder.com/v2/shops/1"
}
},
"_embedded": {
"items": [
{
"id": 101,
"name": "Summer Dress",
"slug": "summer-dress",
"status": "visible",
"price": 49.99,
"regular_price": 69.99,
"currency": "USD",
"created_on": "2024-03-15T10:00:00Z",
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/products/101"
},
"products:update": {
"href": "https://api.onbolder.com/v2/products/101",
"method": "put"
},
"products:delete": {
"href": "https://api.onbolder.com/v2/products/101",
"method": "delete"
}
}
},
{
"id": 102,
"name": "Linen Shirt",
"slug": "linen-shirt",
"status": "visible",
"price": 35,
"regular_price": 35,
"currency": "USD",
"created_on": "2024-02-10T08:30:00Z",
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/products/102"
}
}
}
]
}
}
```
---
# List product sets
Source: https://developers.onbolder.com/api/v2/products/list-shop-product-sets
Returns product sets (dynamic rule-based groupings) for the shop.
`GET /shops/{shop_id}/product_sets`
**Responses**
- `200` — Product sets list
Response example, 200:
```json
{
"_class": [
"results",
"productSets"
],
"total_items": 2,
"_embedded": {
"items": [
{
"id": 1,
"name": "Sale Items",
"rules": []
}
]
}
}
```
---
# List vendors
Source: https://developers.onbolder.com/api/v2/products/list-shop-vendors
Returns all vendor names present in the shop's product catalog.
`GET /shops/{shop_id}/vendors`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/shops/1/vendors"
```
**Responses**
- `200` — Vendors list
Response example, 200:
```json
{
"_class": [
"results",
"vendors"
],
"_embedded": {
"items": [
{
"name": "Acme Corp"
},
{
"name": "Widget Co"
}
]
}
}
```
---
# Products KPI strip
Source: https://developers.onbolder.com/api/v2/products/products-stats
Returns `{ currency_code, period, live, out_of_stock, low_stock, revenue, top_products }`. With `interval` or `series=true` also `series` and `previous_series`: `[{ date, units, revenue }]` (paid orders by close date, cents), zero-filled, oldest first.
`GET /products/stats`
**Responses**
- `200` — KPI numbers
---
# Update a product
Source: https://developers.onbolder.com/api/v2/products/update-product
Updates the product. If a product with the given ID/slug does not exist and
`dont_create` is not set, a new product will be created (upsert).
`PUT /products/{id}`
**cURL**
```bash
curl -s -X PUT \
-H "Authorization: Bearer $BOLDER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"status":"hidden","sale_price":2499}' \
"https://api.onbolder.com/v2/products/101"
```
**Request body** (`application/json`, required)
- `shop_id` (integer, required) — ID of the shop (required on create)
- `name` (string, required) — Product name.
- `slug` (string) — URL slug. Auto-generated from name if omitted.
- `status` (string)
- `class` (string) — Product class. Defaults to \`Product\`.
- `description` (string)
- `short_description` (string)
- `deliverable` (boolean) — Whether the product requires physical delivery. Defaults to \`true\`. Set to \`false\` for digital goods, event tickets, subscriptions, etc. Returned as \`is\_deliverable\` in responses.
- `tracks_stock` (boolean) — Whether stock is tracked for this product's variants.
- `has_prices_per_variant` (boolean) — Required when variants have different prices. Must be \`true\` if any variant sets its own \`regular\_price\`; the API returns 422 otherwise.
- `has_weights_per_variant` (boolean) — Whether variants carry individual weights.
- `regular_price` (integer) — Regular price in cents, applied to all variants (for non-bundle products). Use per-variant \`regular\_price\` inside \`variants\` when \`has\_prices\_per\_variant\` is true.
- `sale_price` (integer) — Sale price in cents, applied to all variants.
- `sale_price_starts_at` (string) — ISO 8601 datetime when the sale price activates.
- `sale_price_ends_at` (string) — ISO 8601 datetime when the sale price expires.
- `product_type_name` (string) — Product type name. Created automatically if it doesn't exist.
- `vendor_name` (string) — Vendor name. Created automatically if it doesn't exist.
- `tags` (string[]) — Tag names. Replaces all existing tags unless \`\_policies.keep\_attributes\` is set.
- `cart_quantity_multiple` (integer) — Enforces cart quantities to be multiples of this value.
- `variant_options` (object) — Names for up to 3 variant option axes (e.g. "Color", "Size").
- `variants` (VariantInput[]) — Variant definitions. By default, variants not present in the array are removed; set \`\_policies.keep\_variants: true\` to preserve them.
- `collections` (object[]) — Collections to assign the product to. Pass \`id\` to reference an existing collection, or \`title\` to create/find by name. Replaces all existing collections unless \`\_policies.keep\_collections: true\`.
- `custom_attributes` (object[]) — Custom meta-field attributes.
- `volume_discount_id` (integer) — Assigns an existing volume discount to this product.
- `relations` (object[]) — Related products (e.g. "goes well with"). Replaces all existing relations unless \`\_policies.keep\_relations: true\`.
- `custom_variant_options` (object) — Custom variant option metadata (id/name/type) for up to 3 option axes, keyed by \`option1\`/\`option2\`/\`option3\`.
- `bundle_options` (object[]) — Configurable bundle option groups (customer picks items from each group). Only applies to \`class: ProductBundle\`.
- `bundle_items` (object[]) — Fixed bundle items (always included, no customer choice). Only applies to \`class: ProductBundle\`.
- `_policies` (object) — Control update behaviour. All flags default to \`false\` (destructive replace). Set to \`true\` to preserve existing data when the corresponding field is not included in the request.
Request body example:
```json
{
"name": "Wireless Headphones Pro",
"regular_price": 12999,
"sale_price": 9999,
"status": "visible",
"description": "Updated premium wireless headphones.",
"tags": [
"electronics",
"audio",
"wireless"
]
}
```
**Responses**
- `200` — Product updated
- `201` — Product created (upsert)
- `403` — Access denied
Response example, 200:
```json
{
"id": 101,
"name": "Wireless Headphones Pro",
"slug": "wireless-headphones-pro",
"status": "visible",
"price": 9999,
"regular_price": 12999,
"currency_code": "USD",
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/products/101"
}
}
}
```
Response example, 201:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"name": "string",
"slug": "string",
"class_name": "string",
"status": "visible",
"description": "string",
"short_description": "string",
"is_bundle": true,
"is_deliverable": true,
"has_options": true,
"tags": [
"string"
],
"currency_code": "USD",
"bundle_price_mode": "price",
"bundle_discount": 0,
"regular_price": 0,
"price": 0,
"sale_price": 0,
"sale_price_starts_at": "2019-08-24T14:15:22Z",
"sale_price_ends_at": "2019-08-24T14:15:22Z",
"has_multiple_prices": true,
"discount_percentage": 0,
"safety_stock": 0,
"available": true,
"stock": 0,
"online_stock": 0,
"all_stock": 0,
"vendor": "string",
"variant_options": [
{
"name": "string",
"value": "string"
}
],
"global_tags": [
"string"
],
"stocks": {
"property1": 0,
"property2": 0
},
"stocks_per_location": {
"property1": 0,
"property2": 0
},
"cart_max_quantity": 0,
"cart_quantity_multiple": 0,
"list_prices": [
{
"price_list_id": 0,
"price": 0
}
],
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
```
---
# Create a promotion
Source: https://developers.onbolder.com/api/v2/promotions/create-promotion
Creates a new promotion for a shop. Promotions apply percentage or fixed-amount
discounts via coupon codes or automatic rules.
`shop_id` is required in the body.
`POST /promotions`
**Request body** (`application/json`, required)
- `shop_id` (integer, required) — ID of the shop (required on create)
- `name` (string, required)
- `status` (string)
- `cumulative` (boolean) — Whether this promotion can stack with others. Defaults to \`true\`.
- `discount_type` (string, required)
- `discount` (number, required) — Discount value — a percentage (0–100) or a fixed amount in cents.
- `activation_mode` (string, required) — How the promotion is activated. \`code\` requires a coupon code; \`auto\` applies automatically to eligible orders; \`fulfillment\` activates for a specific fulfillment method; \`payment\` activates for a specific payment method.
- `code` (string) — Coupon code (required when \`activation\_mode\` is \`code\`).
- `fulfillment_method` (integer) — Fulfillment method ID (required when \`activation\_mode\` is \`fulfillment\`).
- `payment_method_id` (integer) — Payment method ID (required when \`activation\_mode\` is \`payment\`).
- `usage_limit` (integer) — Maximum total redemptions. Unlimited if omitted.
- `per_user_limit` (integer) — Maximum redemptions per customer. Unlimited if omitted.
- `starts_at` (string)
- `expires_at` (string)
- `price_range_from` (integer) — Minimum order subtotal in cents for the promotion to apply.
- `price_range_to` (integer) — Maximum order subtotal in cents for the promotion to apply.
- `product_range_from` (integer) — Minimum number of qualifying products required in the cart.
- `product_range_to` (integer) — Maximum number of qualifying products allowed in the cart.
- `product_discount_group_id` (integer) — ID of the group whose products receive the discount.
- `product_discount_group_type` (string)
- `product_discount_group_filter_mode` (string) — Which items within the discount group receive the discount.
- `product_count_group_id` (integer) — Group used to count qualifying products (for count-gated discounts).
- `product_count_group_type` (string)
- `price_list_id` (integer) — Restrict the promotion to orders using this price list.
- `apply_tag` (string) — Tag to apply to the order when this promotion is used.
Request body example:
```json
{
"shop_id": 1,
"name": "Summer Sale",
"code": "SUMMER20",
"discount_type": "percentage",
"discount": 20,
"activation_mode": "code",
"starts_at": "2024-06-01T00:00:00Z",
"expires_at": "2024-08-31T23:59:59Z"
}
```
**Responses**
- `201` — Promotion created
- `422` — Validation error
Response example, 201:
```json
{
"id": 20,
"name": "Summer Sale",
"code": "SUMMER20",
"discount_type": "percentage",
"discount": 20,
"status": "active",
"starts_at": "2024-06-01T00:00:00Z",
"expires_at": "2024-08-31T23:59:59Z",
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/promotions/20"
}
}
}
```
---
# Delete a promotion
Source: https://developers.onbolder.com/api/v2/promotions/delete-promotion
Soft-deletes a promotion.
`DELETE /promotions/{id}`
**Responses**
- `204` — Promotion deleted
---
# Get a promotion
Source: https://developers.onbolder.com/api/v2/promotions/get-promotion
Returns a single promotion by ID.
`GET /promotions/{id}`
**Responses**
- `200` — Promotion details
- `404` — Promotion not found
Response example, 200:
```json
{
"id": 20,
"name": "Summer Sale",
"code": "SUMMER20",
"discount_type": "percentage",
"discount": 20,
"status": "active",
"starts_at": "2024-06-01T00:00:00Z",
"expires_at": "2024-08-31T23:59:59Z",
"usage_count": 0,
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/promotions/20"
}
}
}
```
---
# List promotions
Source: https://developers.onbolder.com/api/v2/promotions/list-promotions
Returns discount promotions. Promotions apply percentage or
fixed-amount discounts via coupon codes or automatic rules.
`GET /promotions`
**Responses**
- `200` — Paginated promotions list
- `403` — Access denied
- `422` — \`shop\_id\` required
Response example, 200:
```json
{
"_class": [
"results",
"promotions"
],
"total_items": 1,
"per_page": 20,
"page": 1,
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/promotions?shop_id=1"
}
},
"_embedded": {
"items": [
{
"id": 20,
"name": "Summer Sale 20%",
"code": "SUMMER20",
"activation_mode": "code",
"discount_type": "percentage",
"discount": 20,
"status": "active",
"starts_at": "2024-06-01T00:00:00Z",
"expires_at": "2024-08-31T23:59:59Z",
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/promotions/20"
}
}
}
]
}
}
```
---
# Update a promotion
Source: https://developers.onbolder.com/api/v2/promotions/update-promotion
Updates an existing promotion.
`PUT /promotions/{id}`
**Request body** (`application/json`, required)
- `shop_id` (integer, required) — ID of the shop (required on create)
- `name` (string, required)
- `status` (string)
- `cumulative` (boolean) — Whether this promotion can stack with others. Defaults to \`true\`.
- `discount_type` (string, required)
- `discount` (number, required) — Discount value — a percentage (0–100) or a fixed amount in cents.
- `activation_mode` (string, required) — How the promotion is activated. \`code\` requires a coupon code; \`auto\` applies automatically to eligible orders; \`fulfillment\` activates for a specific fulfillment method; \`payment\` activates for a specific payment method.
- `code` (string) — Coupon code (required when \`activation\_mode\` is \`code\`).
- `fulfillment_method` (integer) — Fulfillment method ID (required when \`activation\_mode\` is \`fulfillment\`).
- `payment_method_id` (integer) — Payment method ID (required when \`activation\_mode\` is \`payment\`).
- `usage_limit` (integer) — Maximum total redemptions. Unlimited if omitted.
- `per_user_limit` (integer) — Maximum redemptions per customer. Unlimited if omitted.
- `starts_at` (string)
- `expires_at` (string)
- `price_range_from` (integer) — Minimum order subtotal in cents for the promotion to apply.
- `price_range_to` (integer) — Maximum order subtotal in cents for the promotion to apply.
- `product_range_from` (integer) — Minimum number of qualifying products required in the cart.
- `product_range_to` (integer) — Maximum number of qualifying products allowed in the cart.
- `product_discount_group_id` (integer) — ID of the group whose products receive the discount.
- `product_discount_group_type` (string)
- `product_discount_group_filter_mode` (string) — Which items within the discount group receive the discount.
- `product_count_group_id` (integer) — Group used to count qualifying products (for count-gated discounts).
- `product_count_group_type` (string)
- `price_list_id` (integer) — Restrict the promotion to orders using this price list.
- `apply_tag` (string) — Tag to apply to the order when this promotion is used.
Request body example:
```json
{
"name": "Summer Sale Extended",
"discount": 25
}
```
**Responses**
- `200` — Promotion updated
- `404` — Promotion not found
Response example, 200:
```json
{
"id": 20,
"name": "Summer Sale Extended",
"discount": 25,
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/promotions/20"
}
}
}
```
---
# API root
Source: https://developers.onbolder.com/api/v2/root/get-root
Returns the API entry point. Embeds the authenticated account's shops and
provides navigation links (`_links`) to all v2 resources.
The `products:list` link is a URI template — expand it with search
parameters to list products.
`GET /`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2"
```
**Responses**
- `200` — API root entity
- `401` — Invalid or missing token
- `403` — Access denied
Response example, 200:
```json
{
"_class": [
"root"
],
"account_id": 10,
"account_status": "active",
"uid": 42,
"email": "admin@myshop.com",
"user_name": "admin",
"provider": "bolder",
"scopes": "products.read,orders.read,orders.write",
"_links": {
"curies": [
{
"name": "sellers",
"href": "https://api.onbolder.com/docs/v2#sellers:{rel}",
"templated": true
},
{
"name": "products",
"href": "https://api.onbolder.com/docs/v2#products:{rel}",
"templated": true
},
{
"name": "shops",
"href": "https://api.onbolder.com/docs/v2#shops:{rel}",
"templated": true
},
{
"name": "orders",
"href": "https://api.onbolder.com/docs/v2#orders:{rel}",
"templated": true
},
{
"name": "customers",
"href": "https://api.onbolder.com/docs/v2#customers:{rel}",
"templated": true
}
],
"self": {
"href": "https://api.onbolder.com/v2"
},
"sellers:list": {
"href": "https://api.onbolder.com/v2/sellers"
},
"sellers:show": {
"href": "https://api.onbolder.com/v2/sellers/{id}",
"templated": true
},
"products:list": {
"href": "https://api.onbolder.com/v2/products{?q,status,page,per_page,...}",
"templated": true
},
"orders:list": {
"href": "https://api.onbolder.com/v2/orders{?shop_id,status,page,...}",
"templated": true
},
"customers:list": {
"href": "https://api.onbolder.com/v2/customers{?shop_id,q,page,...}",
"templated": true
}
},
"_embedded": {
"sellers": [
{
"id": 5,
"account_id": 10,
"locale": "en",
"currency_code": "USD",
"products_count": 42,
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/sellers/5"
},
"shops:show": {
"href": "https://api.onbolder.com/v2/shops/1"
},
"shops:list": {
"href": "https://api.onbolder.com/v2/shops?seller_id=5"
}
}
}
],
"shops": [
{
"id": 1,
"name": "My Shop",
"subdomain": "myshop",
"url": "myshop.onbolder.com",
"status": "active"
}
]
}
}
```
---
# Get a seller
Source: https://developers.onbolder.com/api/v2/sellers/get-seller
Returns a single seller by ID. Sellers are account-scoped.
`GET /sellers/{id}`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/sellers/5"
```
**Responses**
- `200` — Seller entity
- `401` — Invalid or missing token
- `403` — Access denied
- `404` — Seller not found
Response example, 200:
```json
{
"id": 5,
"account_id": 10,
"locale": "en",
"currency_code": "USD",
"provides_shipping": true,
"provides_pickup": true,
"prices_include_tax": true,
"tax_rate": 0.19,
"tax_name": "VAT",
"products_count": 42,
"created_on": "2023-01-15T10:00:00Z",
"updated_on": "2024-03-20T14:30:00Z",
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/sellers/5"
},
"shops:show": {
"href": "https://api.onbolder.com/v2/shops/1",
"title": "Main shop"
},
"shops:list": {
"href": "https://api.onbolder.com/v2/shops?seller_id=5",
"title": "All shops owned by this seller"
},
"products:list": {
"href": "https://api.onbolder.com/v2/products{?seller_ids,...}",
"templated": true
}
}
}
```
---
# List sellers
Source: https://developers.onbolder.com/api/v2/sellers/list-sellers
Returns all sellers belonging to the authenticated account.
Results are automatically scoped to the account associated with the token.
`GET /sellers`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/sellers"
```
**Responses**
- `200` — List of sellers
- `401` — Invalid or missing token
- `403` — Access denied
Response example, 200:
```json
{
"_class": [
"results",
"sellers"
],
"total_items": 1,
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/sellers"
}
},
"_embedded": {
"items": [
{
"id": 5,
"account_id": 10,
"locale": "en",
"currency_code": "USD",
"provides_shipping": true,
"provides_pickup": true,
"prices_include_tax": true,
"tax_rate": 0.19,
"tax_name": "VAT",
"products_count": 42,
"created_on": "2023-01-15T10:00:00Z",
"updated_on": "2024-03-20T14:30:00Z",
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/sellers/5"
},
"shops:show": {
"href": "https://api.onbolder.com/v2/shops/1"
},
"shops:list": {
"href": "https://api.onbolder.com/v2/shops?seller_id=5"
},
"products:list": {
"href": "https://api.onbolder.com/v2/products{?seller_ids,...}",
"templated": true
}
}
}
]
}
}
```
---
# Assign a user to a branch
Source: https://developers.onbolder.com/api/v2/shops/assign-shop-branch-user
Needs `shops.write`. The user must have a role in the seller; the assignment records that role (`vendedor`,
`supervisor`...). Assigning twice is a no-op. Note that a full-scope token of a user with branches only sees the
orders fulfilled from those branches.
`POST /shops/{shop_id}/branches/{id}/users`
**Request body** (`application/json`, required)
- `user_id` (integer, required)
Request body example:
```json
{
"user_id": 0
}
```
**Responses**
- `201` — The branch with its users
- `403` — Access denied
- `422` — Not a user of this shop
---
# Create a shipping option (shipping cost)
Source: https://developers.onbolder.com/api/v2/shops/create-shipping-option
Adds a shipping option to the table. See `ShippingOptionInput` for how `rate_mode` decides the price.
`POST /shipping_tables/{shipping_table_id}/shipping_options`
**Request body** (`application/json`, required)
- `name` (string) — Name shown to buyers at checkout. Required on create.
- `rate_mode` (string) — Required on create.
- `active` (boolean)
- `courier` (string | null)
- `exclusive` (boolean) — When true, buyers in this option's regions/localities don't see broader options. Only for region or locality zones, and not overlapping another exclusive option of the table.
- `price_list_id` (integer | null) — Only for orders with this price list. \`-1\` any price list, \`-2\` orders without a price list, \`null\` all orders.
- `base_rate` (integer)
- `rate_per_km` (integer | null)
- `rate_per_weight_step` (integer | null)
- `weight_step_in_gr` (integer | null)
- `over_weight_amount_per_kg` (integer) — Amount per extra kg over \`max\_allowed\_weight\_in\_gr\` (or the last rate). \`-1\` means heavier orders can't use this option.
- `max_allowed_weight_in_gr` (integer | null)
- `min_days_to_delivery` (integer | null) — Business days. Must be lower than \`max\_days\_to\_delivery\`.
- `max_days_to_delivery` (integer | null)
- `cutoff_time` (string | null)
- `availability_schedule_id` (integer | null) — Must belong to the seller.
- `delivery_window_schedule_id` (integer | null) — Must belong to the seller.
- `polygon_set_id` (integer | null) — Polygon set limiting the zone. Must belong to the seller.
- `polygons_exclude_mode` (boolean) — Exclude (instead of include) the polygon set area.
- `shipping_zones` (ShippingZone[]) — Zones covered, all at the same level: country (only \`country\_code\`), region (\`country\_code\` + \`region\_code\`, one country) or locality (all three, one country and region). Required on create; on update it replaces the zones.
- `shipping_rates` (ShippingRate[]) — Only for \`by-weight\` and \`by-stretch\` (required for them on create, or when switching to them). On update it replaces all rates.
Request body example, Flat rate for a region:
```json
{
"name": "Envío a Santiago",
"rate_mode": "flat",
"base_rate": 3990,
"courier": "Starken",
"min_days_to_delivery": 1,
"max_days_to_delivery": 3,
"shipping_zones": [
{
"country_code": "CL",
"region_code": "RM"
}
]
}
```
Request body example, Rate by weight ranges, for two localities:
```json
{
"name": "Envío por peso",
"rate_mode": "by-weight",
"shipping_zones": [
{
"country_code": "CL",
"region_code": "RM",
"locality_id": 13101
},
{
"country_code": "CL",
"region_code": "RM",
"locality_id": 13114
}
],
"shipping_rates": [
{
"to": 1000,
"amount": 2500
},
{
"to": 5000,
"amount": 4500
}
]
}
```
**Responses**
- `201` — Shipping option created
- `403` — Access denied
- `422` — Validation errors, e.g. missing \`shipping\_zones\`, zones at mixed levels, \`shipping\_rates\` missing for \`by-weight\`/\`by-stretch\` (or sent for another mode), \`base\_rate\` 0 on a \`flat\` option, or a \`price\_list\_id\`/schedule/polygon set of another seller.
Response example, 201:
```json
{
"_class": [
"shippingOption"
],
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/shipping_options/1"
},
"shops:show_shipping_table": {
"href": "https://api.onbolder.com/v2/shipping_tables/2"
},
"shops:update_shipping_option": {
"href": "https://api.onbolder.com/v2/shipping_options/1",
"method": "put"
},
"shops:delete_shipping_option": {
"href": "https://api.onbolder.com/v2/shipping_options/1",
"method": "delete"
}
},
"id": 1,
"name": "Por peso a Santiago",
"active": true,
"courier": "Starken",
"exclusive": false,
"price_list_id": null,
"rate_mode": "by-weight",
"base_rate": 0,
"rate_per_km": null,
"rate_per_weight_step": null,
"weight_step_in_gr": null,
"over_weight_amount_per_kg": -1,
"max_allowed_weight_in_gr": null,
"min_days_to_delivery": null,
"max_days_to_delivery": null,
"cutoff_time": null,
"availability_schedule_id": null,
"delivery_window_schedule_id": null,
"shipping_table_id": 2,
"polygon_set_id": null,
"polygons_exclude_mode": false,
"created_at": "2026-10-20T13:47:28.000Z",
"updated_at": "2026-10-20T13:47:28.000Z",
"_embedded": {
"shipping_zones": [
{
"_class": [
"shippingZone"
],
"id": 1,
"locality_id": null,
"region_code": "RM",
"country_code": "CL"
}
],
"shipping_rates": [
{
"_class": [
"shippingRate"
],
"id": 1,
"to": 1000,
"amount": 2500
},
{
"_class": [
"shippingRate"
],
"id": 2,
"to": 5000,
"amount": 4500
}
]
}
}
```
---
# Create a shipping table
Source: https://developers.onbolder.com/api/v2/shops/create-shipping-table
Creates an empty shipping table for a seller. Pass `seller_id` (or `shop_id`);
both can be omitted when the token has a single shop. Names are unique per seller.
`POST /shipping_tables`
**Request body** (`application/json`, required)
- `seller_id` (integer)
- `shop_id` (integer)
- `name` (string, required)
Request body example:
```json
{
"seller_id": 1,
"name": "Envíos sucursal norte"
}
```
**Responses**
- `201` — Shipping table created
- `403` — Access denied
- `422` — Validation errors (missing or duplicate name)
---
# Create a dealer user
Source: https://developers.onbolder.com/api/v2/shops/create-shop-admin
Adds a user with the `vendedor` role (own-orders access, see Authentication) to the shop's seller and emails
them an invitation. Needs `shops.write`. A new user is created in the seller's account with a random password
the dealer replaces through the login page's password reset; an existing user of the same account just
gets the role. Other roles cannot be assigned here.
`POST /shops/{shop_id}/admins`
**Request body** (`application/json`, required)
- `email` (string, required) — Lowercased on save. \`422\` if the user already has a role in the seller or belongs to another account.
- `name` (string) — Defaults to the part of the email before the \`@\`.
- `branch_id` (integer) — The sucursal (a branch of the seller) the dealer works at. \`422\` on \`$.branch\_id\` if it is not one.
Request body example:
```json
{
"email": "string",
"name": "string",
"branch_id": 0
}
```
**Responses**
- `201` — The created dealer user
- `403` — Access denied
- `422` — Invalid or unknown fields, or the user cannot be added
Response example, 201:
```json
{
"id": 11,
"email": "ana@dealer.cl",
"name": "Ana Dealer",
"role": "vendedor",
"disabled": false,
"last_logged_on": null
}
```
---
# Create a branch (sucursal)
Source: https://developers.onbolder.com/api/v2/shops/create-shop-branch
Needs `shops.write`. Creates a branch of type `Office` that is hidden and is not a fulfillment location (no
shipping table, no pickup), so it never appears in the store locator or at checkout. The name must be unique among
the seller's branches; the dealer app uses the convention `Empresa · Sucursal`.
`POST /shops/{shop_id}/branches`
**Request body** (`application/json`, required)
- `name` (string, required)
Request body example:
```json
{
"name": "string"
}
```
**Responses**
- `201` — The created branch
- `403` — Access denied
- `422` — Blank or used name, or unknown fields
---
# Delete a shipping option
Source: https://developers.onbolder.com/api/v2/shops/delete-shipping-option
Deletes the option with its zones and rates.
`DELETE /shipping_options/{id}`
**Responses**
- `204` — Deleted
- `403` — Access denied
- `404` — Shipping option not found
---
# Get a shipping option
Source: https://developers.onbolder.com/api/v2/shops/get-shipping-option
`GET /shipping_options/{id}`
**Responses**
- `200` — Shipping option
- `404` — Shipping option not found
Response example, 200:
```json
{
"name": "string",
"rate_mode": "flat",
"active": true,
"courier": "string",
"exclusive": true,
"price_list_id": 0,
"base_rate": 0,
"rate_per_km": 0,
"rate_per_weight_step": 0,
"weight_step_in_gr": 0,
"over_weight_amount_per_kg": -1,
"max_allowed_weight_in_gr": 0,
"min_days_to_delivery": 0,
"max_days_to_delivery": 0,
"cutoff_time": "14:00",
"availability_schedule_id": 0,
"delivery_window_schedule_id": 0,
"polygon_set_id": 0,
"polygons_exclude_mode": true,
"shipping_zones": [
{
"id": 0,
"country_code": "CL",
"region_code": "RM",
"locality_id": 13101
}
],
"shipping_rates": [
{
"id": 0,
"to": 1000,
"amount": 2500
}
],
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"shipping_table_id": 0,
"created_at": "2019-08-24T14:15:22Z",
"updated_at": "2019-08-24T14:15:22Z",
"_embedded": {
"shipping_zones": [
{
"id": 0,
"country_code": "CL",
"region_code": "RM",
"locality_id": 13101
}
],
"shipping_rates": [
{
"id": 0,
"to": 1000,
"amount": 2500
}
]
}
}
```
---
# Get a shipping table for a shop
Source: https://developers.onbolder.com/api/v2/shops/get-shipping-table
`GET /shops/{shop_id}/shipping_tables/{id}`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/shops/1/shipping_tables/1"
```
**Responses**
- `200` — Shipping table with rates
Response example, 200:
```json
{
"id": 1,
"name": "Standard Domestic",
"shipping_options": [
{
"id": 10,
"name": "Courier by weight step",
"active": true,
"courier": "BlueExpress",
"rate_mode": "by-weight-step",
"base_rate": 1000,
"rate_per_weight_step": 500,
"weight_step_in_gr": 250,
"over_weight_amount_per_kg": -1,
"shipping_rates": []
}
]
}
```
---
# Get a shipping table
Source: https://developers.onbolder.com/api/v2/shops/get-shipping-table-flattened
Returns the table with its shipping options embedded (`_embedded.shipping_options`, same representation as the shipping option endpoints).
`GET /shipping_tables/{id}`
**Responses**
- `200` — Shipping table details
- `403` — Access denied
- `404` — Shipping table not found
Response example, 200:
```json
{
"_class": [
"shippingTable"
],
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/shipping_tables/2"
},
"shops:shipping_options": {
"href": "https://api.onbolder.com/v2/shipping_tables/2/shipping_options"
},
"shops:update_shipping_table": {
"href": "https://api.onbolder.com/v2/shipping_tables/2",
"method": "put"
},
"shops:create_shipping_option": {
"href": "https://api.onbolder.com/v2/shipping_tables/2/shipping_options",
"method": "post"
}
},
"id": 2,
"name": "Opciones de envío para Venta Online",
"seller_id": 1,
"_embedded": {
"shipping_options": []
}
}
```
---
# Get a shop
Source: https://developers.onbolder.com/api/v2/shops/get-shop
`GET /shops/{id}`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/shops/1"
```
**Responses**
- `200` — Shop entity
- `403` — Access denied
- `404` — Shop not found
Response example, 200:
```json
{
"id": 1,
"seller_id": 5,
"account_id": 10,
"name": "My Store",
"subdomain": "mystore",
"url": "mystore.onbolder.com",
"locale": "en",
"currency_code": "USD",
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/shops/1"
},
"products:list": {
"href": "https://api.onbolder.com/v2/products?shop_id=1"
},
"orders:list": {
"href": "https://api.onbolder.com/v2/orders?shop_id=1"
},
"customers:list": {
"href": "https://api.onbolder.com/v2/customers?shop_id=1"
},
"hub:subscriptions": {
"href": "https://api.onbolder.com/v2/sellers/5/webhooks"
}
}
}
```
---
# Get a shop admin
Source: https://developers.onbolder.com/api/v2/shops/get-shop-admin
`GET /shops/{shop_id}/admins/{id}`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/shops/1/admins/10"
```
**Responses**
- `200` — Admin entity
- `404` — Admin not found
Response example, 200:
```json
{
"id": 10,
"email": "admin@myshop.com",
"name": "Admin User",
"role": "administrador",
"disabled": false,
"last_logged_on": "2024-06-20T14:00:00Z"
}
```
---
# Get a branch
Source: https://developers.onbolder.com/api/v2/shops/get-shop-branch
A user with only `sellers.read` gets `404` for a branch they are not assigned to.
`GET /shops/{shop_id}/branches/{id}`
**Responses**
- `200` — The branch
- `403` — Access denied
- `404` — Not found
---
# Get shop settings
Source: https://developers.onbolder.com/api/v2/shops/get-shop-settings
`GET /shops/{id}/settings`
**Responses**
- `200` — Shop with settings
Response example, 200:
```json
{
"id": 1,
"name": "My Store",
"subdomain": "mystore",
"currency": "USD",
"country": "US",
"tax_rate": 0.21,
"shipping_zones": [
"domestic",
"international"
],
"payment_methods": [
"credit_card",
"paypal"
],
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/shops/1/settings"
}
}
}
```
---
# List events
Source: https://developers.onbolder.com/api/v2/shops/list-events-flattened
Returns events across the account.
Optionally filter by `shop_id` or `seller_id` query parameter.
`GET /events`
**Responses**
- `200` — Events list
---
# List exchange rates for a shop
Source: https://developers.onbolder.com/api/v2/shops/list-exchange-rates
Returns current USD exchange rates for supported currencies.
`GET /shops/{shop_id}/exchange_rates`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/shops/1/exchange_rates"
```
**Responses**
- `200` — Exchange rates
Response example, 200:
```json
{
"_class": [
"results",
"exchangeRates"
],
"_embedded": {
"items": [
{
"from": "USD",
"to": "CLP",
"rate": 950.5,
"updated_at": "2024-06-15T00:00:00Z"
},
{
"from": "USD",
"to": "ARS",
"rate": 890,
"updated_at": "2024-06-15T00:00:00Z"
}
]
}
}
```
---
# List exchange rates
Source: https://developers.onbolder.com/api/v2/shops/list-exchange-rates-flattened
Returns exchange rates across the account.
Optionally filter by `shop_id` or `seller_id` query parameter.
`GET /exchange_rates`
**Responses**
- `200` — Exchange rates list
---
# List order tags
Source: https://developers.onbolder.com/api/v2/shops/list-order-tags
Returns all tags currently applied to orders in this shop.
`GET /shops/{shop_id}/order_tags`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/shops/1/order_tags"
```
**Responses**
- `200` — Order tags list
Response example, 200:
```json
{
"_class": [
"results",
"tags"
],
"_embedded": {
"items": [
{
"name": "urgent"
},
{
"name": "gift"
}
]
}
}
```
---
# List payment methods for a shop
Source: https://developers.onbolder.com/api/v2/shops/list-payment-methods
Returns the payment methods configured for the shop.
`GET /shops/{shop_id}/payment_methods`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/shops/1/payment_methods"
```
**Responses**
- `200` — Payment methods list
Response example, 200:
```json
{
"_class": [
"results",
"paymentMethods"
],
"total_items": 2,
"_embedded": {
"items": [
{
"id": 3,
"type": "webpay_rest_payment_method",
"title": "Webpay Plus (Rest)",
"active": true
},
{
"id": 7,
"type": "bank_transfer_payment_method",
"title": "Transferencia bancaria",
"active": true
}
]
}
}
```
---
# List payment methods
Source: https://developers.onbolder.com/api/v2/shops/list-payment-methods-flattened
Returns payment methods across the account.
Optionally filter by `shop_id` or `seller_id` query parameter.
`GET /payment_methods`
**Responses**
- `200` — Payment methods list
---
# List shipping options (shipping costs)
Source: https://developers.onbolder.com/api/v2/shops/list-shipping-options
Returns the shipping options of a shipping table.
`GET /shipping_tables/{shipping_table_id}/shipping_options`
**Responses**
- `200` — Paginated shipping options (\`\_embedded.items\`)
- `403` — Access denied
- `404` — Shipping table not found
---
# List shipping tables for a shop
Source: https://developers.onbolder.com/api/v2/shops/list-shipping-tables
Returns shipping tables configured for the shop, with their shipping options.
Each option has a `rate_mode` that says how its price is computed (amounts in cents):
- `flat`: `base_rate`.
- `by-weight`: `base_rate` plus the `shipping_rates` range matching the order weight (`to` in grams).
- `by-distance`: `base_rate` plus `rate_per_km` for every km between origin and destination.
- `by-stretch`: `base_rate` plus the `shipping_rates` range matching the distance (`to` in km). `rate_per_km` only controls what happens over the last range (`1` keeps the last rate, higher values charge that per extra km).
- `by-weight-step`: `base_rate` plus `rate_per_weight_step` for every started `weight_step_in_gr`
grams of the order (e.g. 500 per 250 g: a 251 g order pays 1000). It has no `shipping_rates`.
`GET /shops/{shop_id}/shipping_tables`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/shops/1/shipping_tables"
```
**Responses**
- `200` — Shipping tables list
Response example, 200:
```json
{
"_class": [
"results",
"shippingTables"
],
"total_items": 1,
"_embedded": {
"items": [
{
"id": 1,
"name": "Standard Domestic",
"shipping_options": [
{
"id": 10,
"name": "Courier by weight step",
"active": true,
"courier": "BlueExpress",
"rate_mode": "by-weight-step",
"base_rate": 1000,
"rate_per_weight_step": 500,
"weight_step_in_gr": 250,
"over_weight_amount_per_kg": -1,
"shipping_rates": []
}
]
}
]
}
}
```
---
# List shipping tables
Source: https://developers.onbolder.com/api/v2/shops/list-shipping-tables-flattened
Returns shipping tables across the account.
Optionally filter by `shop_id` or `seller_id` query parameter.
`GET /shipping_tables`
**Responses**
- `200` — Shipping tables list
---
# List shop admins
Source: https://developers.onbolder.com/api/v2/shops/list-shop-admins
Returns admin users for the given shop.
`GET /shops/{shop_id}/admins`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/shops/1/admins"
```
**Responses**
- `200` — List of admins
- `403` — Access denied
Response example, 200:
```json
{
"_class": [
"results",
"admins"
],
"total_items": 2,
"_embedded": {
"items": [
{
"id": 10,
"email": "admin@myshop.com",
"name": "Admin User",
"role": "administrador",
"disabled": false,
"last_logged_on": "2024-06-20T14:00:00Z"
}
]
}
}
```
---
# List branches
Source: https://developers.onbolder.com/api/v2/shops/list-shop-branches
The seller's branches that are not deleted, sorted by name. Staff (`shops.read`) see all of them, each with its
assigned `users` and their role. A user with only `sellers.read` (the vendedor role) sees just the branches
they are assigned to, without the users: that is how a dealer app finds its own sucursal.
`GET /shops/{shop_id}/branches`
**Responses**
- `200` — Paginated list under \`\_embedded.items\`
- `403` — Access denied
Response example, 200:
```json
{
"_class": [
"results",
"branches"
],
"total_items": 1,
"_embedded": {
"items": [
{
"id": 12,
"name": "BYD · Las Condes",
"type": "Office",
"status": "hidden",
"_embedded": {
"users": [
{
"id": 31,
"name": "Camila Rojas",
"email": "camila.rojas@example.cl",
"role": "vendedor"
}
]
}
}
]
}
}
```
---
# List shop events
Source: https://developers.onbolder.com/api/v2/shops/list-shop-events
Returns the event log for this shop (webhook delivery history, order events, etc).
`GET /shops/{shop_id}/events`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/shops/1/events"
```
**Responses**
- `200` — Paginated event list
Response example, 200:
```json
{
"_class": [
"results",
"events"
],
"total_items": 42,
"_embedded": {
"items": [
{
"sequence": 1001,
"type": "order",
"topic": "order.created",
"item_type": "Order",
"item_id": 5001,
"item_name": "ORD-5001",
"scope": "shop",
"payload": {},
"created_on": "2024-06-15T09:45:00Z"
}
]
}
}
```
---
# List shops
Source: https://developers.onbolder.com/api/v2/shops/list-shops
Returns shops belonging to the authenticated account.
`GET /shops`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/shops"
```
**Responses**
- `200` — Paginated list of shops
Response example, 200:
```json
{
"_class": [
"results",
"shops"
],
"total_items": 1,
"per_page": 20,
"page": 1,
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/shops"
}
},
"_embedded": {
"items": [
{
"id": 1,
"name": "My Store",
"subdomain": "mystore",
"url": "https://mystore.onbolder.com",
"status": "active",
"currency": "USD",
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/shops/1"
}
}
}
]
}
}
```
---
# Remove a user from a branch
Source: https://developers.onbolder.com/api/v2/shops/unassign-shop-branch-user
Needs `shops.write`.
`DELETE /shops/{shop_id}/branches/{id}/users/{user_id}`
**Responses**
- `204` — Removed
- `403` — Access denied
---
# Update a shipping option
Source: https://developers.onbolder.com/api/v2/shops/update-shipping-option
Only the fields sent are changed. `shipping_zones` and `shipping_rates` replace
the current lists when sent. Changes are atomic: if validation fails nothing is saved.
`PUT /shipping_options/{id}`
**Request body** (`application/json`, required)
- `name` (string) — Name shown to buyers at checkout. Required on create.
- `rate_mode` (string) — Required on create.
- `active` (boolean)
- `courier` (string | null)
- `exclusive` (boolean) — When true, buyers in this option's regions/localities don't see broader options. Only for region or locality zones, and not overlapping another exclusive option of the table.
- `price_list_id` (integer | null) — Only for orders with this price list. \`-1\` any price list, \`-2\` orders without a price list, \`null\` all orders.
- `base_rate` (integer)
- `rate_per_km` (integer | null)
- `rate_per_weight_step` (integer | null)
- `weight_step_in_gr` (integer | null)
- `over_weight_amount_per_kg` (integer) — Amount per extra kg over \`max\_allowed\_weight\_in\_gr\` (or the last rate). \`-1\` means heavier orders can't use this option.
- `max_allowed_weight_in_gr` (integer | null)
- `min_days_to_delivery` (integer | null) — Business days. Must be lower than \`max\_days\_to\_delivery\`.
- `max_days_to_delivery` (integer | null)
- `cutoff_time` (string | null)
- `availability_schedule_id` (integer | null) — Must belong to the seller.
- `delivery_window_schedule_id` (integer | null) — Must belong to the seller.
- `polygon_set_id` (integer | null) — Polygon set limiting the zone. Must belong to the seller.
- `polygons_exclude_mode` (boolean) — Exclude (instead of include) the polygon set area.
- `shipping_zones` (ShippingZone[]) — Zones covered, all at the same level: country (only \`country\_code\`), region (\`country\_code\` + \`region\_code\`, one country) or locality (all three, one country and region). Required on create; on update it replaces the zones.
- `shipping_rates` (ShippingRate[]) — Only for \`by-weight\` and \`by-stretch\` (required for them on create, or when switching to them). On update it replaces all rates.
Request body example, Replace the weight ranges:
```json
{
"shipping_rates": [
{
"to": 2000,
"amount": 3000
},
{
"to": 10000,
"amount": 6000
}
]
}
```
Request body example, Switch to a flat rate (drops the ranges):
```json
{
"rate_mode": "flat",
"base_rate": 4990
}
```
Request body example, Hide it from checkout:
```json
{
"active": false
}
```
**Responses**
- `200` — Shipping option updated
- `403` — Access denied
- `422` — Validation errors
Response example, 200:
```json
{
"name": "string",
"rate_mode": "flat",
"active": true,
"courier": "string",
"exclusive": true,
"price_list_id": 0,
"base_rate": 0,
"rate_per_km": 0,
"rate_per_weight_step": 0,
"weight_step_in_gr": 0,
"over_weight_amount_per_kg": -1,
"max_allowed_weight_in_gr": 0,
"min_days_to_delivery": 0,
"max_days_to_delivery": 0,
"cutoff_time": "14:00",
"availability_schedule_id": 0,
"delivery_window_schedule_id": 0,
"polygon_set_id": 0,
"polygons_exclude_mode": true,
"shipping_zones": [
{
"id": 0,
"country_code": "CL",
"region_code": "RM",
"locality_id": 13101
}
],
"shipping_rates": [
{
"id": 0,
"to": 1000,
"amount": 2500
}
],
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"shipping_table_id": 0,
"created_at": "2019-08-24T14:15:22Z",
"updated_at": "2019-08-24T14:15:22Z",
"_embedded": {
"shipping_zones": [
{
"id": 0,
"country_code": "CL",
"region_code": "RM",
"locality_id": 13101
}
],
"shipping_rates": [
{
"id": 0,
"to": 1000,
"amount": 2500
}
]
}
}
```
---
# Update a shipping table
Source: https://developers.onbolder.com/api/v2/shops/update-shipping-table
Renames a shipping table.
`PUT /shipping_tables/{id}`
**Request body** (`application/json`, required)
- `name` (string)
Request body example:
```json
{
"name": "Envíos online"
}
```
**Responses**
- `200` — Shipping table updated
- `403` — Access denied
- `422` — Validation errors
---
# Update or disable a dealer user
Source: https://developers.onbolder.com/api/v2/shops/update-shop-admin
Renames a `vendedor` user or disables/enables their access to the seller. Needs `shops.write`. A disabled
dealer keeps the user but gets no scopes: logins, token exchanges and refreshes fail, and the merchant admin
refuses them. Access tokens already issued last until they expire (1 hour). Users with another role answer `422`.
`PUT /shops/{shop_id}/admins/{id}`
**Request body** (`application/json`, required)
- `name` (string)
- `disabled` (boolean)
- `branch_id` (integer) — Moves the dealer to this sucursal (replacing their current one); blank takes them out of it; omitted leaves it as is.
Request body example:
```json
{
"name": "string",
"disabled": true,
"branch_id": 0
}
```
**Responses**
- `200` — The dealer user
- `403` — Access denied
- `404` — User not found in this shop
- `422` — Not a dealer user, or invalid or unknown fields
Response example, 200:
```json
{
"id": 11,
"email": "ana@dealer.cl",
"name": "Ana Dealer",
"role": "vendedor",
"disabled": true,
"last_logged_on": "2024-06-20T14:00:00Z"
}
```
---
# Rename a branch
Source: https://developers.onbolder.com/api/v2/shops/update-shop-branch
Needs `shops.write`.
`PUT /shops/{shop_id}/branches/{id}`
**Request body** (`application/json`, required)
- `name` (string, required)
Request body example:
```json
{
"name": "string"
}
```
**Responses**
- `200` — The branch
- `403` — Access denied
- `404` — Not a branch of this shop's seller
- `422` — Blank or used name, or unknown fields
---
# List themes
Source: https://developers.onbolder.com/api/v2/store/list-themes-flattened
Returns themes across the account.
`GET /themes`
**Responses**
- `200` — Themes list
---
# Create a membership plan
Source: https://developers.onbolder.com/api/v2/subscriptions/create-membership-plan
Needs `products.write`. Body: `name`, `description`, `state`, `max_seats`, `fees` (at least one; `payment_interval` from the subscription fee intervals), `permissions`, `accessible_product_ids`.
`POST /membership_plans`
**Responses**
- `201` — Plan created
- `422` — Validation error (no fees, invalid interval, invalid max\_seats)
---
# Add a membership seat
Source: https://developers.onbolder.com/api/v2/subscriptions/create-membership-seat
Gives a customer a seat. The customer must belong to the same shop and to the same company as the purchasing customer, can hold only one seat per purchase, and the purchase must not have reached `max_seats` (the purchasing customer counts as one). By default the call is made on behalf of the seller (`initiated_by: admin`) and notification emails are sent to the added customer and to the buyer. With `initiated_by: buyer` (the buyer managing their own seats) no emails are sent, and the plan must allow self-service seat management. Sends `subscription.seat_added`. Rel: `subscriptions:create_seat`.
`POST /subscription_purchases/{id}/seats`
**Request body** (`application/json`, required)
- `contact_id` (integer, required) — Customer to give the seat to
- `initiated_by` (string)
Request body example:
```json
{
"contact_id": 4521
}
```
**Responses**
- `201` — Seat created
- `403` — Token lacks \`orders.write\` on the purchase's account
- `404` — Purchase or customer not found, or the purchase is not of a membership plan
- `422` — \`contact\_id\` missing, seat limit reached, customer already has a seat, customer is not in the buyer's company, or self-service is not enabled for the plan (with \`initiated\_by\` buyer)
Response example, 201:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"subscription_purchase_id": 0,
"contact_id": 0,
"contact_email": "string",
"contact_name": "string",
"created_on": "2019-08-24"
}
```
---
# Create a subscription fee
Source: https://developers.onbolder.com/api/v2/subscriptions/create-subscription-fee
Attaches recurring-payment terms to a product (and optionally a specific variant).
`POST /subscription_fees`
**Request body** (`application/json`, required)
- `product_id` (integer, required)
- `variant_id` (integer)
- `payment_interval` (string, required)
- `renewal_fee` (integer) — In cents
- `setup_fee` (integer) — In cents
- `number_of_payments` (integer)
- `payment_day` (integer)
- `by_percentage` (boolean)
- `allow_non_recurring_payment` (boolean)
- `min_days_for_cancellation` (integer)
- `allow_changing_day` (boolean)
- `extend_on_reenable` (boolean)
- `on_first_renewal` (string)
Request body example:
```json
{
"product_id": 101,
"payment_interval": "1_month",
"renewal_fee": 2999
}
```
**Responses**
- `201` — Subscription fee created
- `422` — Validation error
Response example, 201:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"seller_id": 0,
"product_id": 0,
"variant_id": 0,
"payment_interval": "1_week",
"renewal_fee": 0,
"renewal_fee_price": 0,
"setup_fee": 0,
"number_of_payments": 0,
"payment_day": 0,
"by_percentage": true,
"allow_non_recurring_payment": true,
"min_days_for_cancellation": 0,
"allow_changing_day": true,
"extend_on_reenable": true,
"on_first_renewal": "string",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
```
---
# Delete a membership plan
Source: https://developers.onbolder.com/api/v2/subscriptions/delete-membership-plan
`DELETE /membership_plans/{id}`
**Responses**
- `204` — Deleted
- `422` — The plan has active or paused subscriptions
---
# Remove a membership seat
Source: https://developers.onbolder.com/api/v2/subscriptions/delete-membership-seat
Removes a customer's seat. `initiated_by` works as when adding a seat: `admin` (default) sends notification emails, `buyer` sends none and requires self-service to be enabled on the plan. Sends `subscription.seat_removed`. Rel: `subscriptions:delete_seat`.
`DELETE /subscription_purchases/{id}/seats/{seat_id}`
**Responses**
- `204` — Seat removed
- `403` — Token lacks \`orders.write\` on the purchase's account
- `404` — Purchase or seat not found
- `422` — Self-service is not enabled for the plan (\`initiated\_by: buyer\`)
---
# Delete a subscription fee
Source: https://developers.onbolder.com/api/v2/subscriptions/delete-subscription-fee
`DELETE /subscription_fees/{id}`
**Responses**
- `204` — Subscription fee deleted
- `422` — Cannot delete (e.g. in use by active purchases)
---
# Get a membership plan
Source: https://developers.onbolder.com/api/v2/subscriptions/get-membership-plan
`GET /membership_plans/{id}`
**Responses**
- `200` — Plan
- `404` — Not found
---
# Get a subscription fee
Source: https://developers.onbolder.com/api/v2/subscriptions/get-subscription-fee
`GET /subscription_fees/{id}`
**Responses**
- `200` — Subscription fee entity
- `404` — Not found
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"seller_id": 0,
"product_id": 0,
"variant_id": 0,
"payment_interval": "1_week",
"renewal_fee": 0,
"renewal_fee_price": 0,
"setup_fee": 0,
"number_of_payments": 0,
"payment_day": 0,
"by_percentage": true,
"allow_non_recurring_payment": true,
"min_days_for_cancellation": 0,
"allow_changing_day": true,
"extend_on_reenable": true,
"on_first_renewal": "string",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
```
---
# Get a subscription purchase
Source: https://developers.onbolder.com/api/v2/subscriptions/get-subscription-purchase
`GET /subscription_purchases/{id}`
**Responses**
- `200` — Subscription purchase entity
- `404` — Not found
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"shop_id": 0,
"contact_id": 0,
"company_id": 0,
"original_order_id": 0,
"product_id": 0,
"product_name": "string",
"state": "string",
"currency_code": "string",
"total_renewal_fee": 0,
"payment_interval": "string",
"total_payments": 0,
"remaining_payments": 0,
"failed_attempts": 0,
"last_payment_on": "2019-08-24",
"next_payment_on": "2019-08-24",
"start_date": "2019-08-24",
"end_date": "2019-08-24",
"cancel_at": "2019-08-24",
"subscription_fee_id": 0,
"renewal_discount": 0,
"discount_num_charges": 0,
"payments_to_skip": 0,
"paused_by": "string",
"paid_setup_fee": true,
"units": 0,
"max_seats": 0,
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
```
---
# List membership plans
Source: https://developers.onbolder.com/api/v2/subscriptions/list-membership-plans
Facade over membership plans and their fees (the Product STI is hidden). Needs `products.read`.
`GET /membership_plans`
**Responses**
- `200` — Plans: \`{ id, name, description, state, max\_seats, currency\_code, fees \[{ id, payment\_interval, setup\_fee, renewal\_fee, payment\_day, total\_payments, fixed\_term, term\_days, term\_months }\], accessible\_product\_ids, permissions { pause\_intervals, allows\_edit, allows\_reenable, manage\_seats }, active\_purchases\_count }\`. Money in cents; \`total\_payments\` -1 is unlimited. A \`fixed\_term\` fee (send \`fixed\_term: true\` and exactly one positive \`term\_days\` or \`term\_months\`) is paid once (setup fee) and its purchase ends at start + term (\`total\_payments\` becomes 1).
---
# List membership seats
Source: https://developers.onbolder.com/api/v2/subscriptions/list-membership-seats
Returns the additional customers sharing a membership subscription, with the purchase's `max_seats`. The purchasing customer is not listed: they occupy one seat implicitly. Rel: `subscriptions:seats`.
`GET /subscription_purchases/{id}/seats`
**Responses**
- `200` — Membership seats
- `403` — Token lacks \`subscriptions.read\` (or \`orders.read\`) on the purchase's account
- `404` — Not found, or the purchase is not of a membership plan
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"total_items": 0,
"max_seats": 0,
"_embedded": {
"items": [
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"subscription_purchase_id": 0,
"contact_id": 0,
"contact_email": "string",
"contact_name": "string",
"created_on": "2019-08-24"
}
]
}
}
```
---
# List subscription fees
Source: https://developers.onbolder.com/api/v2/subscriptions/list-subscription-fees
`GET /subscription_fees`
**Responses**
- `200` — Subscription fees list
Response example, 200:
```json
{
"_embedded": {
"items": [
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"seller_id": 0,
"product_id": 0,
"variant_id": 0,
"payment_interval": "1_week",
"renewal_fee": 0,
"renewal_fee_price": 0,
"setup_fee": 0,
"number_of_payments": 0,
"payment_day": 0,
"by_percentage": true,
"allow_non_recurring_payment": true,
"min_days_for_cancellation": 0,
"allow_changing_day": true,
"extend_on_reenable": true,
"on_first_renewal": "string",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
]
}
}
```
---
# List subscription purchases
Source: https://developers.onbolder.com/api/v2/subscriptions/list-subscription-purchases
Returns customer subscriptions.
`GET /subscription_purchases`
**Responses**
- `200` — Subscription purchases list. Each item also embeds \`contact\` {id,name,email}, \`plan\` {id,name,kind,max\_seats} (\`kind\`/\`max\_seats\` only for membership plans), \`fee\` {id,payment\_interval,renewal\_fee,setup\_fee,payment\_day,total\_payments,fixed\_term,term\_days,term\_months}, and has \`seats\` {used,max} (membership plans only) and \`original\_order\` {id,code}.
- `422` — \`shop\_id\` or \`seller\_ids\` is required
Response example, 200:
```json
{
"_embedded": {
"items": [
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"shop_id": 0,
"contact_id": 0,
"company_id": 0,
"original_order_id": 0,
"product_id": 0,
"product_name": "string",
"state": "string",
"currency_code": "string",
"total_renewal_fee": 0,
"payment_interval": "string",
"total_payments": 0,
"remaining_payments": 0,
"failed_attempts": 0,
"last_payment_on": "2019-08-24",
"next_payment_on": "2019-08-24",
"start_date": "2019-08-24",
"end_date": "2019-08-24",
"cancel_at": "2019-08-24",
"subscription_fee_id": 0,
"renewal_discount": 0,
"discount_num_charges": 0,
"payments_to_skip": 0,
"paused_by": "string",
"paid_setup_fee": true,
"units": 0,
"max_seats": 0,
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
]
}
}
```
---
# Record a manual renewal
Source: https://developers.onbolder.com/api/v2/subscriptions/renew-subscription-purchase
Advances `last_payment_on`/`next_payment_on` and sets `state` back to `active`. Decrements `remaining_payments` if the subscription has a fixed number of payments left.
`PUT /subscription_purchases/{id}/renew`
**Request body** (`application/json`)
- `payment_date` (string) — Defaults to today if omitted
Request body example:
```json
{
"payment_date": "2019-08-24"
}
```
**Responses**
- `200` — Subscription purchase renewed
- `422` — Invalid \`payment\_date\`, or validation error
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"shop_id": 0,
"contact_id": 0,
"company_id": 0,
"original_order_id": 0,
"product_id": 0,
"product_name": "string",
"state": "string",
"currency_code": "string",
"total_renewal_fee": 0,
"payment_interval": "string",
"total_payments": 0,
"remaining_payments": 0,
"failed_attempts": 0,
"last_payment_on": "2019-08-24",
"next_payment_on": "2019-08-24",
"start_date": "2019-08-24",
"end_date": "2019-08-24",
"cancel_at": "2019-08-24",
"subscription_fee_id": 0,
"renewal_discount": 0,
"discount_num_charges": 0,
"payments_to_skip": 0,
"paused_by": "string",
"paid_setup_fee": true,
"units": 0,
"max_seats": 0,
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
```
---
# Subscriptions KPI strip
Source: https://developers.onbolder.com/api/v2/subscriptions/subscription-purchase-stats
Returns `{ currency_code, period, active { count }, mrr { total, previous_total }, upcoming_renewals { count, amount }, failing { count } }` (cents). MRR is the sum of active purchases' renewal fee normalised per month; `previous_total` approximates the same at the start of the period (no history is kept). `upcoming_renewals` are active purchases due within the period; `failing` are active purchases with failed attempts.
`GET /subscription_purchases/stats`
**Responses**
- `200` — KPI numbers
---
# Subscription counts per state
Source: https://developers.onbolder.com/api/v2/subscriptions/subscription-purchase-status-counts
Returns `{ state: { all, active, paused, cancelled, completed } }`. Honours the list filters except `state`.
`GET /subscription_purchases/status_counts`
**Responses**
- `200` — Counts per state
---
# Update a membership plan
Source: https://developers.onbolder.com/api/v2/subscriptions/update-membership-plan
Same body as create. `fees` replaces the set: items with `id` are updated, items without are added, missing ones are removed (refused while they have active purchases).
`PUT /membership_plans/{id}`
**Responses**
- `200` — Plan updated
- `422` — Validation error
---
# Update a subscription fee
Source: https://developers.onbolder.com/api/v2/subscriptions/update-subscription-fee
`PUT /subscription_fees/{id}`
**Request body** (`application/json`, required)
- `payment_interval` (string)
- `renewal_fee` (integer)
- `setup_fee` (integer)
- `number_of_payments` (integer)
- `payment_day` (integer)
- `update_purchases_fees` (boolean) — Also apply the new fee terms to existing active purchases
- `notify_purchases` (boolean)
Request body example:
```json
{
"payment_interval": "1_week",
"renewal_fee": 0,
"setup_fee": 0,
"number_of_payments": 0,
"payment_day": 0,
"update_purchases_fees": true,
"notify_purchases": true
}
```
**Responses**
- `200` — Subscription fee updated
- `422` — Validation error
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"seller_id": 0,
"product_id": 0,
"variant_id": 0,
"payment_interval": "1_week",
"renewal_fee": 0,
"renewal_fee_price": 0,
"setup_fee": 0,
"number_of_payments": 0,
"payment_day": 0,
"by_percentage": true,
"allow_non_recurring_payment": true,
"min_days_for_cancellation": 0,
"allow_changing_day": true,
"extend_on_reenable": true,
"on_first_renewal": "string",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
```
---
# Update a subscription purchase
Source: https://developers.onbolder.com/api/v2/subscriptions/update-subscription-purchase
Only the fields listed below are writable.
`PUT /subscription_purchases/{id}`
**Request body** (`application/json`, required)
- `state` (string)
- `next_payment_on` (string)
- `total_renewal_fee` (integer)
- `remaining_payments` (integer)
- `subscription_fee_id` (integer)
- `cancel_at` (string)
- `payments_to_skip` (integer)
- `paused_by` (string)
- `last_pause_at` (string)
- `renewal_discount` (number)
- `discount_num_charges` (integer)
- `end_date` (string)
- `company_id` (integer | null) — Company of the same seller; null clears it.
Request body example:
```json
{
"state": "paused",
"paused_by": "admin"
}
```
**Responses**
- `200` — Subscription purchase updated
- `422` — Validation error (e.g. invalid \`state\`, or no updatable fields provided)
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"shop_id": 0,
"contact_id": 0,
"company_id": 0,
"original_order_id": 0,
"product_id": 0,
"product_name": "string",
"state": "string",
"currency_code": "string",
"total_renewal_fee": 0,
"payment_interval": "string",
"total_payments": 0,
"remaining_payments": 0,
"failed_attempts": 0,
"last_payment_on": "2019-08-24",
"next_payment_on": "2019-08-24",
"start_date": "2019-08-24",
"end_date": "2019-08-24",
"cancel_at": "2019-08-24",
"subscription_fee_id": 0,
"renewal_discount": 0,
"discount_num_charges": 0,
"payments_to_skip": 0,
"paused_by": "string",
"paid_setup_fee": true,
"units": 0,
"max_seats": 0,
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
```
---
# Create a dev theme
Source: https://developers.onbolder.com/api/v2/themes/create-shop-theme
Creates a new development theme by cloning the current production theme.
If a dev theme already exists it is returned unchanged.
`POST /shops/{id}/themes`
**Request body** (`application/json`)
- `title` (string) — Optional title for the new dev theme
Request body example:
```json
{
"title": "My Dev Theme"
}
```
**Responses**
- `201` — Dev theme created
- `401` — Unauthorized
- `403` — Access denied
Response example, 201:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": "string",
"linked": true,
"parent_id": "string",
"production": true,
"public": true,
"accessible": true,
"title": "string",
"settings": "string",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"_embedded": {
"templates": [
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": "string",
"file_name": "layout.html",
"path": "string",
"body": "string",
"is_section": true,
"schema": {},
"parent_commit": "string",
"updated_on": "2019-08-24T14:15:22Z",
"warnings": [
{
"severity": "error",
"code": "syntax",
"message": "string",
"line": 0
}
]
}
],
"assets": [
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": "string",
"file_name": "logo.png",
"content_type": "image/png",
"file_size": 0,
"digest": "string",
"url": "string",
"updated_on": "2019-08-24T14:15:22Z"
}
]
}
}
```
---
# Create a template
Source: https://developers.onbolder.com/api/v2/themes/create-template
Creates a new template file inside the theme. The `file_name` determines
the template type (e.g. `layout.html`, `product.html`, `custom.css`).
Unknown request fields (e.g. `content` instead of `body`) are rejected with `422`,
one error per field (`{"field": "$.content", "messages": ["is not a known field"]}`).
Response keys echoed back from a GET (`id`, `_links`, `_embedded`, `updated_on`,
`path`, `is_section`, `warnings`, `parent_commit`) are ignored.
`POST /themes/{theme_id}/templates`
**Request body** (`application/json`, required)
- `file_name` (string, required) — Template filename (determines type)
- `body` (string) — Template source code
Request body example:
```json
{
"file_name": "collection.html",
"body": "{{ collection.name }}
"
}
```
**Responses**
- `201` — Template created
- `401` — Unauthorized
- `403` — Access denied
- `422` — Validation error
Response example, 201:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": "string",
"file_name": "layout.html",
"path": "string",
"body": "string",
"is_section": true,
"schema": {},
"parent_commit": "string",
"updated_on": "2019-08-24T14:15:22Z",
"warnings": [
{
"severity": "error",
"code": "syntax",
"message": "string",
"line": 0
}
]
}
```
---
# Upload a theme asset
Source: https://developers.onbolder.com/api/v2/themes/create-theme-asset
Uploads a new file asset (image, font, stylesheet, etc.) to the theme.
Send the file as a multipart/form-data upload.
`POST /themes/{theme_id}/assets`
**Request body** (`multipart/form-data`, required)
- `file` (string, required) — File to upload
Request body example:
```json
{
"file": "string"
}
```
**Responses**
- `201` — Asset uploaded
- `401` — Unauthorized
- `403` — Access denied
- `422` — Validation error
Response example, 201:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": "string",
"file_name": "logo.png",
"content_type": "image/png",
"file_size": 0,
"digest": "string",
"url": "string",
"updated_on": "2019-08-24T14:15:22Z"
}
```
---
# Delete a template
Source: https://developers.onbolder.com/api/v2/themes/delete-template
Deletes a non-required template from the theme. Required templates
(e.g. `layout.html`) cannot be deleted — the `store:delete_template`
link is absent for those.
`DELETE /themes/{theme_id}/templates/{id}`
**Responses**
- `204` — Template deleted
- `403` — Cannot delete a required template
- `404` — Template not found
---
# Delete a dev theme
Source: https://developers.onbolder.com/api/v2/themes/delete-theme
Deletes the dev theme. The production theme cannot be deleted.
The `store:delete_theme` link is only present on dev themes.
`DELETE /themes/{id}`
**Responses**
- `204` — Theme deleted
- `403` — Cannot delete the production theme
- `404` — Theme not found
---
# Delete a theme asset
Source: https://developers.onbolder.com/api/v2/themes/delete-theme-asset
Permanently deletes an asset from the theme. The `store:delete_theme_asset`
link is present on each asset entity.
`DELETE /themes/{theme_id}/assets/{id}`
**Responses**
- `204` — Asset deleted
- `401` — Unauthorized
- `403` — Access denied
- `404` — Asset not found
---
# Get a template
Source: https://developers.onbolder.com/api/v2/themes/get-template
Returns a single template including its full body source.
`GET /themes/{theme_id}/templates/{id}`
**Responses**
- `200` — Template entity
- `401` — Unauthorized
- `403` — Access denied
- `404` — Template not found
Response example, 200:
```json
{
"id": "t1",
"file_name": "layout.html",
"name": "layout",
"content_type": "text/html",
"body": "{{ content }}",
"is_section": false,
"updated_on": "2024-11-15T09:30:00Z",
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/themes/abc123/templates/t1"
},
"store:update_template": {
"href": "https://api.onbolder.com/v2/themes/abc123/templates/t1",
"method": "put"
},
"store:delete_template": {
"href": "https://api.onbolder.com/v2/themes/abc123/templates/t1",
"method": "delete"
}
}
}
```
---
# Get a theme
Source: https://developers.onbolder.com/api/v2/themes/get-theme
Returns the full theme entity including all embedded templates and assets.
The `production` field indicates whether this is the live theme.
For the public/production theme, `store:create_dev_theme` is available.
For a dev theme, `store:publish_theme` and `store:delete_theme` are available.
`GET /themes/{id}`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/themes/abc123"
```
**Responses**
- `200` — Theme entity with embedded templates and assets
- `401` — Unauthorized
- `403` — Access denied
- `404` — Theme not found
Response example, 200:
```json
{
"id": "abc123",
"title": "My Theme",
"production": true,
"public": true,
"accessible": true,
"linked": false,
"parent_id": null,
"settings": "{\"bg_color\":\"#fff\"}",
"created_on": "2023-06-01T10:00:00Z",
"updated_on": "2024-11-15T09:30:00Z",
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/themes/abc123"
},
"store:update_theme": {
"href": "https://api.onbolder.com/v2/themes/abc123",
"method": "put"
},
"store:create_template": {
"href": "https://api.onbolder.com/v2/themes/abc123/templates",
"method": "post"
},
"store:create_theme_asset": {
"href": "https://api.onbolder.com/v2/themes/abc123/assets",
"method": "post"
},
"store:theme_preview": {
"href": "https://myshop.onbolder.com",
"type": "text/html"
},
"store:create_dev_theme": {
"href": "https://api.onbolder.com/v2/shops/1/themes",
"method": "post"
}
},
"_embedded": {
"templates": [
{
"id": "t1",
"file_name": "layout.html",
"name": "layout",
"content_type": "text/html",
"body": "...",
"is_section": false,
"updated_on": "2024-11-15T09:30:00Z"
}
],
"assets": [
{
"id": "a1",
"file_name": "logo.png",
"content_type": "image/png",
"file_size": 12400,
"url": "https://cdn.onbolder.com/themes/abc123/logo.png"
}
]
}
}
```
---
# List themes for a shop
Source: https://developers.onbolder.com/api/v2/themes/list-shop-themes
Returns the theme navigation document for the shop — links to the current
production theme, the dev theme (if any), and the action to create a new
dev theme. Requires `store.read` scope and ownership of the shop.
`GET /shops/{id}/themes`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/shops/1/themes"
```
**Responses**
- `200` — Themes navigation document
- `401` — Unauthorized
- `403` — Access denied
Response example, 200:
```json
{
"_class": [
"results",
"themes"
],
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/shops/1/themes"
},
"store:theme": {
"href": "https://api.onbolder.com/v2/themes/abc123",
"title": "Current theme"
},
"store:create_dev_theme": {
"href": "https://api.onbolder.com/v2/shops/1/themes",
"method": "post"
},
"store:dev_theme": {
"href": "https://api.onbolder.com/v2/themes/def456",
"title": "Dev theme"
}
}
}
```
---
# Publish a dev theme
Source: https://developers.onbolder.com/api/v2/themes/publish-theme
Promotes the dev theme to production. The current production theme is
archived and the dev theme becomes the new live theme.
Only available when `store:publish_theme` is present in the dev theme's links.
`PUT /themes/{id}/publication`
**Responses**
- `200` — Theme published — returns the now-production theme
- `401` — Unauthorized
- `403` — Access denied
- `404` — Theme not found
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": "string",
"linked": true,
"parent_id": "string",
"production": true,
"public": true,
"accessible": true,
"title": "string",
"settings": "string",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"_embedded": {
"templates": [
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": "string",
"file_name": "layout.html",
"path": "string",
"body": "string",
"is_section": true,
"schema": {},
"parent_commit": "string",
"updated_on": "2019-08-24T14:15:22Z",
"warnings": [
{
"severity": "error",
"code": "syntax",
"message": "string",
"line": 0
}
]
}
],
"assets": [
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": "string",
"file_name": "logo.png",
"content_type": "image/png",
"file_size": 0,
"digest": "string",
"url": "string",
"updated_on": "2019-08-24T14:15:22Z"
}
]
}
}
```
---
# Update a template
Source: https://developers.onbolder.com/api/v2/themes/update-template
Replace the body of an existing template. Unknown request fields are rejected
with `422` (`{"field": "$.content", "messages": ["is not a known field"]}`);
echoed GET response keys are ignored, as on create.
`PUT /themes/{theme_id}/templates/{id}`
**Request body** (`application/json`, required)
- `body` (string) — New template source code
Request body example:
```json
{
"body": "{{ shop.name }}{{ content }}"
}
```
**Responses**
- `200` — Template updated
- `401` — Unauthorized
- `403` — Access denied
- `404` — Template not found
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": "string",
"file_name": "layout.html",
"path": "string",
"body": "string",
"is_section": true,
"schema": {},
"parent_commit": "string",
"updated_on": "2019-08-24T14:15:22Z",
"warnings": [
{
"severity": "error",
"code": "syntax",
"message": "string",
"line": 0
}
]
}
```
---
# Update a theme
Source: https://developers.onbolder.com/api/v2/themes/update-theme
Update theme settings or title.
`PUT /themes/{id}`
**cURL**
```bash
curl -s -X PUT \
-H "Authorization: Bearer $BOLDER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"My Updated Theme"}' \
"https://api.onbolder.com/v2/themes/abc123"
```
**Request body** (`application/json`, required)
- `title` (string)
- `settings_json` (string) — Raw JSON string of theme settings. Note: the request field is \`settings\_json\`, not \`settings\` (the response field, returned as \`settings\`, is named differently from the request field).
- `parent_id` (string) — Reassigns the dev theme's parent (production) theme.
- `last_updated_on` (string) — Optimistic-locking timestamp; rejects the update if the theme changed since.
Request body example:
```json
{
"title": "Updated Theme Title",
"settings_json": "{\"bg_color\":\"#333\"}"
}
```
**Responses**
- `200` — Theme updated
- `401` — Unauthorized
- `403` — Access denied
- `404` — Theme not found
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": "string",
"linked": true,
"parent_id": "string",
"production": true,
"public": true,
"accessible": true,
"title": "string",
"settings": "string",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z",
"_embedded": {
"templates": [
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": "string",
"file_name": "layout.html",
"path": "string",
"body": "string",
"is_section": true,
"schema": {},
"parent_commit": "string",
"updated_on": "2019-08-24T14:15:22Z",
"warnings": [
{
"severity": "error",
"code": "syntax",
"message": "string",
"line": 0
}
]
}
],
"assets": [
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": "string",
"file_name": "logo.png",
"content_type": "image/png",
"file_size": 0,
"digest": "string",
"url": "string",
"updated_on": "2019-08-24T14:15:22Z"
}
]
}
}
```
---
# Update theme settings
Source: https://developers.onbolder.com/api/v2/themes/update-theme-settings
Replaces the theme's global settings.
`PUT /themes/{theme_id}/templates/settings`
**Request body** (`application/json`, required)
- `settings_json` (string, required) — JSON string containing the theme settings blob
Request body example:
```json
{
"settings_json": "{\"color\":\"#ff0000\",\"font\":\"Helvetica\"}"
}
```
**Responses**
- `200` — Settings updated; returns the settings template stub
- `401` — Unauthorized
- `403` — Access denied
- `422` — Validation error
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": "string",
"file_name": "layout.html",
"path": "string",
"body": "string",
"is_section": true,
"schema": {},
"parent_commit": "string",
"updated_on": "2019-08-24T14:15:22Z",
"warnings": [
{
"severity": "error",
"code": "syntax",
"message": "string",
"line": 0
}
]
}
```
---
# Batch update variants
Source: https://developers.onbolder.com/api/v2/variants/batch-update-variants
Updates multiple **existing** variants in a single request (does not create new variants). Each entry in `batch` must include `id` to match an existing variant; a batch may mix variants of **different products**, as long as they all belong to the same seller. Up to 50 entries per request, with no repeated `id`s.
`id` is either the numeric variant ID or the SKU — the mode is decided by the **first** entry (numeric → all entries are matched by ID, otherwise all by SKU), so don't mix both in the same batch. Entries that don't match a variant of the seller (unknown ID/SKU, or a variant owned by another seller) are silently skipped: the response is still `201` and they simply don't appear in `valid_items`/`invalid_items`.
Stock fields use `available_units`/`total_units` (`units` is deprecated but still accepted).
`POST /variants/updaters`
**cURL**
```bash
curl -s -X POST -H "Authorization: Bearer $BOLDER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"batch":[{"id":"201","regular_price":7999},{"id":"305","regular_price":8999}]}' \
"https://api.onbolder.com/v2/variants/updaters?shop_id=1"
```
**Request body** (`application/json`, required)
- `batch` (object[], required)
Request body example:
```json
{
"batch": [
{
"id": "201",
"title": "Small",
"regular_price": 7999
},
{
"id": "305",
"title": "Large",
"regular_price": 8999
}
]
}
```
**Responses**
- `201` — Batch processed. Check \`invalid\_items\` — a \`201\` does not mean every entry was applied.
- `422` — \`batch\` is empty, exceeds 50 entries or repeats an \`id\`; or \`shop\_id\` was omitted and could not be resolved from the token.
Response example, 201:
```json
{
"_embedded": {
"valid_items": [
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"name": "string"
}
],
"invalid_items": [
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"name": "string"
}
]
}
}
```
---
# Create a variant
Source: https://developers.onbolder.com/api/v2/variants/create-variant
Creates a new variant for a product.
`POST /products/{product_id}/variants`
**Request body** (`application/json`, required)
- `id` (integer) — Pre-assign a specific variant ID (rarely used; usually omitted).
- `position` (integer) — Ordering position within the product (lower comes first). On a \`chained\_variants\` product, omitting it defaults to the current highest position instead of 0.
- `option1` (string)
- `option2` (string)
- `option3` (string)
- `option_values` (object) — Custom variant option values, keyed by \`option1\`/\`option2\`/\`option3\`.
- `option_asset_ids` (object) — Asset IDs to associate with each option value.
- `product_asset_ids` (integer[]) — Product image asset IDs to associate with this variant.
- `sku` (string)
- `barcode_number` (string)
- `regular_price` (integer) — In cents. Required if the product has per-variant prices.
- `sale_price` (integer) — In cents
- `cost` (integer)
- `weight_in_grams` (integer)
- `unit_quantity` (number)
- `color` (string)
- `available_if_no_stock` (boolean)
- `online_stock` (integer)
- `status` (string)
- `stocks` (object[])
Request body example:
```json
{
"option1": "Large",
"sku": "WH-001-L",
"regular_price": 8999,
"stocks": [
{
"location_id": 1,
"units": 15
}
]
}
```
**Responses**
- `201` — Variant created
Response example, 201:
```json
{
"id": 202,
"product_id": 101,
"name": "Large",
"sku": "WH-001-L",
"regular_price": 8999,
"price": 8999
}
```
---
# Delete a variant
Source: https://developers.onbolder.com/api/v2/variants/delete-variant
`DELETE /products/{product_id}/variants/{id}`
**Responses**
- `204` — Variant deleted
---
# Get a variant
Source: https://developers.onbolder.com/api/v2/variants/get-variant
`GET /products/{product_id}/variants/{id}`
**Responses**
- `200` — Variant details
- `404` — Variant not found
Response example, 200:
```json
{
"id": 201,
"product_id": 101,
"name": "Small",
"sku": "WH-001-S",
"regular_price": 8999,
"price": 8999,
"online_stock": 10
}
```
---
# List variants
Source: https://developers.onbolder.com/api/v2/variants/list-variants
Returns variants for a product.
`GET /products/{product_id}/variants`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/products/101/variants"
```
**Responses**
- `200` — Paginated variants list
Response example, 200:
```json
{
"_class": [
"results",
"variants"
],
"total_items": 3,
"per_page": 20,
"page": 1,
"_embedded": {
"items": [
{
"id": 201,
"product_id": 101,
"name": "Small",
"sku": "WH-001-S",
"option1": "Small",
"currency_code": "USD",
"regular_price": 8999,
"price": 8999,
"online_stock": 10
}
]
}
}
```
---
# Update a variant
Source: https://developers.onbolder.com/api/v2/variants/update-variant
Variant names are set via `option1`/`option2`/`option3`. On update, `stocks` accepts `available_units`/`total_units` in addition to `location_id`.
`PUT /products/{product_id}/variants/{id}`
**Request body** (`application/json`, required)
- `position` (integer) — Ordering position within the product (lower comes first).
- `sku` (string)
- `barcode_number` (string)
- `option1` (string)
- `option2` (string)
- `option3` (string)
- `option_values` (object) — Custom variant option values, keyed by \`option1\`/\`option2\`/\`option3\`.
- `option_asset_ids` (object) — Asset IDs to associate with each option value.
- `product_asset_ids` (integer[]) — Product image asset IDs to associate with this variant.
- `color` (string)
- `regular_price` (integer)
- `sale_price` (integer)
- `cost` (integer) — Cost in cents (for margin reporting, not shown to buyers).
- `status` (string)
- `available_if_no_stock` (boolean)
- `weight_in_grams` (integer)
- `unit_quantity` (number)
- `online_stock` (integer)
- `stocks` (object[])
Request body example:
```json
{
"regular_price": 7999
}
```
**Responses**
- `200` — Variant updated
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"product_id": 0,
"name": "string",
"position": 0,
"status": "visible",
"sku": "string",
"barcode_number": "string",
"option1": "string",
"option2": "string",
"option3": "string",
"currency_code": "string",
"regular_price": 0,
"sale_price": 0,
"price": 0,
"online_stock": 0,
"all_stock": 0,
"available_if_no_stock": true,
"weight_in_grams": 0,
"unit_quantity": 0,
"color": "string",
"cost": 0,
"profit": 0,
"stocks": [
{
"location_id": 0,
"available_units": 0,
"total_units": 0
}
]
}
```
---
# Create a volume discount
Source: https://developers.onbolder.com/api/v2/volume-discounts/create-volume-discount
Creates a new volume discount for a shop. Volume discounts automatically
apply tiered pricing when a customer orders above a unit threshold.
`shop_id` is required in the body.
`POST /volume_discounts`
**Request body** (`application/json`, required)
- `shop_id` (integer, required) — ID of the shop to create the volume discount in
- `name` (string, required)
- `discount_type` (string) — Discount type.
- `cumulative_mode` (string) — Omit for non-cumulative
- `apply_on_tier_start_only` (boolean)
- `tiers` (object[], required)
Request body example:
```json
{
"shop_id": 1,
"name": "Buy 5+ get 10% off",
"discount_type": "percentage",
"tiers": [
{
"from": 5,
"value": 10
},
{
"from": 10,
"value": 15
},
{
"from": 20,
"value": 20
}
]
}
```
**Responses**
- `201` — Volume discount created
- `422` — Validation error
Response example, 201:
```json
{
"id": 30,
"name": "Buy 5+ get 10% off",
"discount_type": "percentage",
"tiers": {
"5": 10,
"10": 15,
"20": 20
},
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/volume_discounts/30"
}
}
}
```
---
# Delete a volume discount
Source: https://developers.onbolder.com/api/v2/volume-discounts/delete-volume-discount
Soft-deletes a volume discount.
`DELETE /volume_discounts/{id}`
**Responses**
- `204` — Volume discount deleted
---
# Get a volume discount
Source: https://developers.onbolder.com/api/v2/volume-discounts/get-volume-discount
Returns a single volume discount by ID.
`GET /volume_discounts/{id}`
**Responses**
- `200` — Volume discount details
- `404` — Volume discount not found
Response example, 200:
```json
{
"id": 30,
"name": "Buy 5+ get 10% off",
"discount_type": "percentage",
"tiers": {
"5": 10
},
"cumulative": false,
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/volume_discounts/30"
}
}
}
```
---
# List volume discounts
Source: https://developers.onbolder.com/api/v2/volume-discounts/list-volume-discounts
Returns volume discount rules. Volume discounts automatically
apply tiered pricing when a customer orders above a quantity threshold.
`GET /volume_discounts`
**Responses**
- `200` — Paginated volume discounts list
- `403` — Access denied
- `422` — \`shop\_id\` required
Response example, 200:
```json
{
"_class": [
"results",
"volume_discounts"
],
"total_items": 1,
"per_page": 20,
"page": 1,
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/volume_discounts?shop_id=1"
}
},
"_embedded": {
"items": [
{
"id": 30,
"name": "Buy 5+ get 10% off",
"discount_type": "percentage",
"tiers": {
"5": 10,
"10": 15
},
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/volume_discounts/30"
}
}
}
]
}
}
```
---
# Update a volume discount
Source: https://developers.onbolder.com/api/v2/volume-discounts/update-volume-discount
Updates an existing volume discount.
`PUT /volume_discounts/{id}`
**Request body** (`application/json`, required)
- `shop_id` (integer, required) — ID of the shop to create the volume discount in
- `name` (string, required)
- `discount_type` (string) — Discount type.
- `cumulative_mode` (string) — Omit for non-cumulative
- `apply_on_tier_start_only` (boolean)
- `tiers` (object[], required)
Request body example:
```json
{
"tiers": [
{
"from": 5,
"value": 15
}
]
}
```
**Responses**
- `200` — Volume discount updated
- `404` — Volume discount not found
Response example, 200:
```json
{
"id": 30,
"tiers": {
"5": 15
},
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/volume_discounts/30"
}
}
}
```
---
# Add a balance or payment transfer
Source: https://developers.onbolder.com/api/v2/wallets/add-wallet-transfer
Creates a completed incoming transfer by default — used both for adding points balance and for recording a credit-wallet payment. Pass `state: pending` for a credit payment awaiting confirmation.
`POST /wallets/{wallet_id}/transfers`
**Request body** (`application/json`, required)
- `amount` (integer, required) — Positive integer amount to add.
- `description` (string, required)
- `reference_code` (string)
- `payment_method` (string)
- `shop_id` (string)
- `state` (string)
Request body example:
```json
{
"amount": 500,
"description": "Signup bonus",
"reference_code": "signup-3001"
}
```
**Responses**
- `201` — Transfer created
- `422` — Invalid transfer (e.g. insufficient balance for an outgoing amount)
Response example, 201:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"direction": "incoming",
"state": "pending",
"amount": 0,
"original_amount": 0,
"description": "string",
"reference_code": "string",
"payment_method": "string",
"shop_id": "string",
"wallet_id": 0,
"balance_expires_at": "2019-08-24T14:15:22Z",
"created_on": "2019-08-24T14:15:22Z"
}
```
---
# Create (or find) a wallet for a contact
Source: https://developers.onbolder.com/api/v2/wallets/create-wallet
Idempotent per contact + program: if a wallet already exists for this contact and program, the existing wallet is returned rather than a duplicate created.
`POST /wallets`
**Request body** (`application/json`, required)
- `contact_id` (integer, required)
- `program_id` (integer) — Defaults to the seller's active program of the given \`type\`.
- `type` (string)
Request body example:
```json
{
"contact_id": 3001
}
```
**Responses**
- `201` — Wallet created (or found)
Response example, 201:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"type": "points",
"name": "string",
"state": "active",
"balance": 0,
"contact_id": 0,
"program_id": 0,
"consented": true,
"limit_amount": 0,
"overdue_limit_amount": 0,
"loan_term": "string",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
```
---
# Get a wallet
Source: https://developers.onbolder.com/api/v2/wallets/get-wallet
`GET /wallets/{id}`
**Responses**
- `200` — The wallet
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"type": "points",
"name": "string",
"state": "active",
"balance": 0,
"contact_id": 0,
"program_id": 0,
"consented": true,
"limit_amount": 0,
"overdue_limit_amount": 0,
"loan_term": "string",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
```
---
# Get a wallet program
Source: https://developers.onbolder.com/api/v2/wallets/get-wallet-program
`GET /wallet_programs/{id}`
**Responses**
- `200` — The wallet program
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"type": "points",
"name": "string",
"status": "active",
"points_name": "string",
"points_min": 0,
"points_duration": "string",
"points_divider": 0,
"points_divider_scope": "cart_total",
"credits_renewal": "manual",
"credits_reserve": true,
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
```
---
# List wallet programs
Source: https://developers.onbolder.com/api/v2/wallets/list-wallet-programs
A seller has at most one active `PointsProgram` and one active
`CreditProgram`. Each program automatically covers every shop under
the seller.
`GET /wallet_programs`
**Responses**
- `200` — Paginated list of wallet programs
Response example, 200:
```json
{
"_class": [
"results",
"walletPrograms"
],
"total_items": 1,
"per_page": 30,
"page": 1,
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/wallet_programs"
}
},
"_embedded": {
"items": [
{
"id": 4,
"type": "points",
"name": "Loyalty",
"status": "active",
"points_name": "points",
"points_min": 500,
"points_duration": "1_year",
"points_divider": 100,
"points_divider_scope": "cart_total",
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/wallet_programs/4"
}
}
}
]
}
}
```
---
# List transfers for a wallet
Source: https://developers.onbolder.com/api/v2/wallets/list-wallet-transfers
`GET /wallets/{wallet_id}/transfers`
**Responses**
- `200` — Paginated list of transfers
Response example, 200:
```json
{
"total_items": 42,
"per_page": 20,
"page": 1,
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"_class": [
"results",
"walletTransfers"
],
"_embedded": {
"items": [
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"direction": "incoming",
"state": "pending",
"amount": 0,
"original_amount": 0,
"description": "string",
"reference_code": "string",
"payment_method": "string",
"shop_id": "string",
"wallet_id": 0,
"balance_expires_at": "2019-08-24T14:15:22Z",
"created_on": "2019-08-24T14:15:22Z"
}
]
}
}
```
---
# List wallets
Source: https://developers.onbolder.com/api/v2/wallets/list-wallets
`GET /wallets`
**Responses**
- `200` — Paginated list of wallets
Response example, 200:
```json
{
"_class": [
"results",
"wallets"
],
"total_items": 1,
"per_page": 30,
"page": 1,
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/wallets"
}
},
"_embedded": {
"items": [
{
"id": 55,
"type": "points",
"name": "Personal wallet",
"state": "active",
"balance": 500,
"contact_id": 3001,
"program_id": 4,
"consented": true,
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/wallets/55"
},
"wallets:transfers": {
"href": "https://api.onbolder.com/v2/wallets/55/transfers"
}
}
}
]
}
}
```
---
# Register points earned from a completed purchase
Source: https://developers.onbolder.com/api/v2/wallets/register-wallet-purchase
Points wallets only.
`POST /wallets/{id}/register_purchase`
**Request body** (`application/json`, required)
- `reference_code` (string, required)
- `order_amount` (integer, required)
- `description` (string, required)
- `campaign_id` (integer)
- `shop_id` (string)
Request body example:
```json
{
"reference_code": "order-9001",
"order_amount": 25000,
"description": "Points earned on order #9001"
}
```
**Responses**
- `200` — Points registered
- `422` — A transfer for this reference\_code already exists, or the program's points divider is misconfigured
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"direction": "incoming",
"state": "pending",
"amount": 0,
"original_amount": 0,
"description": "string",
"reference_code": "string",
"payment_method": "string",
"shop_id": "string",
"wallet_id": 0,
"balance_expires_at": "2019-08-24T14:15:22Z",
"created_on": "2019-08-24T14:15:22Z"
}
```
---
# Reverse points earned from a purchase (e.g. on order cancellation)
Source: https://developers.onbolder.com/api/v2/wallets/unregister-wallet-purchase
Points wallets only. Reverses the earning transfer outright when the full amount is still available; otherwise drains whatever remains so the points can't be spent again (a partial reversal).
`POST /wallets/{id}/unregister_purchase`
**Request body** (`application/json`, required)
- `reference_code` (string, required)
- `order_amount` (integer, required)
Request body example:
```json
{
"reference_code": "string",
"order_amount": 0
}
```
**Responses**
- `200` — Points reversed (fully or partially)
- `422` — Unable to complete this action
---
# Update a wallet program's settings
Source: https://developers.onbolder.com/api/v2/wallets/update-wallet-program
Only the fields matching the program's own `type` are applied — `points_*` fields for a `PointsProgram`, `credits_*` for a `CreditProgram`.
`PUT /wallet_programs/{id}`
**Request body** (`application/json`)
- `name` (string)
- `points_name` (string)
- `points_min` (integer)
- `points_duration` (string)
- `points_divider` (integer)
- `points_divider_scope` (string)
- `credits_renewal` (string)
- `credits_reserve` (boolean)
Request body example:
```json
{
"points_name": "coins",
"points_min": 1000
}
```
**Responses**
- `200` — Updated wallet program
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"type": "points",
"name": "string",
"status": "active",
"points_name": "string",
"points_min": 0,
"points_duration": "string",
"points_divider": 0,
"points_divider_scope": "cart_total",
"credits_renewal": "manual",
"credits_reserve": true,
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
```
---
# Create a webhook for a seller
Source: https://developers.onbolder.com/api/v2/webhooks/create-seller-webhook
`POST /sellers/{seller_id}/webhooks`
**cURL**
```bash
curl -s -X POST \
-H "Authorization: Bearer $BOLDER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"topic":"order.created","url":"https://myapp.com/webhook"}' \
"https://api.onbolder.com/v2/sellers/5/webhooks"
```
**Request body** (`application/json`, required)
- `topic` (string, required) — Supported topics: \`order.created\`, \`order.updated\`, \`order.updated.closed\`, \`order.updated.shipped\`, \`customer.created\`, \`customer.updated\`, \`customer.opted\_in\`, \`customer.opted\_out\`, \`product.created\`, \`product.updated\`, \`product.deleted\`. The plural spelling (\`orders.created\`) is accepted too; either way the subscription receives the singular one. See the Webhooks section for the topic spelling and what each customer event means.
- `url` (string, required)
- `notify_origin` (boolean) — If false (the default), events caused by the application that created the subscription are not delivered to it.
- `auth` (object) — Credentials sent as HTTP Basic auth with every request to \`url\`, the activation included. Deliveries are not signed; use this to authenticate them.
Request body example:
```json
{
"topic": "order.created",
"url": "https://myapp.com/webhook"
}
```
**Responses**
- `201` — Webhook created
- `403` — Token lacks the scope required to subscribe to this topic
Response example, 201:
```json
{
"id": 101,
"channel_id": 5,
"topic": "order.created",
"topic_style": "singular",
"url": "https://myapp.com/webhook",
"status": "pending",
"notify_origin": false,
"_links": {
"self": {
"href": "https://api.onbolder.com/v2/webhooks/101"
}
}
}
```
---
# Create a webhook
Source: https://developers.onbolder.com/api/v2/webhooks/create-webhook-flattened
`shop_id` is required when creating via this flat endpoint. Alternatively, use `POST /sellers/{seller_id}/webhooks`.
`POST /webhooks`
**Request body** (`application/json`, required)
- `topic` (string, required) — Supported topics: \`order.created\`, \`order.updated\`, \`order.updated.closed\`, \`order.updated.shipped\`, \`customer.created\`, \`customer.updated\`, \`customer.opted\_in\`, \`customer.opted\_out\`, \`product.created\`, \`product.updated\`, \`product.deleted\`. The plural spelling (\`orders.created\`) is accepted too; either way the subscription receives the singular one. See the Webhooks section for the topic spelling and what each customer event means.
- `url` (string, required)
- `notify_origin` (boolean) — If false (the default), events caused by the application that created the subscription are not delivered to it.
- `auth` (object) — Credentials sent as HTTP Basic auth with every request to \`url\`, the activation included. Deliveries are not signed; use this to authenticate them.
Request body example:
```json
{
"topic": "order.created",
"url": "https://myapp.com/webhook"
}
```
**Responses**
- `201` — Webhook created
- `403` — Token lacks the scope required to subscribe to this topic
Response example, 201:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"channel_id": 0,
"topic": "order.created",
"topic_style": "singular",
"url": "https://myapp.com/events",
"status": "active",
"auth": {},
"notify_origin": true,
"app_id": 0,
"error_count": 0,
"last_error": "string",
"last_error_on": "2019-08-24T14:15:22Z",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
```
---
# Delete a webhook
Source: https://developers.onbolder.com/api/v2/webhooks/delete-webhook-flattened
`DELETE /webhooks/{id}`
**Responses**
- `204` — Webhook deleted
---
# Get a webhook
Source: https://developers.onbolder.com/api/v2/webhooks/get-webhook-flattened
`GET /webhooks/{id}`
**Responses**
- `200` — Webhook details
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"channel_id": 0,
"topic": "order.created",
"topic_style": "singular",
"url": "https://myapp.com/events",
"status": "active",
"auth": {},
"notify_origin": true,
"app_id": 0,
"error_count": 0,
"last_error": "string",
"last_error_on": "2019-08-24T14:15:22Z",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
```
---
# List webhooks for a seller
Source: https://developers.onbolder.com/api/v2/webhooks/list-seller-webhooks
Seller-nested equivalent of `GET /webhooks?seller_id=X`.
`GET /sellers/{seller_id}/webhooks`
**cURL**
```bash
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/sellers/5/webhooks"
```
**Responses**
- `200` — Webhooks list
Response example, 200:
```json
{
"total_items": 42,
"per_page": 20,
"page": 1,
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"_class": [
"results",
"hubSubscriptions"
],
"_embedded": {
"subscriptions": [
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"channel_id": 0,
"topic": "order.created",
"topic_style": "singular",
"url": "https://myapp.com/events",
"status": "active",
"auth": {},
"notify_origin": true,
"app_id": 0,
"error_count": 0,
"last_error": "string",
"last_error_on": "2019-08-24T14:15:22Z",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
]
}
}
```
---
# List webhooks
Source: https://developers.onbolder.com/api/v2/webhooks/list-webhooks-flattened
Returns webhooks across the account.
Optionally filter by `shop_id` or `seller_id` query parameter.
### Delivery payload guarantees
Every order-related event payload (`order.created`, `order.updated`,
`order.updated.*`) is built from the same serializer and always includes
both `id` (numeric order id) and `code` (the order's public-facing
reference/permalink) — neither is ever omitted.
### Retries and automatic disabling
Two independent counters are involved — don't confuse them:
- **Per-event retries (up to 10, over ~24h)**: each individual event
delivery (e.g. one order's `order.updated`) is retried by the
delivery worker up to 10 times with growing backoff, spread across
roughly a day (a few minutes after the 1st failure, up to ~24h
after it by the 10th and final attempt) — this gives a
temporarily-down endpoint a full day to recover before this
specific event delivery is given up on.
- **Subscription-wide `error_count` (disables at 100)**: every
failed attempt across *all* events (including each of the 10
per-event retries above) increments `error_count` on the
subscription. Reaching 100 sets `status` to `failed` and stops all
further delivery — in practice that means roughly 10 different
order events each exhausting their full retry chain with no
successful delivery in between, since **any single successful
delivery resets `error_count` to 0**. There's no fixed time bound
on this: it depends on how many order events fire while your
endpoint is down.
Delivery outcomes are tracked on the subscription independent of the
worker retries:
- A `410 Gone` response from your endpoint immediately sets the
subscription's `status` to `disabled` — no further attempts are made,
regardless of `error_count`.
- Any other non-2xx response (or timeout) increments `error_count`.
Timeouts and a handful of transient upstream errors (502/512/523) don't
count toward the disable threshold; other non-2xx responses do.
- A `failed` (or `disabled`) subscription can be re-enabled at any time via
`PUT /webhooks/{id}/reactivate`, which resets the error count.
`GET /webhooks`
**cURL**
```bash
# List all webhooks for account
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/webhooks"
# Filter by shop
curl -s -H "Authorization: Bearer $BOLDER_TOKEN" \
"https://api.onbolder.com/v2/webhooks?shop_id=1"
```
**Responses**
- `200` — Webhooks list
Response example, 200:
```json
{
"total_items": 42,
"per_page": 20,
"page": 1,
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"_class": [
"results",
"hubSubscriptions"
],
"_embedded": {
"subscriptions": [
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"channel_id": 0,
"topic": "order.created",
"topic_style": "singular",
"url": "https://myapp.com/events",
"status": "active",
"auth": {},
"notify_origin": true,
"app_id": 0,
"error_count": 0,
"last_error": "string",
"last_error_on": "2019-08-24T14:15:22Z",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
]
}
}
```
---
# Reactivate a failed webhook
Source: https://developers.onbolder.com/api/v2/webhooks/reactivate-webhook-flattened
`PUT /webhooks/{id}/reactivate`
**Responses**
- `200` — Webhook reactivated
Response example, 200:
```json
{
"_links": {
"property1": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
},
"property2": {
"href": "string",
"templated": true,
"method": "get",
"title": "string",
"type": "string"
}
},
"id": 0,
"channel_id": 0,
"topic": "order.created",
"topic_style": "singular",
"url": "https://myapp.com/events",
"status": "active",
"auth": {},
"notify_origin": true,
"app_id": 0,
"error_count": 0,
"last_error": "string",
"last_error_on": "2019-08-24T14:15:22Z",
"created_on": "2019-08-24T14:15:22Z",
"updated_on": "2019-08-24T14:15:22Z"
}
```
---
# Get webhook delivery history
Source: https://developers.onbolder.com/api/v2/webhooks/webhook-history-flattened
`GET /webhooks/{id}/history`
**Responses**
- `200` — Delivery history
---
# Agentic checkout (UCP) API
Source: https://developers.onbolder.com/changelog/2026-08-03-ucp-checkout
Every storefront now exposes a [Universal Commerce Protocol](/frontend/sdk/ucp) checkout API, discoverable at `/.well-known/ucp` — shopping agents can create a checkout session, add/update line items, and complete a purchase (or hand off to the hosted web checkout when a human-facing step is required) without a human driving the browser.
---
# Cross-origin customer auth for headless integrations
Source: https://developers.onbolder.com/changelog/2026-09-10-cross-origin-auth
`storefront.auth.*` (login, `isLoggedIn()`, the Customer Console) now works from a headless integration hosted on its own domain, not just one proxied under the shop's — the session cookie is now `SameSite=None; Secure` in production. This closed two CORS/CSRF gaps that relaxing `SameSite` would otherwise have reopened; see [Authentication](/frontend/sdk/auth) and this repo's `CLAUDE.md` for the full writeup.
---
# Declarative WebMCP annotations on search and add-to-cart
Source: https://developers.onbolder.com/changelog/2026-09-16-webmcp-search
Search and add-to-cart forms now carry declarative `toolname`/`tooldescription` [WebMCP](https://github.com/webmachinelearning/webmcp) annotations, so agentic browsers can discover and drive these flows without bespoke integration work per shop.
---
# Add 'by weight step' shipping rate mode
Source: https://developers.onbolder.com/changelog/2026-10-08-add-by-weight-step-shipping-rate-mode
Merchants can now configure a new shipping rate mode that charges a specific amount for every X grams, rounded up per started step. This enables more flexible tiered weight-based shipping calculations directly in your storefront and checkout flows.
---
# Direct ID and SKU search with disabled typo tolerance
Source: https://developers.onbolder.com/changelog/2026-10-08-direct-id-and-sku-search-with-disabled-typo-tolerance
Product search now indexes IDs with a high search boost, allowing customers to find items instantly by entering their product ID. Additionally, queries matching ID or SKU patterns automatically bypass typo tolerance to ensure exact matches every time.
---
# Enforce polygon max distance
Source: https://developers.onbolder.com/changelog/2026-10-08-enforce-maximum-polygon-distance-for-shipping-options
Fixed a bug where shipping options with distance limits could sometimes be selected during checkout even if the delivery address was out of range. The validation now correctly enforces maximum polygon distances at the time of order selection and re-validation.
---
# Per-shop security edge headers
Source: https://developers.onbolder.com/changelog/2026-10-13-per-shop-edge-headers-security-settings-group
Added a new security settings group to shops allowing configuration of Content Security Policy (CSP), permissions policies, and HSTS preload headers. These settings are automatically mirrored to the edge cache so Nginx can attach them to responses without hitting the application.
---
# Tailwind partials fix and CSS load order update
Source: https://developers.onbolder.com/changelog/2026-10-13-tailwind-partials-fix-and-css-load-order-update
Fixed a bug where Tailwind configuration files inside the partials directory were ignored during stylesheet generation. Additionally, the CSS load order in the longtail2 theme has been updated to ensure custom styles correctly override default Tailwind defaults.
---
# Fix/dynamic prices comparison update
Source: https://developers.onbolder.com/changelog/2026-10-14-fix-dynamic-prices-comparison-update
Fixed a bug in the DynamicPrices JS module where comparison prices failed to update when switching product variants if the comparison price happened to match the regular price node's content.
---
# Add markdown as a post/section format
Source: https://developers.onbolder.com/changelog/2026-10-15-add-markdown-as-a-post-section-format
Added `markdown` as a supported editor format across models, enabling `markdown?` validation and raw source retrieval via `plain_body` for the API and storefront renderer.
---
# Domain event worker, customer topics and tracker dispatch
Source: https://developers.onbolder.com/changelog/2026-10-15-domain-event-worker-customer-topics-and-tracker-dispatch
Introduced the domain event worker and expanded customer event tracking to support subscription preferences like opt-in and opt-out actions. This architecture ensures reliable publishing of customer and order events to webhooks and trackers while keeping retry logic safe from duplicate executions.
---
# Feedback-button: reuse customer-menu inline SVG icons
Source: https://developers.onbolder.com/changelog/2026-10-15-feedback-button-reuse-customer-menu-inline-svg-icons
Shared inline SVG icons replace CSS background images across storefront components, fixing contrast issues on hover.
---
# Route blogs sharing a base_path by language
Source: https://developers.onbolder.com/changelog/2026-10-15-route-blogs-sharing-a-base_path-by-language
Fixed blog routing to correctly resolve blogs sharing the same base path across different languages. When multiple blogs share a path, the storefront now prioritizes the blog matching the current locale, preventing 404 errors on localized blog pages.
---
# Fix extra kg cost being ignored when updating shipping options via CSV
Source: https://developers.onbolder.com/changelog/2026-10-16-fix-extra-kg-cost-being-ignored-when-updating-shipping-options-via-csv
Fixed an issue where updating shipping options via CSV import silently ignored changes to the extra kilogram cost.
---
# Checkout: one-click pay for redirect payment methods
Source: https://developers.onbolder.com/changelog/2026-10-19-checkout-one-click-pay-for-redirect-payment-methods
Redirect payment methods like PayPal, Webpay and Flow now skip the redundant confirmation screen. Selecting a redirect method displays an integrated notice and exchange rate summary, allowing customers to complete their purchase with a single click.
---
# Fixed display of loyalty points opt-in at checkout
Source: https://developers.onbolder.com/changelog/2026-10-19-fixed-display-of-loyalty-points-opt-in-at-checkout
FIxed validation for unverified and newly registered buyers so they can now see the loyalty points opt-in toggle during checkout. If a buyer has an unverified membership, checkout displays a prompt to verify their account and unlock their points balance.
---
# Welcome popup: subscribe.success event, sign-up source, Klaviyo/Retail Rocket
Source: https://developers.onbolder.com/changelog/2026-10-19-welcome-popup-subscribe-success-event-sign-up-source-klaviyo-retail-rocket
Added new browser events for newsletter sign-ups and integrated the welcome popup with Klaviyo and Retail Rocket marketing tools. The welcome popup now tags its submissions so subscribers from the popup correctly trigger marketing automation sequences.
---
# Add v2 endpoints to create and update shipping costs
Source: https://developers.onbolder.com/changelog/2026-10-20-add-v2-endpoints-to-create-and-update-shipping-costs
Added v2 endpoints to create, update, and manage shipping tables and their shipping options. Developers can now programmatically configure delivery zones, rate modes, and cost rules directly through the API.
---
# Brevo web tracker: use SDK v2 (client_key snippet)
Source: https://developers.onbolder.com/changelog/2026-10-20-brevo-web-tracker-use-sdk-v2-client_key-snippet
The Brevo web tracker integration now uses the SDK v2 loader and updated event payloads. This resolves verification issues with newly generated tracking keys and aligns user identification and product views with the current Brevo tracking specifications.
---
# Support unit_quantity column for product variants
Source: https://developers.onbolder.com/changelog/2026-10-20-support-unit_quantity-column-for-product-variants
Product import and update CSV tasks now support an optional unit quantity column for product variants. When provided, the value is sent to the API as a variant attribute, while blank cells are ignored to preserve existing data.
---
# Fix Retail Rocket resubscription popup never showing
Source: https://developers.onbolder.com/changelog/2026-10-21-fix-retail-rocket-resubscription-popup-never-showing
Fixed an issue where the Retail Rocket resubscription popup failed to appear because the visitor state response structure was not parsed correctly.
---
# Fix search ignoring relevance sort
Source: https://developers.onbolder.com/changelog/2026-10-21-fix-search-ignoring-relevance-sort
Fixed an issue where product searches incorrectly applied the store's default sorting order instead of search relevance. Searches now correctly preserve relevance ranking.
---
# Granular notification profiles for store alerts
Source: https://developers.onbolder.com/changelog/2026-10-21-granular-notification-profiles-for-store-alerts
You can now configure granular notification profiles to control exactly which alerts, emails, and subscription events are sent to each recipient. Profiles support optional cart-based filtering, role scope validation, and customizable low-stock alert thresholds. Manage these settings directly from the admin panel to tailor store communications to your workflow.
---
# New merchant alerts feed and notification system
Source: https://developers.onbolder.com/changelog/2026-10-21-new-merchant-alerts-feed-and-notification-system
Stores can now surface stateful and one-off notifications directly in the admin panel via a new notification feed. This system automatically reports misconfigured integrations and invalid API credentials, and clears alerts once the issues are resolved. Merchants can also configure notification profiles to receive automated email updates for selected alert topics.
---
# Unit pricing now available in search results
Source: https://developers.onbolder.com/changelog/2026-10-21-unit-pricing-now-available-in-search-results
Search results now correctly resolve `price_per_display_unit`, `unit_of_measure`, and `unit_display_quantity` for API-backed products. Liquid themes can now properly display measurement unit pricing badges on search pages. This ensures consistent pricing displays across both catalog and search views.
---
# Beacon: category, description and suggested help pages
Source: https://developers.onbolder.com/changelog/2026-10-22-beacon-category-description-and-suggested-help-pages
The help panel now displays categories, descriptions, and context-aware suggested help articles based on your current path in the admin panel.
---
# Block gibberish name/message bots on contact form
Source: https://developers.onbolder.com/changelog/2026-10-22-block-gibberish-name-message-bots-on-contact-form
Contact form submissions now use the same automated bot detection as newsletter forms, filtering out entries with gibberish names or messages. Flagged submissions receive a silent redirect just like honeypot catches.
---
# Enhanced Google Analytics tracking for carts and checkouts
Source: https://developers.onbolder.com/changelog/2026-10-22-enhanced-google-analytics-tracking-for-carts-and-checkouts
Google Analytics integration now tracks cart views, item variants, and pricing details automatically. Additionally, checkout flows now reliably send `begin_checkout` events—even on direct links or buy-now buttons—and load GA4 alongside Google Tag Manager unless your container already hosts it. If your GTM container includes its own GA4 tag, configure the GTM addon option to prevent duplicate tracking.
---
# Track and handle bounced customer and notification emails
Source: https://developers.onbolder.com/changelog/2026-10-22-track-and-handle-bounced-customer-and-notification-emails
The platform now tracks bounced email addresses for both store notifications and customer profiles, exposing new fields (`email_bounced_at` and `email_bounce_reason`) via the API. When an email bounces, the system automatically skips it for store alerts and triggers domain events (`recipient.bounced`, `recipient.bounce_cleared`, and `recipient.deleted`). Updating or replacing an affected email address automatically clears its bounce state so normal messaging can resume.
---
# Better integration error alerts and automatic refund failure notices
Source: https://developers.onbolder.com/changelog/2026-10-23-better-integration-error-alerts-and-automatic-refund-failure-notices
Third-party integrations now more accurately detect and separate missing configuration options from rejected API credentials, surfacing these issues as clear alerts in the admin panel and automatically clearing them upon a successful sync. Additionally, order trackers run independently so that a failure in one integration does not block others. Finally, when an automatic order refund fails, the system now triggers a persistent admin alert until a manual refund is processed.
---
# Theme validation and runtime health checks
Source: https://developers.onbolder.com/changelog/2026-10-23-theme-validation-and-runtime-health-checks
Stores now automatically validate Liquid templates upon saving, surfacing syntax errors, missing layout tags, and unreferenced assets directly in the theme editor. Additionally, published themes track runtime issues such as rendering errors, slow templates, and missing assets, reporting them as alerts via the API. Template endpoints now include a `warnings` field to help you diagnose and fix theme issues quickly without blocking your workflow.
---
# Keep sale price reduction on every unit when promo discounts add up to the regular price
Source: https://developers.onbolder.com/changelog/2026-10-24-keep-sale-price-reduction-on-every-unit-when-promo-discounts-add-up-to-the-regular-price
Fixed a calculation bug where promotional discounts combined with regular prices incorrectly stripped sale price reductions from order items. Promotional charges are now excluded from full-discount unit counts, ensuring item sale prices and discounts apply correctly.
---
# Fulfillment step shows a note when the order has a shipping discount
Source: https://developers.onbolder.com/changelog/2026-10-26-fulfillment-step-shows-a-note-when-the-order-has-a-shipping-discount
The checkout now displays order-level shipping discount labels as a distinct note above the shipping options list. When a discount zeroes out shipping entirely, all available options correctly show as free with their original prices struck through.
---
# OpenAI Ads pixel support in checkout
Source: https://developers.onbolder.com/changelog/2026-10-26-openai-ads-pixel-support-in-checkout
You can now track conversion metrics from ChatGPT advertising campaigns by adding the OpenAI Ads pixel to your checkout. The integration measures checkout initiation and order completion events, automatically normalizing and SHA-256 hashing contact details for secure user matching. It also includes out-of-the-box support for Google Tag Manager integration.
---
# [Beta] Integrate AI agents with scoped permissions and proposals API
Source: https://developers.onbolder.com/changelog/2026-10-27-integrate-ai-agents-with-scoped-permissions-and-proposals-api
You can now connect permissioned AI agents to your store to assist with automated workflows through MCP endpoints. Agents can read store data and either apply changes directly (subject to write scopes, guardrails, and daily limits) or submit them as proposals requiring human review. Use the new agent proposals API to list, approve, or reject pending writes for products, variants, collections, inventory stock, and order tags. Autonomous agents are also supported via dedicated OAuth client credentials that issue short-lived tokens scoped strictly to the agent's permissions.
---
# Enhanced Cloudflare integration and asynchronous custom domains
Source: https://developers.onbolder.com/changelog/2026-10-28-enhanced-cloudflare-integration-and-asynchronous-custom-domains
Bolder now supports self-service Cloudflare accounts alongside managed CDN integrations, allowing merchants to configure their own API tokens for automated cache purging. Custom domain verification and SSL certificate generation now run asynchronously in the background, and domain validation fully supports Cloudflare-proxied setups and custom CNAME targets. Additionally, cache-sweeping and file tasks have been moved to dedicated background workers to improve request performance and reliability.
---
# Customer wallets, purchase blocking, and checkout updates
Source: https://developers.onbolder.com/changelog/2026-10-30-customer-wallets-purchase-blocking-and-checkout-updates
The Customer API endpoint now embeds loyalty points, credit wallets, and integration sync details for better monitoring. Store administrators can now block specific customers from completing new purchases, and checkout paths now strictly whitelist buyer-supplied contact attributes for improved security. Additionally, the checkout configuration now uses a 1:1 redemption rate for loyalty points and correctly handles purchase order numbers.
---
# AI discovery manifests and price list search fixes
Source: https://developers.onbolder.com/changelog/2026-11-02-ai-discovery-manifests-and-price-list-search-fixes
Stores now publish AI discovery manifests at `/.well-known/ard.json` and `/.well-known/ai-catalog.json` when agent discovery is enabled, exposing agent-readable surfaces such as guides, rules, and checkout capabilities. Additionally, storefront catalogs, collections, product types, vendors, and tags now correctly display products for customers assigned to a specific price list.
---
# Tracking Tech order export and bulk shipment imports
Source: https://developers.onbolder.com/changelog/2026-11-03-tracking-tech-order-export-and-bulk-shipment-imports
Stores on the Lite plan and above can now export orders in a format tailored for Tracking Tech using the new CSV endpoint at `/:shop_id/orders_for_tracking_tech.csv`. Additionally, you can now upload spreadsheets to bulk import fulfillments via Tracking Tech by including a `seller_code` column. Single-shop sellers also benefit from full pagination and a per-page selector in the orders view.
---
# Customers: monthly activity series
Source: https://developers.onbolder.com/changelog/2026-11-04-customers-monthly-activity-series
Staff contact responses now include a monthly activity series covering orders, carts, favorites, and messages over the last 12 months, batched efficiently per page.
---
# Fixed-term subscription fees and automated manual order closing
Source: https://developers.onbolder.com/changelog/2026-11-04-fixed-term-subscription-fees-and-automated-manual-order-closing
You can now configure fixed-term subscription fees and memberships with defined durations in days or months. These fees automatically require a single payment without renewals and complete on their end date via a daily background task. Additionally, the orders API now supports closing orders automatically when recording manual payments to quickly fulfill offline-paid memberships and prevent duplicate active purchases.
---
# Subscription lifecycle events and API scopes
Source: https://developers.onbolder.com/changelog/2026-11-04-subscription-lifecycle-events-and-api-scopes
Subscription purchases now emit webhook lifecycle events—such as started, renewed, cancelled, completed, and end date changes—and store the purchaser's company ID. Additionally, new `subscriptions.read` and `subscriptions.write` scopes have been added to the API and commerce roles, allowing you to filter subscription purchases by company and expiry dates and manage them programmatically.
---
# v2 orders: from_quote on create/update, payment_method_id on manual payments (ARQ quote orders)
Source: https://developers.onbolder.com/changelog/2026-11-04-v2-orders-from_quote-on-create-update-payment_method_id-on-manual-payments-arq-quote-orders
Added support for creating and updating quote orders via the API using the `from_quote` parameter, subject to store settings. Manual payment endpoints now also accept `payment_method_id` to handle orders created without a default payment method.
---
# v2 products list: embed the first image from the ES hit
Source: https://developers.onbolder.com/changelog/2026-11-04-v2-products-list-embed-the-first-image-from-the-es-hit
The v2 products list endpoint now correctly includes embedded product images and thumbnails for search hits, matching the behavior of the v1 endpoint. This ensures that product lists returning from search results properly display thumbnail links and image collections.
---
# Customer email for order messages
Source: https://developers.onbolder.com/changelog/2026-11-06-customer-email-for-order-messages
Added a customer email notification for external order messages sent by staff. The notification safely ignores internal notes and missing messages, supporting localized templates across v1 and v2 layouts.
---
# Customer email on cancelled orders; signed tracking link in customer mails
Source: https://developers.onbolder.com/changelog/2026-11-06-customer-email-on-cancelled-orders-signed-tracking-link-in-customer-mails
Customer order emails now include a secure tracking link, and a new notification is sent automatically when an order is cancelled. The tracking link points to a hosted page using a signed token and appears in all standard customer order updates.
---
# Getting Started
Source: https://developers.onbolder.com/cli
The Bolder CLI (`bolder-cli` npm package, `bolder` executable) is a command-line tool for building on Bolder: scaffold headless or hosted storefront projects, edit and publish hosted themes, and query products, orders and webhooks. It requires Node.js and wraps [`bolder-api-client`](https://www.npmjs.com/package/bolder-api-client), the JS client for the Bolder v2 API.
1. **Install**
```bash
npm install -g bolder-cli
```
2. **Log in**
```bash
bolder login
```
Opens your browser to authorize the CLI (OAuth 2.0 with PKCE) and stores the resulting token. On a machine without a browser, or in CI, use a personal access token instead: `bolder login --token`.
3. **Pick a shop**
```bash
bolder shops use
```
Sets the default shop for future commands.
4. **Start a project**
```bash
bolder new my-storefront
```
Walks you through choosing a headless storefront (Vite or plain `
```
The loader's positional arguments (after `window`/`document`/`'script'`) are `shop`,
`src` (the SDK URL), and an optional 6th `apiBase` override. They end up as
`data-shop`/`data-api-base` on the dynamically-created script tag, which is what
`createStorefront()` reads internally to auto-init. You never write those attributes
by hand.
If you need to initialise manually with options the loader doesn't expose, skip the
loader and call `createStorefront()` yourself once the script has loaded — see
[createStorefront(options)](/frontend/sdk/core#createstorefrontoptions).
**npm package**
```bash
npm install bolder-storefront
```
```js
import { createStorefront } from 'bolder-storefront'
createStorefront({ shop: 'acme' }).then(function(storefront) {
storefront.cart.on('updated', function() {
document.querySelector('#cart-count').textContent = storefront.cart.units
})
})
```
## Pre-load queue pattern
The loader snippet above already implements this: `Bolder.ready(fn)` pushes `fn` onto
`window.Bolder.q` if the SDK hasn't finished loading yet, and calls `fn` immediately once
it has. You can call `Bolder.ready()` as many times as you like, from as many separate
`
```
There's no manual queue array to manage — don't push onto `window.Bolder.q` directly,
always go through `Bolder.ready()`.
**[Core API](/frontend/sdk/core)**
createStorefront(options), Emitter, and the full config reference.
**[Cart](/frontend/sdk/cart)**
Adding items, cross-origin cart identity.
---
# Authentication & Delivery
Source: https://developers.onbolder.com/frontend/sdk/auth
## Authentication
`storefront.auth` exposes customer authentication methods. All callbacks receive `(code, body)` — same raw shape as the underlying API response.
| Method | Description |
|--------|-------------|
| `login(email, password, cb)` | Log in with email and password. |
| `createAccount(body, cb)` | Register a new customer. `body`: `{ email, password, name, … }`. |
| `checkExists(email, cb)` | Check whether an account exists for the given email. |
| `forgotPassword(email, cb)` | Send a password-reset email. |
| `validateOTPCode(email, otp, otpMode, activationToken, password, customPhone, cb)` | Validate a one-time password. `otpMode`: `'otp_email'` or `'otp_sms'`. |
| `resendOTPCode(email, otpMode, customPhone, cb)` | Resend an OTP code. |
| `skipOTPValidation(cb)` | Skip OTP validation (if allowed by shop settings). |
| `oauthLogin(provider)` | Initiate an OAuth flow. `provider`: `'google'` or `'github'`. Submits a form; no callback. |
| `isLoggedIn(cb)` | Check session state. Calls `cb(isLoggedIn, userData)` — `userData` is the `/user.json` body when logged in. |
```js
storefront.auth.isLoggedIn(function(loggedIn, user) {
if (loggedIn) console.log('Hello', user.name)
})
storefront.auth.login('customer@example.com', 'secret', function(code, body) {
if (code === 200) console.log('Logged in')
})
```
### openLogin()
`openLogin(storefront, opts)` — exported from `bolder-storefront` — imperatively opens the login/signup modal. It mounts the `AuthDelivery` component into a managed overlay on the first call and reuses it on subsequent calls.
```js
import { createStorefront, openLogin } from 'bolder-storefront'
createStorefront({ shop: 'acme' }).then(function(storefront) {
document.querySelector('#login-btn').addEventListener('click', function() {
openLogin(storefront, {
onLoginComplete: function() {
console.log('User is now logged in')
}
})
})
})
```
`opts` is forwarded to the component's `showLogin()` method. The most useful option is `onLoginComplete` — a callback fired after a successful login or registration.
## Delivery Setup
`storefront.delivery` provides access to shipping and delivery data (countries, regions, methods). It is used internally by the `DeliverySetup` component and by `openDeliverySetup()`.
`openDeliverySetup(storefront, opts)` and `closeDeliverySetup()` — both exported from `bolder-storefront` — open and close a managed delivery-setup overlay. The overlay lazily mounts `DeliverySetupV2` on first call.
```js
import { createStorefront, openDeliverySetup, closeDeliverySetup } from 'bolder-storefront'
createStorefront({ shop: 'acme' }).then(function(storefront) {
document.querySelector('#change-delivery').addEventListener('click', function() {
openDeliverySetup(storefront, {
allowPickup: true,
allowShipping: true,
onComplete: function() {
console.log('Delivery setup completed')
closeDeliverySetup()
}
})
})
})
```
| Option | Type | Description |
|--------|------|-------------|
| `allowPickup` | `boolean` | Show in-store pickup options. |
| `allowShipping` | `boolean` | Show shipping address options. |
| `onComplete` | `function` | Called when the customer confirms their delivery choice. |
---
# Cart
Source: https://developers.onbolder.com/frontend/sdk/cart
`storefront.cart` is an event emitter (see [Emitter](/frontend/sdk/core#emitter)) wrapping the shop's cart session.
```js
storefront.cart.on('updated', function() {
document.querySelector('#cart-count').textContent = storefront.cart.units
})
// Add by numeric product/variant id — see addItem below for the full signature
storefront.cart.addItem(productId, variantId, 1)
// Add by a product's URL slug instead — no need to look up its numeric id yourself,
// the server resolves it (product must have exactly one variant, or you'll need
// to resolve the variant id yourself and use addItem)
storefront.cart.quickAdd('aurora-runner', function(item) {
if (item) console.log('added', item)
})
```
- **`quickAdd(productIdOrSlug, fn?)`** — Add a product by numeric ID or by URL slug. A
numeric-looking string/number is sent as `product_id`; anything else is sent as
`product_slug` and resolved server-side. Respects `onBeforeAdd` (see [Config
Reference](/frontend/sdk/core#config-reference)) the same way `addItem` does.
- **`addItem(productId, variantId, quantity, formData?, fn?)`** — Add or increase
quantity of a line item. Pass `variantId: -1` to let the server pick the product's
default/only variant. `formData.product_slug` resolves a slug the same way `quickAdd`
does internally.
## Cross-origin cart identity
The cart session is identified server-side by an id (`_btc_oid`) that hosted themes carry
in a same-site cookie automatically. A headless page served from a different origin than
`apiBase` — true for almost any headless integration that isn't proxied under the shop's
own domain — can't rely on that cookie the normal way: `SameSite`/`Secure` cross-site
cookie rules would require the API to be served over HTTPS just for the cookie to be
honored.
To sidestep that entirely, the SDK's cart client **doesn't depend on the cookie**:
- Every cart response includes the cart's id (`cart.id`), which the SDK caches in
`localStorage` and sends back as an `X-Cart-Token` header on subsequent cart requests —
a plain custom header, not a cookie, so none of the `SameSite`/HTTPS requirements apply.
Server-side, `cart_cookie` (the single method every cart-resolving code path in `app.rb`
goes through) accepts either the cookie or this header, in that order.
- The one exception is the checkout button itself: it's a real `