---
title: "REST API v1 reference"
description: "The Bridge to Brands REST API at https://api.brdg2b.ai/v1: brands, catalog, availability grids, bulk lookup, ship-tos, orders and usage, with conventions and examples."
canonical: https://brdg2b.ai/docs/rest-api
updated: 2026-10-16
---

# REST API v1 reference

Bridge to Brands is a product of RunOps Inc.

Base URL: `https://api.brdg2b.ai/v1`. One schema for products, availability, prices, ship-tos and orders across every brand; the brand is a path parameter. The machine-readable contract is [/openapi.json](/openapi.json).

## Conventions

- **Auth**: `Authorization: Bearer b2b_live_<prefix>_<secret>`; scopes `read` and `orders`. See [Authentication](/docs/authentication).
- **Errors**: RFC 9457 `application/problem+json`; `type` is a stable URL under `https://brdg2b.ai/problems/`. See [Errors](/docs/errors).
- **Pagination**: cursor-based, `?limit=` (max 100) and `?cursor=`; responses are `{ "data": [...], "next_cursor": "...", "meta": {...} }`.
- **Idempotency**: `Idempotency-Key` header required on `POST /orders`; the same key within 24 hours replays the original response.
- **Request ids**: `X-Request-Id` is echoed; include it in support requests.
- **Schemas**: unknown fields are rejected with `400`.
- **Money**: strings with two decimals plus `currency`, never floats.
- **Time**: RFC 3339 UTC. `as_of` on every availability response says when the portal was asked.
- **Versioning**: `/v1` is stable and changes additively; breaking changes go to `/v2`.

## Brands

| Method | Path | Scope | Notes |
|---|---|---|---|
| GET | `/brands` | read | every supported brand with `capabilities`, `platform`, and this account's `connection` |
| GET | `/brands/{brand}` | read | one brand |

Capabilities: `catalog_search`, `prices`, `width_axis`, `ordering_modes`, `ordering`, `drop_ship`, `drop_ship_requires_phone`, `drop_ship_requires_email`, `drop_ship_max_per_send`, `po_max_length`, `submit` and `submit_verified` (placing orders; not yet verified for any brand), `portal_url`.

### Several logins per brand

A retailer can keep more than one login for a brand, for example one for the stores and one for Amazon. `GET /accounts` lists them with the name and description the retailer wrote. Brand calls take an optional `account` (the id or exact name); without it they use the brand's default login. Answers say which login they came from (`account: {id, name}`), and availability takes `accounts` to compare logins side by side.

## Catalog and availability

| Method | Path | Scope | Notes |
|---|---|---|---|
| GET | `/brands/{brand}/products?q=` | read | free-text search; `capability_unavailable` without `catalog_search` |
| GET | `/brands/{brand}/products/{style_id}` | read | colourways, size columns, images, prices |
| GET | `/brands/{brand}/products/{style_id}/availability?color_id=` | read | canonical grids keyed by colour id |
| POST | `/brands/{brand}/lookup` | read | up to 200 items by UPC, SKU or style + colour + size |

```json
{
  "brand": "brooks", "style_id": "120443", "color_id": "120443189",
  "status": "ok",
  "columns": [ { "key": "7.0/B", "size": "7", "width": "B" } ],
  "rows": [
    { "date": null, "ordering_mode": "at_once",
      "cells": { "7.0/B": { "qty": 200, "display": "200+", "order_limit": null } } },
    { "date": "2026-11-15", "ordering_mode": "future",
      "cells": { "7.0/B": { "qty": 48, "display": "48" } } }
  ],
  "cap_threshold": 200, "cap_applied_by": "server",
  "price": { "wholesale": "70.00", "retail": "140.00", "currency": "USD" },
  "as_of": "2026-10-16T15:04:05Z"
}
```

## Ship-tos

`GET /brands/{brand}/ship-tos` (read): your stores as the vendor knows them, with `ref`, `account_ref`, `name`, `city`, `state`, `postal` and your own `label`.

## Orders

| Method | Path | Scope | Notes |
|---|---|---|---|
| POST | `/orders` | orders | place one order in one brand portal (or stage it for review in Bridge); `Idempotency-Key` required |
| GET | `/orders/{order_id}` | orders | one order |
| GET | `/orders?brand=&status=&since=` | orders | list |
| POST | `/orders/{order_id}/acknowledge-draft` | orders | confirm a `draft_left_behind` was dealt with |

```json
{
  "brand": "brooks",
  "kind": "fill_in",
  "ship_to_ref": "1002",
  "po": "FILL-1016-COLA",
  "lines": [
    { "variant_id": "1204431B189.070", "quantity": 2 },
    { "upc": "195394123456", "quantity": 1, "ordering_mode": "at_once" }
  ]
}
```

A drop-ship order adds `"kind": "drop_ship"` and a `consumer` block (addressee, street, city, state, postal, country, and phone or email only when the brand requires them; the email is the store's). Orders take `mode: "stage" | "submit"` and, to place, `confirm: true` with a key holding `orders:submit`; the effective mode is the most conservative of the request, the key, the account's per-brand setting and the brand's verified capability, within the account caps. The response is `202 Accepted` with `status` (`pending`, `staged_in_portal`, `submitted`, `partial`, `failed`, `submit_unknown`, `draft_left_behind`), `submitted` (true only with a vendor order number or a positive read-back), the portal URL, the vendor order number or draft id, and staged and skipped lines with reasons.

## Usage

`GET /usage?from=&to=&group_by=day|brand|operation` (read): the calling key's own requests, results, orders, lines and units.

## Not in the API, on purpose

Connecting portals, managing keys and members, and the cross-key dashboard live in the web app (`/app/*`, Google sign-in), never under `/v1`.
