git:20260810.feed32e to git:20260914.bc382d6

14 added, 7 removed. Audit A to A.

---
name: acehardware-checkout
- description: Buy products from Ace Hardware (acehardware.com) and manage those orders via the Zinc API (zinc.com). Use when the user wants to purchase, order, or check out an item from Ace Hardware, check Ace Hardware order status or tracking, cancel a Ace Hardware order, or return a Ace Hardware item. One API also covers Amazon, Walmart, Target, Best Buy and 50+ other US retailers. Supports API key auth (ZINC_API_KEY) or Machine Payments Protocol (MPP) for per-request payments via a Stripe card, Tempo stablecoins, or x402 (USDC on Base).
+ description: Buy, track, and return real products from Ace Hardware (acehardware.com) through the Zinc API (zinc.com). Use when the user wants to buy something from Ace Hardware, check a Ace Hardware order's status or tracking, cancel one, or return an item. The same API covers almost any other US retailer. Needs no account to begin — the agent mints its own test key, then earns a live key and a starter credit once its operator approves it. Pays by API key or per request over HTTP 402.
---
# Ace Hardware Checkout
Buy, track, and return products from Ace Hardware (acehardware.com) through the Zinc API (`https://api.zinc.com`). US orders.
- > **Powered by Zinc Universal Checkout.** The same API buys from Ace Hardware and 50+ other US retailers (Amazon, Walmart, Target, Best Buy, eBay, and more). To order across multiple retailers from one skill, install the [`universal-checkout`](https://github.com/zincio/skills/tree/master/skills/universal-checkout) skill (`npx skills add zincio/skills --skill universal-checkout`). Live retailer list: `GET https://api.zinc.com/retailers`.
+ > **Powered by Zinc Universal Checkout.** The same API buys from Ace Hardware and almost any other US retailer. To order across multiple retailers from one skill, install the [`universal-checkout`](https://github.com/zincio/skills/tree/master/skills/universal-checkout) skill (`npx skills add zincio/skills --skill universal-checkout`). Live retailer list: `GET https://api.zinc.com/retailers`.
- ## Quick Start
+ ## Which key am I using?
- **Which auth method should I use?**
+ - **Your operator already gave you one** (often `$ZINC_API_KEY`) → send `Authorization: Bearer <key>` and go straight to ordering.
+ - **You have no key** → get your own below. No signup form, and a starter credit to spend once your operator approves you.
+ - **Your operator never wants an account** → skip keys entirely and pay per request over MPP: `POST /agent/orders` to buy, `/agent/search` to discover, settled with a Stripe card, Tempo stablecoins, or x402. See [MPP Auth](#mpp-auth-machine-payments-protocol).
- - **`ZINC_API_KEY` env var is set** → Use `POST /orders` with Bearer token auth. This is the standard flow for pre-registered users.
- - **MPP — no account needed** → Use the `/agent/*` endpoints and pay per request with a **Stripe card** (via Stripe Link — no crypto), **Tempo** stablecoins, or **x402** (USDC on Base). `POST /agent/orders` to buy; `/agent/search` to discover ($0.01 per data call). `GET /retailers` is free.
- - **Neither is set** → Ask the user to either sign up at [app.zinc.com](https://app.zinc.com) for an API key, or set up an MPP payment method. Try it without code at [agent.zinc.com](https://agent.zinc.com).
+ ## Getting started
+
+ Four steps, in order. Details for each are in [references/getting-started.md](https://github.com/zincio/skills/blob/master/skills/acehardware-checkout/references/getting-started.md) — read that when a step does not behave as described.
+
+ 1. **Get a test key.** `POST https://api.zinc.com/sandbox/keys` — no auth, no signup. Returns `api_key` (`zn_test_…`) and a ready-made `example_order`. Send it as `Authorization: Bearer <api_key>` from here on.
+ 2. **Place a test order.** `POST /orders` with that `example_order`, then poll `GET /orders/{id}`. Costs nothing. `status` reaches **`order_placed`** — that is success, and it never becomes `delivered`; shipping shows up separately under `tracking_numbers[0].status`, which reaches `delivered` about fifteen seconds in.
+ 3. **Earn your live key.** `POST /device/code`, show your operator the link it returns, and wait while polling `POST /device/token`. When they approve, you receive a `zn_live_` key **once** — plus a starter credit in their wallet, and your sandbox history moved onto their account.
+ 4. **Buy something real.** `POST /orders` with a acehardware.com product URL and `max_price` at or under `starter_credit.max_price_cents`. Ask your operator for the shipping address (**`phone_number` is required**) and confirm the item and price with them first.
All amounts are in **US cents** (e.g. `5000` = $50.00).
## Authentication
### API Key Auth
```
Authorization: Bearer $ZINC_API_KEY
```
### MPP Auth (Machine Payments Protocol)
MPP is an open standard for HTTP 402 machine-to-machine payments (spec: [mpp.dev](https://mpp.dev)) — no API key needed upfront:
1. Send the request (e.g. `POST /agent/orders`) with **no** `Authorization` header → HTTP `402 Payment Required`. The challenge advertises every available rail: MPP methods as one `WWW-Authenticate: Payment …` header each (`method="stripe"`, `method="tempo"`), and **x402** as a `PAYMENT-REQUIRED` header (USDC on Base).
2. Pick the rail your client supports and pay, then retry: MPP methods resubmit with `Authorization: Payment <credential>`; x402 clients resubmit with a `PAYMENT-SIGNATURE` header. Client libraries handle this loop automatically.
3. For orders, save the `X-Api-Key` response header — a Bearer token scoped to that order, for `GET /orders/{id}`.
**Select a single rail with `?method=`.** A 402 can carry several challenges at once, and many HTTP clients mishandle repeated `WWW-Authenticate` headers (they fold them into one comma-joined value, corrupting the params). If your client only supports one rail, append **`?method=stripe`**, `?method=tempo`, or `?method=x402` to `/agent/orders` to get a single, unambiguous challenge. Omit it to advertise all rails (for discovery). If you do parse multiple challenges yourself, read the **raw** header list (in Python `httpx`, `resp.headers.raw`) and select by `method=`.
**Payment methods:**
| Method | Pay with | How |
|--------|----------|-----|
| **Stripe** (card) | any credit/debit card via **Stripe Link** — no crypto wallet | mint a one-time Shared Payment Token with the [`create-payment-credential`](https://skills.sh/stripe/link-cli) skill (`link-cli`), then pay `/agent/orders?method=stripe`. |
| **Tempo** | USDC stablecoin, on-chain | `pip install pympp` (Python) / `npm install mppx viem` (TS) — the client signs and pays. |
| **x402** | USDC on **Base** (`eip155:8453`) | advertised via the `PAYMENT-REQUIRED` header on `/agent/orders`; pay with any x402 client (e.g. AgentCash). *x402 is offered on orders, not the $0.01 data endpoints.* |
## Find a product (optional)
If the user already has a Ace Hardware product URL, skip to **Place an order**. Otherwise search for one:
```bash
curl "https://api.zinc.com/search?q=cast+iron+skillet" \
-H "Authorization: Bearer $ZINC_API_KEY"
```
`GET /search` returns `{ status, query, results: [...] }` across retailers; each result has a directly **orderable `url`** plus `retailer`, `title`, `price` (cents), `stars`. Filter results to `retailer == "acehardware"` for Ace Hardware-only, then pass the `url` into an order.
Paying via MPP (no account)? Use the metered `POST /agent/search` instead — $0.01 per call, returns a `Payment-Receipt` header; the MPP client handles the 402 → pay → retry automatically. `GET /retailers` is free.
## Place an order — `POST /orders` (or `POST /agent/orders` for MPP)
**Required fields:**
- `products` — array of product objects (see below)
- `shipping_address` — US delivery address
- `max_price` — integer, the **maximum total in cents** Zinc may spend before finalizing (your price ceiling)
**Product object:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `url` | string | ✓ | Direct Ace Hardware product page URL (on acehardware.com) |
| `quantity` | integer 1–100 | — | Units to buy (default 1) |
| `variant` | array of `{ label, value }` | — | Options, e.g. `[{ "label": "Size", "value": "Large" }]` |
| `condition_in` | array | — | Allowlist of acceptable conditions |
| `condition_not_in` | array | — | Denylist of excluded conditions |
**Condition enum:** `New`, `Refurbished`, `UsedLikeNew`, `UsedVeryGood`, `UsedGood`, `UsedAcceptable`.
**Shipping address:** `first_name`, `last_name`, `address_line1`, `address_line2` (optional), `city`, `state` (2-letter), `postal_code`, `phone_number`, `country` (defaults to `US`).
**Optional order fields:** `handling_days_max` (integer ≥1 — cap on seller handling time, the lever for bounding how fast it ships), `is_gift` (boolean — suppress prices on the packing slip), `idempotency_key` (string ≤36 chars), `metadata` (object), `po_number` (string).
### Controlling price & shipping
There is no shipping-*method* picker; control cost and speed with: `max_price` (price ceiling), `condition_in` (allow used/refurbished for a cheaper qualifying offer), and `handling_days_max` (cap handling time). `max_price` is the **total** ceiling — item + shipping + tax — so leave room for shipping (see `GET /retailers` for each retailer's free-shipping terms).
**Order statuses:** `pending` → `in_progress` → `order_placed` | `order_failed` | `cancelled` | `cancelled_by_retailer`.
**Example (API key):**
```bash
curl -X POST https://api.zinc.com/orders \
-H "Authorization: Bearer $ZINC_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"products": [{ "url": "https://www.acehardware.com/<product-page>", "quantity": 1, "condition_in": ["New"] }],
"max_price": 5000,
"handling_days_max": 5,
"shipping_address": {
"first_name": "Jane",
"last_name": "Doe",
"address_line1": "123 Main St",
"city": "San Francisco",
"state": "CA",
"postal_code": "94105",
"phone_number": "5551234567",
"country": "US"
}
}'
```
### Paying via MPP — `POST /agent/orders`
Same request body, no API key — pay inline. Agent authorizes `max_price + $1.00` upfront (the `$1` is the Zinc API fee, so the full `max_price` stays available to the retailer). **Validation runs before payment:** an invalid URL/retailer/address returns HTTP 400 with no charge, and the credential stays reusable. On success: charged `actual_total + $1`, with any difference under `max_price` auto-refunded; on failure: full refund (server-side). The HTTP 201 response includes `X-Api-Key` (a `zn_live_...` Bearer token, scoped to this order) and `Payment-Receipt`.
- **Stripe card (recommended for a human with just a card):** use the [`create-payment-credential`](https://skills.sh/stripe/link-cli) skill to mint a Shared Payment Token, then have that skill pay `https://api.zinc.com/agent/orders?method=stripe`.
- **Tempo (stablecoin):** `npx mppx https://api.zinc.com/agent/orders --method POST --body '…'`, or `pympp`/`mppx` in code — the client handles the 402 → pay → retry.
- **x402 (USDC on Base):** any x402 client (e.g. AgentCash) pays `POST /agent/orders` directly off the `PAYMENT-REQUIRED` header.
```python
# Tempo example — pip install pympp
from mpp.client import Client
from mpp.methods.tempo import tempo, TempoAccount, ChargeIntent, CHAIN_ID
account = TempoAccount.from_key("0x<your-private-key>")
method = tempo(chain_id=CHAIN_ID, account=account, intents={"charge": ChargeIntent()})
async with Client(methods=[method]) as client:
response = await client.post(
"https://api.zinc.com/agent/orders?method=tempo",
json={
"products": [{"url": "https://www.acehardware.com/<product-page>", "quantity": 1}],
"max_price": 5000,
"shipping_address": {
"first_name": "Jane", "last_name": "Doe",
"address_line1": "123 Main St", "city": "San Francisco",
"state": "CA", "postal_code": "94105",
"phone_number": "5551234567", "country": "US",
},
},
)
# Client handles the 402 → pay → retry flow automatically
order = response.json()
api_key = response.headers["X-Api-Key"] # use for GET /orders/{id}
```
## Track & manage orders
### Get order — `GET /orders/{id}`
Retrieve a single order by UUID (Bearer token: `ZINC_API_KEY` or the MPP `X-Api-Key`). The response includes `status`, `items`, `shipping_address`, plus:
- `tracking_numbers` — array of `{ id, carrier, tracking_number, status, checkpoints, created_at }`. `status` (always present) is the carrier-derived shipment state: `pending` | `in_transit` | `delivered`. `checkpoints` is the per-scan timeline (most recent first). Added automatically; there is no separate tracking endpoint.
- `job_result` (once terminal) — `success`, `error`, `error_type`, `estimated_delivery`, `merchant_order_ids`, and `price_components` (`subtotal`, `tax`, `shipping`, `total`, `currency`).
```bash
curl https://api.zinc.com/orders/<order_id> \
-H "Authorization: Bearer $ZINC_API_KEY"
```
### List orders — `GET /orders`
Returns `{ orders: [...] }`. Requires Bearer token auth. Add `?include=tracking_events` to also get the full `checkpoints` timeline.
### Cancel order — `POST /orders/{id}/cancel`
Cancels an order **only while `pending`** (still queued); once `in_progress` or done it can't be cancelled. Returns `204 No Content` on success.
## Returns — `POST /returns`
Open a return against a placed order (Bearer token auth).
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `order_id` | UUID | ✓ | The order being returned |
| `items` | array of `{ order_item_id, quantity }` | ✓ | Items to return (`order_item_id` comes from the order's `items`) |
| `reason` | enum | ✓ | `damaged`, `not_delivered`, `empty_box`, `wrong_item`, `defective`, `not_as_described`, `wrong_size`, `no_longer_needed`, `forced_cancellation`, `other` |
| `notes` | string ≤2000 | — | Free-text detail |
**Response (201):** `{ id, order_id, status, reason, items, label_urls, merchant_return_id, ... }`. Return `status`: `open` → `approved` | `denied` | `credited`. Ship via the URLs in `label_urls`. Also `GET /returns` and `GET /returns/{id}`.
```bash
curl -X POST https://api.zinc.com/returns \
-H "Authorization: Bearer $ZINC_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"order_id": "8c2d...",
"items": [{ "order_item_id": "a1b2...", "quantity": 1 }],
"reason": "damaged",
"notes": "Arrived with a crushed corner."
}'
```
## Error Handling
See [references/errors.md](https://github.com/zincio/skills/blob/master/skills/acehardware-checkout/references/errors.md) for the full error code reference.
Key points:
- HTTP errors return `{ error: { code, message, details } }`
- Order processing failures appear in the order's `job_result.error_type` field
- Common issues: `max_price_exceeded`, `product_out_of_stock`, `invalid_shipping_address`
- MPP payment failures return HTTP 402 with `{ error: { code: "payment_failed", details: { reason: "..." } } }`
- HTTP 402 without `payment_failed` code means the server is issuing a payment challenge (normal MPP flow — the client handles this automatically)
## Order Status Tracking
Orders process asynchronously and typically take **5–10 minutes**. After placing an order:
1. Wait ~7 minutes, then poll `GET /orders/{id}`.
2. If still `pending` or `in_progress`, poll again in 3–5 minutes.
3. Stop polling when the status is terminal.
**Terminal statuses:** `order_placed`, `order_failed`, `cancelled`, `cancelled_by_retailer`
**Non-terminal:** `pending`, `in_progress`
If your platform supports scheduled tasks or cron jobs, schedule a check ~7 minutes after order creation rather than polling in a loop.
## Safety
- **Always confirm with the user** before placing an order (`POST /orders` or `POST /agent/orders`) or opening a return. Orders spend real money.
- Reading operations (search, `GET /orders`, `GET /orders/{id}`, `GET /returns`) are always safe.
- Set `max_price` to cover the **full** cost — item price **+ tax + shipping/handling** — not just the item. It's the total ceiling Zinc won't exceed, so too-low a value trips `max_price_exceeded`.
- MPP orders authorize `max_price + $1` on the agent's payment method (Stripe card, Tempo wallet, or x402/USDC) — ensure sufficient balance/credit before placing.
## Retailer notes
**Shipping:** Ace Hardware has no flat free-shipping threshold — shipping is added per order, so leave room for it in `max_price`.
## Support
- Email: support@zinc.com
- Book a call with our CEO: https://cal.com/zinc-ian/15min
- Discord: https://discord.gg/cuXgfczYfj