---
name: knot-prototype-shopping
description: >
  Prototype a shopping experience using Knot's development API — link a merchant account,
  add products to a cart, checkout, and retrieve order confirmation data. No SDK or production
  setup required. Use when: (1) "prototype a shopping bot", (2) "build a purchasing agent
  with Knot", (3) "test the shopping flow", (4) "prototype cart and checkout", (5) "build a
  buy-it-for-me feature", (6) "prototype merchant purchasing", (7) "create a shopping
  assistant with Knot". This skill is development-only and never touches production.
metadata:
  author: Knot
  version: "1.0"
---

# Prototype with Shopping

Build a working shopping prototype using Knot's development API — link a merchant account, add products to a cart, set a delivery address, checkout, and retrieve the resulting transaction. No SDK integration or production credentials required. Webhooks are used to receive cart details and order confirmation data.

This is ideal for prototyping use cases like:
- A shopping bot where a user says "buy me dry erase markers" and the purchase is fulfilled
- An expense management app that handles purchasing and receipt capture
- A concierge service that places orders on behalf of users
- An AI agent that can browse, cart, and checkout at real merchants

## API Reference

### POST /merchant/list

Returns merchants available for shopping.

**Request body:**
```json
{
  "type": "shopping"
}
```

**Response (200):**
```json
[
  {
    "id": 44,
    "name": "Amazon",
    "category": "Online Shopping",
    "logo": "https://..."
  }
]
```

### POST /development/accounts/link

Links a merchant account in development without the SDK.

**Request body:**
```json
{
  "external_user_id": "string (required)",
  "merchant_id": "integer (required)"
}
```

**Response (200):**
```json
{ "message": "Success" }
```

### GET /accounts/get

Returns linked merchant accounts for a user.

**Query parameters:**
- `external_user_id` (required) — your unique identifier for the user

**Response (200):**
```json
[
  {
    "merchant": { "id": 44, "name": "Amazon", "logo": "https://..." },
    "connection": { "status": "connected", "scopes": [] },
    "lifecycle": { "status": null },
    "last_user_action": { "attempted_at": null, "status": null }
  }
]
```

### POST /cart (Sync Cart)

Adds products to a user's merchant cart. Optionally includes a delivery address.

**Request body:**
```json
{
  "external_user_id": "string (required)",
  "merchant_id": "integer (required)",
  "products": [
    {
      "external_id": "string (required) — the merchant's product identifier",
      "fulfillment": {
        "id": "string (optional) — fulfillment option ID from SYNC_CART_SUCCEEDED webhook"
      }
    }
  ],
  "delivery_location": "(optional) — omit entirely if not needed",
  "simulate": "(optional) — set to \"failed\" to trigger a SYNC_CART_FAILED webhook in development"
}
```

When `delivery_location` is included, it must contain all of its sub-fields:
```json
{
  "delivery_location": {
    "first_name": "string (required)",
    "last_name": "string (required)",
    "phone_number": "string (required) — E.164 format, e.g. +12125551234",
    "address": {
      "line1": "string (required) — max 46 chars",
      "line2": "string (optional) — max 46 chars",
      "city": "string (required)",
      "region": "string (required) — ISO 3166-2 code, e.g. NY",
      "postal_code": "string (required) — 5-10 chars",
      "country": "string (required) — ISO 3166-1 alpha-2 code, e.g. US"
    },
    "set_as_default": "boolean (required) — whether to save as the user's default address"
  }
}
```

**Response (202):**
```json
{ "message": "Success" }
```

**Important:** `delivery_location` and `simulate` are both optional. However, `delivery_location` is all-or-nothing — if present, all sub-fields (`first_name`, `last_name`, `phone_number`, `address`, `set_as_default`) are required. If omitted entirely, the cart syncs without a delivery address. A delivery address must be present (either sent here or already on the merchant account) before checkout if the order requires delivery.

### POST /cart/checkout

Checks out the user's merchant cart.

**Request body:**
```json
{
  "external_user_id": "string (required)",
  "merchant_id": "integer (required)",
  "payment_method": "(optional) — not required in development; required in production for merchants without a payment method on file",
  "simulate": "(optional) — set to \"failed\" to trigger a CHECKOUT_FAILED webhook in development"
}
```

When `payment_method` is included, it must contain all of its sub-fields:
```json
{
  "payment_method": {
    "id": "string (required) — your unique identifier for the payment method",
    "jwe": "string (required) — encrypted card data via Retrieve JWK",
    "is_single_use": "boolean (required) — whether the payment method is single-use only"
  }
}
```

**Response (202):**
```json
{ "message": "Success" }
```

### GET /transactions/{id}

Retrieves a transaction by ID. Use the transaction IDs from the `CHECKOUT_SUCCEEDED` webhook.

**Response (200):** A full transaction object with products, pricing, payment methods, shipping, and order status. See the Transaction Object Reference below.

## Workflow

### Step 1: Get Development API Keys

First, check whether the user already has a saved API key from a previous session. Look for a `.env` file in the working directory (or a parent) containing `KNOT_DEV_API_KEY`. If found, use it and skip to Step 2.

If no saved key exists, the user needs their **development** credentials from the Knot Dashboard. Walk them through it:

1. Go to https://dashboard.knotapi.com/developers/keys
2. Make sure the **Development** environment is selected (not Production)
3. Copy the **Client ID** (click the copy button)
4. Click **View** on the **Secret** to reveal it, then copy it

Ask the user to provide both values. Once they do, construct the API key:

```
API Key = base64("CLIENT_ID:SECRET")
```

Use base64 encoding (e.g., run `echo -n "CLIENT_ID:SECRET" | base64` in the terminal) and set the resulting string as the `Authorization: Basic <key>` header for all subsequent API calls.

**Save the key for future sessions:** After constructing the base64-encoded API key, save it to a `.env` file in the working directory:

```
KNOT_DEV_API_KEY=<base64-encoded key>
```

If a `.env` file already exists, append the line. If `.gitignore` exists and doesn't already cover `.env`, add it. Mention to the user that this key is saved locally so they won't need to provide it again next time.

**Important:** These are development-only credentials. They only work against `https://development.knotapi.com`. Never use production credentials with this skill.

### Step 2: Discover Available Merchants

Fetch the list of merchants available for shopping:

```
POST https://development.knotapi.com/merchant/list
Authorization: Basic <base64(client_id:secret)>
Content-Type: application/json

{
  "type": "shopping"
}
```

Present the returned merchants to the user. The available merchants depend on which merchants are enabled for the client's account.

### Step 3: Set Up Webhooks

Before syncing a cart, the user must have a webhook endpoint configured to receive events. Walk them through it:

1. Go to https://dashboard.knotapi.com/developers/webhooks
2. Make sure the **Development** environment is selected
3. Add a webhook URL that can receive POST requests (e.g. a local server, ngrok tunnel, or a tool like https://webhook.site)

These webhooks are essential — cart details (products, pricing, fulfillment options, delivery address) are returned in `SYNC_CART_SUCCEEDED`, and transaction IDs for order confirmation are returned in `CHECKOUT_SUCCEEDED`.

### Step 4: Link a Merchant Account

For each merchant the user wants to shop at, link a development account. No SDK is required — this endpoint simulates a full merchant account link server-side.

```
POST https://development.knotapi.com/development/accounts/link
Authorization: Basic <base64(client_id:secret)>
Content-Type: application/json

{
  "external_user_id": "prototype-shopper-1",
  "merchant_id": <merchant_id>
}
```

The `external_user_id` can be any string — use something descriptive for the prototype session. Use the same `external_user_id` across merchants if the prototype involves a single user shopping at multiple merchants.

**Verify the link** by calling `GET /accounts/get?external_user_id=prototype-shopper-1` and confirming the connection status is `connected`.

### Step 5: Add Products to Cart

Add one or more products to the user's cart at a linked merchant:

```
POST https://development.knotapi.com/cart
Authorization: Basic <base64(client_id:secret)>
Content-Type: application/json

{
  "external_user_id": "prototype-shopper-1",
  "merchant_id": <merchant_id>,
  "products": [
    {
      "external_id": "<product_id>"
    }
  ]
}
```

The `external_id` is the merchant's product identifier. In development, any string works as a product ID to generate sample cart data.

**Wait for the `SYNC_CART_SUCCEEDED` webhook** before proceeding. This webhook contains the cart details you need:
- `cart.products[]` — each product with its `external_id` and `fulfillment` options (selected preference + alternatives)
- `cart.delivery_location` — the resolved delivery address (nullable)
- `cart.price` — `{ sub_total, adjustments[], total, currency }`

If you receive `SYNC_CART_FAILED` instead, check `data.reason` for the failure cause (e.g. "product out of stock").

#### With a delivery address

If the prototype requires delivery, include the full `delivery_location`:

```json
{
  "external_user_id": "prototype-shopper-1",
  "merchant_id": 44,
  "products": [
    { "external_id": "3324893784" }
  ],
  "delivery_location": {
    "first_name": "Ada",
    "last_name": "Lovelace",
    "phone_number": "+12125551234",
    "address": {
      "line1": "123 Main St",
      "city": "New York",
      "region": "NY",
      "postal_code": "10001",
      "country": "US"
    },
    "set_as_default": false
  }
}
```

#### Updating the cart

To change the delivery address or fulfillment preference after the initial sync, make the same `POST /cart` request again with updated values. Each call replaces the previous cart state. You will receive a new `SYNC_CART_SUCCEEDED` webhook with the updated cart details.

### Step 6: Checkout

Once you've received the `SYNC_CART_SUCCEEDED` webhook, check out:

```
POST https://development.knotapi.com/cart/checkout
Authorization: Basic <base64(client_id:secret)>
Content-Type: application/json

{
  "external_user_id": "prototype-shopper-1",
  "merchant_id": <merchant_id>
}
```

In development, no `payment_method` is required. The API returns `{ "message": "Success" }` immediately.

**Wait for the `CHECKOUT_SUCCEEDED` webhook** to get the transaction IDs. The webhook contains `data.transactions[]` — an array of `{ id }` objects. Most merchants generate a single transaction per purchase, but some generate multiple.

If you receive `CHECKOUT_FAILED` instead, check `data.reason` for the failure cause (e.g. "card declined").

### Step 7: Retrieve Order Confirmation

Use the transaction IDs from the `CHECKOUT_SUCCEEDED` webhook to retrieve the full order details:

```
GET https://development.knotapi.com/transactions/<transaction_id>
Authorization: Basic <base64(client_id:secret)>
```

Call this for each transaction ID received in the webhook. This returns the full transaction object with itemized products, pricing, payment methods, shipping details, and order status — everything needed to build an order confirmation UI.

### Step 8: Use the Data

Once the flow is working, ask the user how they want to build on it:

**Option A: Build the prototype in this session**

Use the working API flow to build the actual feature. For a shopping bot, this means wiring the cart and checkout calls into the bot's command handler. For an expense management app, this means capturing the transaction data post-checkout.

**Option B: Export the flow as a reference**

Save the complete request/response sequence to a Markdown file for sharing with the team:

```markdown
# Knot Shopping Prototype

Generated: {date}
Merchant: {merchant_name} (ID: {merchant_id})
External User ID: {external_user_id}

## Flow

### 1. Link Account
POST /development/accounts/link -> Success

### 2. Sync Cart
POST /cart
Products: {product_ids}
Delivery: {address summary}
-> Success

### 3. Checkout
POST /cart/checkout -> Success

### 4. Transaction
GET /transactions/{id}
Order Status: {order_status}
Total: {price.total} {price.currency}
Products:
  - {product.name} (qty: {quantity}) — {product.price.total}
```

## Transaction Object Reference

Each transaction returned by `GET /transactions/{id}` has this shape:

### Top-level fields

| Field | Type | Nullable | Description |
|-------|------|----------|-------------|
| `id` | string (UUID) | No | Unique transaction identifier |
| `external_id` | string | Yes | Merchant-provided order identifier |
| `datetime` | string (ISO 8601) | No | Transaction timestamp in UTC |
| `order_status` | enum | No | `ORDERED`, `BILLED`, `SHIPPED`, `DELIVERED`, `PICKED_UP`, `COMPLETED`, `REFUNDED`, `CANCELLED`, `FAILED`, `RETURNED`, `UNRECOGNIZED` |
| `url` | string | Yes | Direct link to order in merchant account |
| `price` | object | No | `{ sub_total, total, currency, adjustments[] }` |
| `products` | array | No | SKU-level items (see below) |
| `payment_methods` | array | No | Payment methods used (see below) |
| `shipping` | object | Yes | Delivery details. Null for digital/pickup orders. |
| `loyalty_membership` | object | Yes | Loyalty tier info. Null if none. |

### products[]

| Field | Type | Nullable | Description |
|-------|------|----------|-------------|
| `external_id` | string | Yes | Merchant-provided product identifier |
| `name` | string | No | Product name |
| `description` | string | Yes | Additional product details |
| `url` | string | Yes | Link to product page |
| `image_url` | string | Yes | Link to product image |
| `quantity` | integer | Yes | Number of units |
| `eligibility` | array of strings | No | Special spending categories (e.g. `"FSA/HSA"`) |
| `price` | object | Yes | `{ sub_total, total, unit_price }` — all nullable strings |
| `seller` | object | Yes | Seller info for marketplace products. Null when merchant is also the seller. |

### payment_methods[]

| Field | Type | Nullable | Description |
|-------|------|----------|-------------|
| `type` | enum | No | `CARD`, `APPLE_PAY`, `GOOGLE_PAY`, `AMAZON_PAY`, `PAYPAL`, `CASH_APP`, `VENMO`, `AFFIRM`, `KLARNA`, `GIFT_CARD`, `CASH`, `BANK_ACCOUNT`, `LOYALTY_POINTS`, `UNRECOGNIZED` |
| `brand` | string | Yes | Card network or store card name |
| `last_four` | string | Yes | Last four digits |
| `transaction_amount` | string | Yes | Amount charged to this method |
| `name` | string | Yes | Customer-provided label (e.g. "Work Card") |
| `external_id` | string | Yes | Merchant-provided payment method identifier |

### price.adjustments[]

Each adjustment: `type` (enum: `DISCOUNT`, `TAX`, `TIP`, `FEE`, `REFUND`, `UNRECOGNIZED`), `label` (nullable string), `amount` (string — positive increases total, negative decreases).

### shipping.location

| Field | Type | Nullable | Description |
|-------|------|----------|-------------|
| `first_name` | string | Yes | Recipient's first name |
| `last_name` | string | Yes | Recipient's last name |
| `address` | object | Yes | `{ line1, line2, city, region, postal_code, country }` |

## Webhook Events Reference

These webhooks are essential to the shopping flow. Cart details are only available in `SYNC_CART_SUCCEEDED`, and transaction IDs for order confirmation are only available in `CHECKOUT_SUCCEEDED`. You must receive these webhooks to proceed through the flow.

### SYNC_CART_SUCCEEDED

Fired when products are successfully added to a cart. This is where you get the cart details. Contains:
- `cart.products[]` — each with `external_id` and `fulfillment` (selected preference + alternative `options[]`)
- `cart.delivery_location` — the resolved delivery address (nullable)
- `cart.price` — `{ sub_total, adjustments[], total, currency }`

Each `fulfillment` object includes: `id`, `type` (`UNSCHEDULED_DELIVERY`, `SCHEDULED_DELIVERY`, `UNSCHEDULED_PICKUP`), `label`, `availabilityStart`, `availabilityEnd`, and `price`. Use `fulfillment.options[].id` in a subsequent `POST /cart` request to change the fulfillment preference.

### SYNC_CART_FAILED

Fired when adding products to a cart fails. Contains `data.reason` (e.g. "product out of stock").

### CHECKOUT_SUCCEEDED

Fired when checkout succeeds. This is where you get the transaction IDs. Contains `data.transactions[]` — an array of `{ id }` objects. Use each `id` with `GET /transactions/{id}` to retrieve the full order details.

### CHECKOUT_FAILED

Fired when checkout fails. Contains `data.reason` (e.g. "card declined").

## Rules

- **Development only.** This skill uses `https://development.knotapi.com` exclusively. Never use production credentials or the production base URL.
- **New user ID each run.** Reusing the same `external_user_id` across multiple prototype sessions can produce unexpected results. Increment or randomize the ID.
- **One operation at a time.** Do not make a second cart or checkout request for the same merchant account until the previous operation completes.
- **Cart is consumed on checkout.** After a successful checkout, the cart is gone. To checkout again, sync a new cart first.
- **Delivery address is all-or-nothing.** If `delivery_location` is present in the sync cart request, all fields are required. Omit it entirely if not needed.
- **Webhooks are required.** Cart details come from `SYNC_CART_SUCCEEDED` and transaction IDs come from `CHECKOUT_SUCCEEDED`. Always wait for the relevant webhook before proceeding to the next step.
- **Use the merchant list endpoint.** Always call `POST /merchant/list` with `type: shopping` to discover available merchants. Do not hardcode merchant IDs — available merchants vary by client.
