---
seo:
  description: Getting Started
sidebar:
  label: Overview
title: Bolder API v2
---

[⬇ Download this spec as YAML](/docs/v2/openapi.yaml)

## 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://auth.onbolder.com/dev/credentials](https://auth.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://auth.onbolder.com](https://auth.onbolder.com), 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.

### 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 |

Request them the same way as any other scope, comma-separated:

```
&scope=products.read,orders.read,orders.write
```

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

<ApiOverview source="v2" />

## Root

API entry point — embeds sellers, shops, and navigation links to every resource

<ApiTagOperations source="v2" tag="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.

<ApiTagOperations source="v2" tag="sellers" />

## Catalog

<ApiTagOperations source="v2" tag="catalog" />

## Customers

Shop customers. Includes customer authentication and password-reset flows.

<ApiTagOperations source="v2" tag="customers" />

## Orders

Shop orders. Supports batch status updates and per-order document management.

<ApiTagOperations source="v2" tag="orders" />

## 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`.

<ApiTagOperations source="v2" tag="products" />

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

<ApiTagOperations source="v2" tag="shops" />

## Webhooks

Event webhooks. Subscribe to events such as `orders.created`, `products.updated`, etc.
Inactive subscriptions can be reactivated and their delivery history inspected.

<ApiTagOperations source="v2" tag="webhooks" />

## Store

<ApiTagOperations source="v2" tag="store" />

## Collections

Curated product collections (manually or rule-based). Products can belong to
multiple collections.

<ApiTagOperations source="v2" tag="collections" />

## Product Types

Product type definitions used to group and filter catalog products.

<ApiTagOperations source="v2" tag="product-types" />

## Variants

Product variants (size, colour, etc.). Nested under `/products/{product_id}/variants`.
Each variant holds its own SKU, price, stock level, and images.

<ApiTagOperations source="v2" tag="variants" />

## Themes

Hosted-storefront themes, templates, and assets. Applies only to shops with a
hosted storefront (`store:` namespace); absent for headless shops.

<ApiTagOperations source="v2" tag="themes" />

## Content

CMS-style content for a shop's storefront — blog posts, static pages, and
contact/lead-capture forms.

<ApiTagOperations source="v2" tag="content" />

## Price Lists

Named price lists for B2B / wholesale pricing. Products can have per-variant price
overrides within a price list.

<ApiTagOperations source="v2" tag="price-lists" />

## Promotions

Promotional discount codes and automatic discounts applied at checkout.

<ApiTagOperations source="v2" tag="promotions" />

## Volume Discounts

Quantity-based discount rules (tiered pricing by units purchased).

<ApiTagOperations source="v2" tag="volume-discounts" />

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

<ApiTagOperations source="v2" tag="subscriptions" />
