# Commands

Every change you can make. Run one through its endpoint, preview it with `POST /plans` or `?preview=true`, or from an agent with `plan_change`. `GET /commands` lists the ones your key may run, with their JSON schemas. See [How changes work](/docs/concepts/changes) for risk levels and approvals.

## Subscriptions

### Re-check subscriptions against Shopify

`subscriptions.reconcile`

Re-check every subscription against Shopify and fix anything out of date. Runs in the background; check progress with get_sync_status. Safe to run any time: it only reads from Shopify and updates our copy.

**Risk:** low · **Undo:** no · **Permission:** `subscriptions:sync` · **Who:** staff, API keys, agents, built-in agents

**Endpoint:** `POST /api/v1/subscriptions/reconcile`

No input.

### Skip the next delivery

`subscriptions.skip_next`

Skip the next delivery. The next charge moves on by one delivery interval (e.g. a month). Good for customers with too much product who'd otherwise cancel.

**Risk:** low · **Undo:** yes · **Permission:** `subscriptions:write` · **Who:** staff, API keys, agents, built-in agents, customers

**Endpoint:** `POST /api/v1/subscriptions/:subscriptionId/skip`

| Field | Type | Required | Description |
|---|---|---|---|
| `subscriptionId` | string | Yes | Our subscription id, or the Shopify contract GID |

### Move the next delivery

`subscriptions.reschedule`

Move the next delivery to a specific date (tomorrow to a year out). Later deliveries follow on from the new date at the usual interval.

**Risk:** low · **Undo:** yes · **Permission:** `subscriptions:write` · **Who:** staff, API keys, agents, built-in agents, customers

**Endpoint:** `POST /api/v1/subscriptions/:subscriptionId/reschedule`

| Field | Type | Required | Description |
|---|---|---|---|
| `subscriptionId` | string | Yes | Our subscription id, or the Shopify contract GID |
| `date` | string (date) | Yes | New next delivery date, YYYY-MM-DD in the store's timezone |

### Pause a subscription

`subscriptions.pause`

Pause a subscription: no charges or deliveries until it's resumed.

**Risk:** medium · **Undo:** yes · **Permission:** `subscriptions:write` · **Who:** staff, API keys, agents, built-in agents, customers

**Endpoint:** `POST /api/v1/subscriptions/:subscriptionId/pause`

| Field | Type | Required | Description |
|---|---|---|---|
| `subscriptionId` | string | Yes | Our subscription id, or the Shopify contract GID |

### Resume a subscription

`subscriptions.resume`

Resume a paused subscription. If its next delivery date has passed, the next delivery is tomorrow at the usual time.

**Risk:** low · **Undo:** yes · **Permission:** `subscriptions:write` · **Who:** staff, API keys, agents, built-in agents, customers

**Endpoint:** `POST /api/v1/subscriptions/:subscriptionId/resume`

| Field | Type | Required | Description |
|---|---|---|---|
| `subscriptionId` | string | Yes | Our subscription id, or the Shopify contract GID |

### Cancel a subscription

`subscriptions.cancel`

Cancel a subscription. No more charges or deliveries. Record why: it drives which save offers are worth making. Customers can always cancel in one step.

**Risk:** high · **Undo:** no · **Permission:** `subscriptions:write` · **Who:** staff, API keys, agents, built-in agents, customers

**Endpoint:** `POST /api/v1/subscriptions/:subscriptionId/cancel`

| Field | Type | Required | Description |
|---|---|---|---|
| `subscriptionId` | string | Yes | Our subscription id, or the Shopify contract GID |
| `reason` | `too_much` · `too_expensive` · `taking_break` · `not_needed` · `didnt_like` · `other` | Yes |  |
| `comment` | string |  |  |

### Keep a customer with an offer

`subscriptions.accept_save_offer`

Take the offer shown to a customer who was about to cancel (skip, deliver later, less often, pause, swap, or a discount) instead of cancelling. Get the offer first with get_save_offer for the customer's reason; only that offer can be accepted. Recorded as a save in the retention report.

**Risk:** medium · **Undo:** no · **Permission:** `subscriptions:write` · **Who:** customers, customers' agents, staff, agents, API keys

**Endpoint:** `POST /api/v1/subscriptions/:subscriptionId/save`

| Field | Type | Required | Description |
|---|---|---|---|
| `subscriptionId` | string | Yes | Our subscription id, or the Shopify contract GID |
| `reason` | `too_much` · `too_expensive` · `taking_break` · `not_needed` · `didnt_like` · `other` | Yes |  |
| `offer` | `skip` · `later` · `less_often` · `pause` · `swap` · `discount` | Yes | The offer's kind, from get_save_offer |
| `variantId` | string (starts_with) |  | For swap: the variant chosen |

### Email a card update link

`subscriptions.send_payment_update`

Email the customer a secure Shopify link to update the card on this subscription. Use it after a declined or expired card, or when a customer asks. At most once an hour per subscription.

**Risk:** low · **Undo:** no · **Permission:** `subscriptions:write` · **Who:** staff, API keys, agents, built-in agents, customers

**Endpoint:** `POST /api/v1/subscriptions/:subscriptionId/payment-update`

| Field | Type | Required | Description |
|---|---|---|---|
| `subscriptionId` | string | Yes | Our subscription id, or the Shopify contract GID |

### Change a quantity

`subscriptions.change_quantity`

Change how many of an item come in each delivery. The price per item stays the same; the charge changes with the quantity.

**Risk:** low · **Undo:** yes · **Permission:** `subscriptions:write` · **Who:** staff, API keys, agents, built-in agents, customers

**Endpoint:** `POST /api/v1/subscriptions/:subscriptionId/quantity`

| Field | Type | Required | Description |
|---|---|---|---|
| `subscriptionId` | string | Yes | Our subscription id, or the Shopify contract GID |
| `lineId` | string |  | Which line (our line id or the Shopify line GID). Optional when there's only one. |
| `quantity` | integer (1–99) | Yes | How many of this item in each delivery |

### Change how often it delivers

`subscriptions.change_frequency`

Change how often deliveries come, to another frequency on the same offer (e.g. every 4 weeks instead of every 2). The next charge date stays; later ones follow the new frequency.

**Risk:** low · **Undo:** yes · **Permission:** `subscriptions:write` · **Who:** staff, API keys, agents, built-in agents, customers

**Endpoint:** `POST /api/v1/subscriptions/:subscriptionId/frequency`

| Field | Type | Required | Description |
|---|---|---|---|
| `subscriptionId` | string | Yes | Our subscription id, or the Shopify contract GID |
| `every` | integer (1–52) | Yes | Deliver every N intervals |
| `interval` | `DAY` · `WEEK` · `MONTH` · `YEAR` | Yes |  |

### Swap to another variant

`subscriptions.swap_variant`

Switch an item to another variant of the same product (size, flavour, grind). The subscription discount carries over to the new variant's price.

**Risk:** low · **Undo:** yes · **Permission:** `subscriptions:write` · **Who:** staff, API keys, agents, built-in agents, customers

**Endpoint:** `POST /api/v1/subscriptions/:subscriptionId/variant`

| Field | Type | Required | Description |
|---|---|---|---|
| `subscriptionId` | string | Yes | Our subscription id, or the Shopify contract GID |
| `lineId` | string |  | Which line (our line id or the Shopify line GID). Optional when there's only one. |
| `variantId` | string (starts_with) | Yes | Another variant of the same product |

### Change the shipping address

`subscriptions.change_address`

Ship future deliveries to a new address. The shipping price and method stay as they are, and Shopify refuses an address the store doesn't ship to. The address isn't kept in the activity log.

**Risk:** low · **Undo:** no · **Permission:** `subscriptions:write` · **Who:** staff, API keys, agents, built-in agents, customers

**Endpoint:** `POST /api/v1/subscriptions/:subscriptionId/address`

| Field | Type | Required | Description |
|---|---|---|---|
| `subscriptionId` | string | Yes | Our subscription id, or the Shopify contract GID |
| `address` | object | Yes | Where future deliveries should go |
| `address.firstName` | string |  |  |
| `address.lastName` | string |  |  |
| `address.company` | string |  |  |
| `address.address1` | string | Yes |  |
| `address.address2` | string |  |  |
| `address.city` | string | Yes |  |
| `address.provinceCode` | string |  | Province or state code, e.g. ON or CA |
| `address.countryCode` | string | Yes | Two-letter country code, e.g. CA |
| `address.zip` | string |  |  |
| `address.phone` | string |  |  |

## Offers

### Create an offer

`offers.create`

Offer subscriptions on products: a name, one or more delivery frequencies, and an optional discount. Shoppers see it on the product page and choose a frequency at checkout.

**Risk:** medium · **Undo:** yes · **Permission:** `offers:write` · **Who:** staff, API keys, agents, built-in agents

**Endpoint:** `POST /api/v1/offers`

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | Yes | Shown to shoppers, e.g. "Subscribe & save" |
| `description` | string |  |  |
| `frequencies` | object[] | Yes | Delivery options shoppers can choose from |
| `discount` | any | Yes | Applied to every subscription order; null for no discount |
| `productIds` | string (starts_with)[] | Yes |  |

### Change an offer

`offers.update`

Change an offer's name, description, delivery frequencies, discount or products. Existing subscriptions keep the terms they started with; changes apply to new subscriptions.

**Risk:** medium · **Undo:** yes · **Permission:** `offers:write` · **Who:** staff, API keys, agents, built-in agents

**Endpoint:** `PATCH /api/v1/offers/:id`

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | string | Yes |  |
| `name` | string |  |  |
| `description` | string |  |  |
| `frequencies` | object[] |  |  |
| `discount` | any |  |  |
| `productIds` | string (starts_with)[] |  | The full set of products; replaces the current set |

### Stop offering a subscription

`offers.delete`

Stop offering a subscription option. It disappears from product pages. Existing subscriptions keep running on the terms they started with.

**Risk:** high · **Undo:** no · **Permission:** `offers:write` · **Who:** staff, API keys, agents, built-in agents

**Endpoint:** `DELETE /api/v1/offers/:id`

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | string | Yes |  |

## Billing

### Retry a failed payment now

`billing.retry_now`

Retry a failed charge now instead of waiting for the next scheduled retry, e.g. after the customer says they've updated their card. Charges the customer's payment method.

**Risk:** financial · **Undo:** no · **Permission:** `billing:write` · **Who:** staff, API keys, agents, built-in agents

**Endpoint:** `POST /api/v1/billing/cycles/:cycleId/retry`

| Field | Type | Required | Description |
|---|---|---|---|
| `cycleId` | string | Yes |  |

## Imports

### Upload subscriptions to import

`imports.create`

Upload a subscriptions export from another app (Recharge, Skio, Appstle, Loop, Seal, Shopify Subscriptions or a spreadsheet). Nothing is created yet: each subscription is checked against the store (customer, saved card, products, date) in the background. Read the import to see what's ready and what needs fixing, then run imports.start.

**Risk:** low · **Undo:** no · **Permission:** `imports:write` · **Who:** staff, API keys, agents

**Endpoint:** `POST /api/v1/imports`

| Field | Type | Required | Description |
|---|---|---|---|
| `csv` | string | Yes | The export from the other app, as CSV text, header row first |
| `filename` | string |  |  |
| `source` | `recharge` · `skio` · `appstle` · `loop` · `seal` · `shopify` · `other` |  | Which app it came from. Guessed from the columns when left out. |

### Create the imported subscriptions

`imports.start`

Create the import's ready subscriptions in Shopify, keeping each one's next charge date, price and schedule. They're created in the background, one at a time. They don't bill until imports.release_billing, so the old app has time to be switched off. Subscriptions with problems are left out.

**Risk:** medium · **Undo:** no · **Permission:** `imports:write` · **Who:** staff, API keys, agents

**Endpoint:** `POST /api/v1/imports/:importId/start`

| Field | Type | Required | Description |
|---|---|---|---|
| `importId` | string | Yes | The import's id, from imports.create |
| `limit` | integer (1–100000) |  | Import only this many (e.g. 10) to check them in Shopify first. The rest stay ready. |

### Start billing imported subscriptions

`imports.release_billing`

Start billing an import's subscriptions here. Run it only once the old app has stopped charging them (turned off or uninstalled), or customers will be charged twice. Any whose charge date passed while waiting move to their next date, since the old app charged them.

**Risk:** high · **Undo:** no · **Permission:** `imports:write` · **Who:** staff, API keys, agents

**Endpoint:** `POST /api/v1/imports/:importId/release-billing`

| Field | Type | Required | Description |
|---|---|---|---|
| `importId` | string | Yes | The import's id, from imports.create |

### Throw away an import

`imports.discard`

Throw away an import that hasn't created anything yet, e.g. to upload a corrected file. Imports that have created subscriptions can't be discarded.

**Risk:** low · **Undo:** no · **Permission:** `imports:write` · **Who:** staff, API keys, agents

**Endpoint:** `DELETE /api/v1/imports/:importId`

| Field | Type | Required | Description |
|---|---|---|---|
| `importId` | string | Yes | The import's id, from imports.create |

### Connect Recharge

`imports.connect`

Connect the app the store is moving from with an API token from it, so subscriptions (and cards held at Stripe, Braintree or Authorize.net) can be fetched directly, and cancelled there when billing switches over. For Recharge the token needs: read_subscriptions, read_customers, read_payment_methods, write_subscriptions. The token is stored encrypted and never shown again.

**Risk:** medium · **Undo:** no · **Permission:** `imports:write` · **Who:** staff, API keys

**Endpoint:** `POST /api/v1/imports/connections`

Contains a secret (`token`), so it's hidden in the activity log and can't wait as a plan.

| Field | Type | Required | Description |
|---|---|---|---|
| `source` | `recharge` | Yes | The app to connect. Recharge for now. |
| `token` | string | Yes | An API token created in that app |

### Disconnect Recharge

`imports.disconnect`

Remove the stored API token for the app the store moved from. Delete the token in that app too.

**Risk:** low · **Undo:** no · **Permission:** `imports:write` · **Who:** staff, API keys, agents

**Endpoint:** `DELETE /api/v1/imports/connections/:source`

| Field | Type | Required | Description |
|---|---|---|---|
| `source` | `recharge` | Yes |  |

### Fetch subscriptions from Recharge

`imports.fetch`

Fetch every active subscription from the connected app (Recharge) and check them against the store, like an upload but always current. Nothing is created yet. Read the import, then run imports.start.

**Risk:** low · **Undo:** no · **Permission:** `imports:write` · **Who:** staff, API keys, agents

**Endpoint:** `POST /api/v1/imports/connections/:source/fetch`

| Field | Type | Required | Description |
|---|---|---|---|
| `source` | `recharge` | Yes |  |

## Integrations

### Connect Klaviyo

`integrations.connect_klaviyo`

Send subscription events (started, cancelled, upcoming charge, order placed, payment failed...) to the store's Klaviyo account, for its own flows. The private API key needs: Accounts: Read, Events: Full, Profiles: Full.

**Risk:** medium · **Undo:** no · **Permission:** `integrations:write` · **Who:** staff, API keys

**Endpoint:** `POST /api/v1/integrations/klaviyo`

Contains a secret (`apiKey`), so it's hidden in the activity log and can't wait as a plan.

| Field | Type | Required | Description |
|---|---|---|---|
| `apiKey` | string | Yes | A Klaviyo private API key (pk_...) |

### Disconnect Klaviyo

`integrations.disconnect_klaviyo`

Stop sending subscription events to Klaviyo and remove the stored key.

**Risk:** low · **Undo:** no · **Permission:** `integrations:write` · **Who:** staff, API keys, agents

**Endpoint:** `DELETE /api/v1/integrations/klaviyo`

No input.

## Store

### Change store settings

`shop.update_settings`

Change store-wide subscription settings: sender name, support email, the failed-payment retry schedule, what happens after the final retry, and which customer emails are sent. Only the fields you pass change.

**Risk:** medium · **Undo:** yes · **Permission:** `settings:write` · **Who:** staff, API keys, agents, built-in agents

**Endpoint:** `PATCH /api/v1/settings`

| Field | Type | Required | Description |
|---|---|---|---|
| `senderName` | string |  | Name shown on subscriber emails and texts. |
| `supportEmail` | string (email) |  | Where subscriber replies go. null to use the store's contact email. |
| `accountPageUrl` | string (uri) |  | The store's Subscriptions page in customer accounts, copied from the checkout and accounts editor. Customer emails and links go there. null to use the customer account home. |
| `dunningRetryDays` | integer[] |  | Days after a failed charge to retry it, in order. |
| `afterFinalRetry` | `pause` · `cancel` |  | What happens to a subscription when every retry has failed. |
| `billingPaused` | boolean |  | Stop all new charges for this store. Charges already sent still finish. |
| `notices` | object |  | Which customer emails Usual sends. Turn off any you'd rather send yourself. |
| `notices.confirmation` | boolean | Yes | Email subscribers their terms and how to cancel when they sign up. |
| `notices.upcomingCharge` | boolean | Yes | Remind subscribers before each renewal charge. |
| `notices.upcomingChargeDays` | integer (1–14) | Yes | How many days before a charge to remind them. |
| `notices.paymentFailed` | boolean | Yes | Tell subscribers when a renewal payment fails. |
| `notices.cancelled` | boolean | Yes | Confirm cancellations by email. |
| `retention` | object |  | What customers are offered when they go to cancel. |
| `retention.offers` | object | Yes | Which offers customers can see when they cancel. One fits their reason; cancelling stays one click. |
| `retention.offers.skip` | boolean | Yes | Skip the next delivery instead. |
| `retention.offers.later` | boolean | Yes | Get the next delivery two weeks later. |
| `retention.offers.lessOften` | boolean | Yes | Switch to a less frequent schedule from the subscription's offer. |
| `retention.offers.pause` | boolean | Yes | Pause until they resume. |
| `retention.offers.swap` | boolean | Yes | Swap to another option of the same product. |
| `retention.offers.discount` | boolean | Yes | A discount on the next few deliveries. |
| `retention.discountPercent` | integer (5–50) | Yes | The discount, in percent. |
| `retention.discountDeliveries` | integer (1–6) | Yes | How many deliveries the discount lasts. |

## Keys

### Create a key

`api_client.create`

Create an API key or agent credential. The token is returned once and never shown again.

**Risk:** high · **Undo:** no · **Permission:** `*` · **Who:** staff

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | Yes |  |
| `kind` | `api_key` · `agent` | Yes | agent: an AI agent. Its writes always need a preview first, and riskier ones need approval. |
| `scopes` | `settings:read` · `settings:write` · `audit:read` · `subscriptions:read` · `subscriptions:write` · `subscriptions:sync` · `offers:read` · `offers:write` · `billing:read` · `billing:write` · `imports:read` · `imports:write` · `integrations:write`[] | Yes |  |

### Revoke a key

`api_client.revoke`

Revoke an API key or agent credential. It stops working immediately.

**Risk:** high · **Undo:** no · **Permission:** `*` · **Who:** staff

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | string | Yes |  |
