Browser-driven adidas Click B2B ordering toolkit — places purchase orders on the adidas Click portal with Playwright, since adidas exposes no public ordering...
---
name: drivethru-adidas-click
description: Browser-driven adidas Click B2B ordering toolkit — places purchase orders on the adidas Click portal with Playwright, since adidas exposes no public ordering API. Accepts style number, color, size, quantity, a purchase-order number, and a ship-to address, drives the cart/checkout, and (on confirm) submits the order. Use whenever the user needs to place or draft a purchase order on adidas Click.
version: 0.3.0
emoji: 👟
homepage: https://b2bportal.adidas-group.com
metadata:
openclaw:
requires:
bins: [python3]
envVars:
ADIDAS_CLICK_USERNAME:
required: false
description: >
adidas Click B2B portal username (typically an email). Optional —
may instead be passed inline in a tool's stdin JSON. Treat as a secret.
ADIDAS_CLICK_PASSWORD:
required: false
description: adidas Click portal password. Optional env cache; treat as a secret.
ADIDAS_CLICK_BASE_URL:
required: false
description: >
Override for the adidas Click base URL. Defaults to
`https://b2bportal.adidas-group.com`. Set this if the account uses a
region-specific host.
ADIDAS_CLICK_HEADLESS:
required: false
description: >
Run the browser headed instead of headless when set to a falsey value
(`false`/`0`/`no`/`off`); default is headless. Headed can help if
adidas's Akamai Bot Manager still challenges the headless browser
(headed needs a display, e.g. `xvfb-run`).
ADIDAS_CLICK_USER_AGENT:
required: false
description: >
Override the browser User-Agent. Defaults to a current desktop-Chrome
UA so adidas's Akamai edge serves the automated browser instead of
stalling the response. Only change it if the default UA gets blocked.
install:
uv:
# Browser automation is the only surface. This installs the Playwright
# *package*; the Chromium *binary* (a separate ~150 MB download that pip
# can't fetch, and OpenClaw has no post-install hook for) is installed
# automatically on the first run — see the browser-binary note below.
- playwright>=1.40
---
# adidas Click B2B toolkit
adidas Click is a **B2B ordering portal with no public API**, so this skill
places purchase orders by driving the portal with **Playwright**. Every tool is
reached through one CLI entrypoint:
```bash
echo '<json-args>' | python3 scripts/adidas.py <action>
```
The action is the **first CLI argument**; arguments are a JSON object on
**stdin**. Each call prints a single JSON object on stdout, or
`{"error": {"type": ..., "message": ...}}` with a non-zero exit code on failure.
Playwright is imported lazily, so the skill loads without it. The Playwright
**package** is a declared `uv` dependency; its Chromium **browser binary** (a
separate ~150 MB download that pip/uv doesn't fetch, and for which OpenClaw has
no install-time hook) is **installed automatically on the first run** if missing
— so no manual `python -m playwright install chromium` is needed. That first run
therefore includes a one-time browser download. If the auto-install fails (e.g.
no network, or missing system libraries), the tool returns a clear
`config_error` pointing at the manual command.
> **Build status — full order flow implemented.** The skill drives a purchase
> order end to end: login → add line quantities to the active cart → checkout
> (Customer PO, delivery location — default / saved / one-time dropship — and
> shipping method) → Next → **Calc. Net Price** (waits for the "Done!" tell so
> the wholesale discount always applies) → **Order Now** → confirmation number
> parsed from the redirect URL. `confirm: false` is a full dry run; `confirm:
> true` **places a real order** (no sandbox). The assembled flow has been built
> and unit-checked selector-by-selector but not yet run against the live site in
> one pass — watch the first real end-to-end run. See
> [`references/order_flow_notes.md`](references/order_flow_notes.md) for the step
> map and captured selectors.
## When to use this skill
Reach for `create-purchase-order` when the request involves placing (or
drafting) a purchase order on **adidas Click**: given a style number, color,
size, quantity, a PO number, and a ship-to address, put the order into the
portal cart/checkout and — on explicit confirmation — submit it.
Do **not** use it for other footwear/apparel vendors, and never invent portal
selectors from prose — the flow lives in the captured
[`references/order_flow_notes.md`](references/order_flow_notes.md).
## Actions
| Action | Risk | stdin JSON (key fields) |
| --- | --- | --- |
| `create-purchase-order` | **high — portal write (browser)** | `{purchase_order: {po_number, lines: [{style, size, quantity}], ship_to?, delivery_location_id?, ship_method?, on_insufficient_stock?, spread_delivery?}, confirm}` |
Run `python3 scripts/adidas.py` with no action to print the action list.
### `create-purchase-order` input
The order can be passed nested under `purchase_order` or as top-level fields:
```json
{
"purchase_order": {
"po_number": "PO-12345",
"lines": [
{"style": "JW4306", "size": "M", "quantity": 24},
{"style": "JW4306", "size": "L", "quantity": 12}
],
"ship_to": null,
"delivery_location_id": null,
"ship_method": null,
"spread_delivery": false,
"notes": null
},
"confirm": false
}
```
Each line is `{style, size, quantity}`. **`color` is optional** — adidas article
numbers encode the color (e.g. `JW4306` = the black colorway), so navigating to
the article lands on the right color with no picker.
Delivery location precedence: `delivery_location_id` (pick a saved location) >
`ship_to` (add a one-time / dropship location) > default (leave the cart's
preset). A one-time `ship_to.state` accepts a 2-letter abbreviation or the full
name. `ship_method` is left at the cart default unless set, and accepts a FedEx
service code (`FDGP`, `FEDE`, `FED4`, …) or its exact label ("FedEx Ground").
`po_number` is capped at **18 characters** and may contain only letters, numbers,
space, and `/ _ . ? &`; any other character (e.g. a hyphen) is auto-replaced with
`_` and the substitution is reported in the result's `warnings`.
By default (`new_cart: true`) the run **creates its own fresh cart** (named with
the PO) right after login and makes it active, so on a shared account it never
adds to — or checks out — a teammate's cart. If a cart with the same PO name
already exists (e.g. a leftover from an errored prior run) it is **deleted
first** so the run never piles onto stale quantities — only carts with that exact
PO name are removed, and any deletion is noted in `warnings`. Pass
`new_cart: false` to use the current active cart instead.
Pass `screenshot_path` to capture the filled page for review.
## Out-of-stock handling (read this)
Before entering quantities, the tool reads each size's availability (scrolling
the horizontal size row as needed to load off-screen sizes). A size is either
**backorderable** (stock below the requested quantity, incl. `0` with a restock
date) or **not available** (the "X" cell — will never be restocked, cannot be
ordered at all; it can only be removed or substituted). When a line's **full
quantity is not available**, behavior depends on `on_insufficient_stock`:
- **`pause`** (default) — **nothing is ordered** (even with `confirm: true`). The
result comes back `status: "needs_confirmation"` with an `out_of_stock` list
(`{style, size, requested, available}`) and a `message`. The agent must **stop
and confirm with the user**, then re-run.
- **`order`** — order the short line(s) anyway, accepting **delayed delivery**
(adidas spreads the backordered portion over future dates).
- **`skip`** — **remove** the out-of-stock line(s) and order the rest.
**Agent guidance:** if the user's request pre-authorizes a choice, set the flag
and skip the pause — e.g. *"go ahead and order anything out of stock"* →
`on_insufficient_stock: "order"`; *"remove any out-of-stock items without
asking"* → `on_insufficient_stock: "skip"`. Otherwise leave it at `pause`; on a
`needs_confirmation` result, **message the user** with the `out_of_stock` details
and the three choices, wait for their answer, then resume:
1. Order them anyway (delayed delivery) → re-run with `on_insufficient_stock: "order"`.
2. Don't order them → re-run with `on_insufficient_stock: "skip"`.
3. **Substitute** (e.g. a different size/style so items match) → edit the
`lines` accordingly and re-run (optionally with a policy for any still-short
substitutes).
`spread_delivery` is the lower-level knob behind `order`: on a per-line spread
prompt, `false` (default) declines (single delivery), `true` accepts.
The result reports `total_quantity` (summed pieces, on dry runs too) and, on a
placed order (`confirm: true`), each line's net `line_total` and `unit_price`
(net ÷ quantity) plus the order `order_total` (net wholesale), read from the
priced review page.
## Credentials
The adidas Click portal login can be supplied three ways (**later wins**):
1. **Environment variables** (preferred for a deployed agent):
```bash
ADIDAS_CLICK_USERNAME=...
ADIDAS_CLICK_PASSWORD=...
ADIDAS_CLICK_BASE_URL=https://b2bportal.adidas-group.com # override for region host
```
2. **Inline in the stdin JSON** — pass `username`, `password`, and `base_url`
alongside the tool's own arguments.
3. **CLI flags** — `--username` / `--password` / `--base-url` after the action.
Handy for local runs with no env vars and no secrets in the order JSON:
```bash
echo '{"purchase_order": {...}, "confirm": false}' \
| python3 scripts/adidas.py create-purchase-order --username "$U" --password "$P"
```
> Command-line arguments are visible to other processes (`ps`) and your shell
> history — for sensitive/automated use, prefer env vars or the stdin JSON.
If no credentials are present via any of these, the tool exits with
`{"error": {"type": "config_error", ...}}` (exit code 2) — treat that as a
signal to ask the user for the missing fields. Never guess or reuse credentials
across accounts, or paste secrets the user did not provide.
## Write safety
`create-purchase-order` is the only (side-effecting) action.
- It requires `"confirm": true` to place the order. Without it, it logs in,
fills the cart + checkout, and returns a **dry-run** preview **without
submitting**. Confirm with the user before placing.
- Placing an order on adidas Click is a **real purchase** — there is no
sandbox. Only run `confirm: true` for an order the user actually intends to
place.
## Reachability & bot mitigation
adidas Click sits behind **Akamai Bot Manager**, which fingerprints the client
and *stalls the HTTP response* (rather than cleanly refusing it) for traffic
that looks automated. The tell is a navigation that hangs to a timeout
(`Page.goto: Timeout … exceeded`) even though the network path is healthy — a
browser-UA `curl` to the same host returns `200` in well under a second, while
a bare `curl` or a default headless Chromium (UA contains `HeadlessChrome`,
`navigator.webdriver=true`) gets tarpitted.
To stay served, the driver launches Chromium with a real desktop-Chrome
User-Agent, a normal viewport/locale, `--disable-blink-features=AutomationControlled`,
and a `navigator.webdriver` mask, plus a longer navigation timeout. If the edge
still challenges from your environment:
- set `ADIDAS_CLICK_HEADLESS=false` to run headed (needs a display — e.g.
`xvfb-run -a python3 scripts/adidas.py …`), which is harder to fingerprint;
- override `ADIDAS_CLICK_USER_AGENT` if the default UA string ages out;
- if it *still* stalls, the block is likely at the **egress IP** (a datacenter
IP Akamai distrusts) — route through an IP adidas allowlists, the same way
`drivethru-sanmar` documents needing its caller IP allowlisted.
## Error model
Failures print `{"error": {...}}` and exit non-zero:
- `config_error` (exit 2) — missing/invalid credentials or an un-captured step.
- `api_error` — the portal rejected an action or a page did not match
expectations. Includes `surface`, `operation`, `retryable`.
- `connection_error` — network / browser-launch / navigation failure
(`retryable: true`).
- `validation_error` — bad input JSON or a missing required field.
- `usage` / `unknown_action` (exit 2) — bad CLI invocation; the message lists
the valid `actions`.
Surface the human-readable `message` to the user. Do not retry on
`config_error` or `validation_error`.
## References
- [`references/order_flow_notes.md`](references/order_flow_notes.md) — the
reverse-engineered portal ordering flow (step map, captured selectors, and the
capture checklist for the next step). **Start here to continue the build.**
don't have the plugin yet? install it then click "run inline in claude" again.