{
  "schema": "aaes/capability-pack/1",
  "provider": "shopify",
  "version": "1.0.0",
  "description": "Shopify: read orders, customers, products and refunds, and create refund records, submit payment refunds and optionally notify customers.",
  "notes": "The endpoint host 'shopify-store.invalid' is a reserved non-production placeholder (an RFC 2606 reserved TLD): substitute the store's own host before enabling, in the endpoints, the resource locators and the egress allowlist — the allowlist matches exactly, so all three must be substituted together, and substitution is a required deployment step. The real substitution shape is '{store}.myshopify.com' (the store's myshopify.com subdomain; myshopify.com subdomains are real vendor hosts but are customer-specific, so the pack ships the reserved pattern). ORDER_ID in the refund endpoint paths is a placeholder too: refunds are sub-resources of one order, so the capability is bound to a concrete order id before enabling.\n\nThis pack pins the Admin REST API version 2026-07 deliberately: Shopify serves dated versions, and a version bump is a reviewed migration, not a silent edit. This pack registers only endpoints the official REST Admin API reference documents: listing orders, listing customers, listing products, listing an order's refunds, and creating a refund. Reads are collection reads: single-record reads (GET /admin/api/2026-07/orders/{order_id}.json, GET /admin/api/2026-07/customers/{customer_id}.json, GET /admin/api/2026-07/products/{product_id}.json, GET.../refunds/{refund_id}.json) and count endpoints are not registered by this pack.\n\nOnly the last 60 days of orders are accessible by default; older orders require the read_all_orders scope in addition to read_orders, which is a deployment decision, not a pack default.\n\nDocumented exclusions: this pack does not register order creation, update, deletion, cancellation, close or re-open (order creation through the API collects no payment, but it still records a customer-facing purchase, and cancellation moves obligations the other way), product creation, update or deletion, customer creation, update, deletion or account-invite sending, fulfillment operations, gift cards, discounts, and the entire GraphQL Admin API — Shopify marks the REST Admin API a legacy API for new public apps, and new deployments should weigh the GraphQL surface under its own review.\n\nRefund creation is the one write in the pack because money moves: it is pay at the minimum R3 tier. Credential custody was reviewed on 2026-09-21 against this pack's custody model (an Admin API access token held in the customer's vault), with the following findings.\n\nIssuance authority and delegation: issuance depends on the app generation, and the two must not be conflated — legacy admin-created custom apps had Shopify pre-generate a non-expiring Admin API access token at install (a flow Shopify documents as no longer creatable, so it applies only to apps the deployment already operates), while a new app is created in the Shopify Dev Dashboard and obtains tokens through a grant — the client credentials grant for a server-side integration acting on the organization's own stores, token exchange or the authorization code grant for embedded and standalone apps.\n\nScope: access scopes are granted per app — read_orders, read_customers and read_products for the reads, plus write_orders only if the refund-creation capability is enabled; Shopify requires apps to be approved for protected customer data before order and customer reads return it, and orders older than 60 days require read_all_orders as a deliberate deployment decision, not a pack default. Audience: no audience mechanism is documented — the token authorizes against the store's own myshopify.com host, and audience binding beyond that is not established. TTL: legacy admin-created tokens do not expire and remain valid until the app is uninstalled or the client secret is revoked; client credentials tokens are valid for 24 hours (expires_in 86399) and renewed by repeating the same request; expiring offline tokens pair a 1-hour access token with a 90-day refresh token.\n\nRefresh ownership: an external deployment process owns minting and renewal — the client credentials grant renews by repeating the request, and Shopify handles refresh retirements as new tokens are acquired — and writes the fresh header value into the vault; the executor injects the stored value verbatim and performs no token lifecycle itself, and a stale value fails closed.\n\nRevocation: uninstalling the app or revoking the client secret ends a token's access, with requests then returning 401. Executor access and replay/exposure: the executor reads only the vault reference; the token is replayable while valid, and its window depends on the generation — 24 hours for a new client-credentials deployment, unbounded for a legacy token until uninstall or secret revocation.\n\nAlternate access paths: the Shopify admin UI and other apps retain access outside this pack, and uninstalling this app or revoking its client secret closes only this path. Credential custody review is complete. Bind the remaining owner placeholders before deployment.",
  "credential": {
    "secret_ref_id": "secret-ref-shopify-admin-token",
    "custody": "customer_vault",
    "inject": "header",
    "inject_name": "X-Shopify-Access-Token",
    "note": "A Shopify Admin API access token held in the client's own secret manager. The stored material must be the complete X-Shopify-Access-Token header value: the token itself, with no scheme. customer_vault is the only generally-supported production custody model here — identity_federation and customer_broker are refused at startup and inline is lab-only.\n\nIssuance depends on the app generation, and the two must not be conflated. Supported legacy credentials: apps created directly in the Shopify admin (admin-created custom apps) had Shopify pre-generate a non-expiring Admin API access token at install — Shopify documents that these apps can no longer be created, so that flow applies only to apps the deployment already operates, and it is not available to a new 2026 deployment; such tokens remain valid until the app is uninstalled or the client secret is revoked, and rotation is an external administrative action.\n\nCurrent issuance and renewal flows: a new app is created in the Shopify Dev Dashboard and obtains tokens through a grant — for a server-side integration acting on the organization's own stores, the client credentials grant returns a token valid for 24 hours that is renewed by repeating the same request; embedded and standalone apps use token exchange or the authorization code grant, and Shopify directs public apps to expiring offline tokens (1-hour access token with a 90-day refresh token, and refresh retirements handled by Shopify as new tokens are acquired).\n\nEither way an external deployment process MUST mint and renew tokens and write the fresh header value into the vault — the executor injects the stored value verbatim and performs no token lifecycle itself, and a stale value fails closed. Uninstalling the app or revoking the client secret ends a token's access (requests then return 401).\n\nLeast privilege: grant the token only the scopes these capabilities need — read_orders, read_customers and read_products for the reads, plus write_orders only if the refund-creation capability is enabled — and Shopify requires apps to be approved for protected customer data before order and customer reads return it.\n\nExecutor hygiene: the value must live only in executor memory for the lifetime of a call, must never be written to logs, traces or diagnostics, and must not appear in crash dumps; a deployment that cannot meet that bar must not enable the pack."
  },
  "capabilities": [
    {
      "capability_id": "shopify.orders.read",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "financial",
      "egress_allowlist": [
        "shopify-store.invalid"
      ],
      "endpoint": "https://shopify-store.invalid/admin/api/2026-07/orders.json",
      "method": "GET",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-shopify-admin-token",
      "inject": "header",
      "inject_name": "X-Shopify-Access-Token",
      "actions": [
        "read"
      ],
      "resource_id": "shopify-store-orders",
      "description": "Retrieve a list of orders (filterable by status and date). Order records carry totals, payment status and transaction references (financial data) alongside customer names, emails, phones and addresses (personal data), and Shopify treats them as protected customer data; the resource is classified as financial data. The registered path covers the collection read only — single-order reads and the order count endpoint are not registered by this pack.",
      "requires_review": true
    },
    {
      "capability_id": "shopify.customers.read",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "pii",
      "egress_allowlist": [
        "shopify-store.invalid"
      ],
      "endpoint": "https://shopify-store.invalid/admin/api/2026-07/customers.json",
      "method": "GET",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-shopify-admin-token",
      "inject": "header",
      "inject_name": "X-Shopify-Access-Token",
      "actions": [
        "read"
      ],
      "resource_id": "shopify-store-customers",
      "description": "Retrieve a list of customers: names, email addresses, phone numbers, addresses and marketing-consent state — personal data, classified as pii (personal data) and treated by Shopify as protected customer data. The registered path covers the collection read only — single-customer reads, customer search, customer order history and the customer count endpoint are not registered by this pack.",
      "requires_review": true
    },
    {
      "capability_id": "shopify.products.read",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "internal",
      "egress_allowlist": [
        "shopify-store.invalid"
      ],
      "endpoint": "https://shopify-store.invalid/admin/api/2026-07/products.json",
      "method": "GET",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-shopify-admin-token",
      "inject": "header",
      "inject_name": "X-Shopify-Access-Token",
      "actions": [
        "read"
      ],
      "resource_id": "shopify-store-products",
      "description": "Retrieve a list of products with variants, prices, tags and publication state — the store's internal catalogue, classified as internal data. The registered path covers the collection read only — single-product reads and the product count endpoint are not registered by this pack.",
      "requires_review": true
    },
    {
      "capability_id": "shopify.orders.refunds.read",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "financial",
      "egress_allowlist": [
        "shopify-store.invalid"
      ],
      "endpoint": "https://shopify-store.invalid/admin/api/2026-07/orders/ORDER_ID/refunds.json",
      "method": "GET",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-shopify-admin-token",
      "inject": "header",
      "inject_name": "X-Shopify-Access-Token",
      "actions": [
        "read"
      ],
      "resource_id": "shopify-store-orders",
      "description": "Retrieve the list of refunds for one order: refund amounts, restock instructions and refund transactions — records of money returned to customers, classified as financial data. ORDER_ID is a placeholder the deployment binds to a concrete order before enabling. The registered path covers the collection read only — single-refund reads are not registered by this pack.",
      "requires_review": true
    },
    {
      "capability_id": "shopify.orders.refunds.create",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R3",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "financial",
      "egress_allowlist": [
        "shopify-store.invalid"
      ],
      "endpoint": "https://shopify-store.invalid/admin/api/2026-07/orders/ORDER_ID/refunds.json",
      "method": "POST",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-shopify-admin-token",
      "inject": "header",
      "inject_name": "X-Shopify-Access-Token",
      "actions": [
        "create",
        "pay",
        "send"
      ],
      "resource_id": "shopify-store-orders",
      "cost_ceiling_usd": 500,
      "description": "Create a refund for one order, with three distinct effects.\n\nRefund record: a 201 response confirms creation of the Refund resource, including line items and restock instructions.\n\nPayment: the request submits refund transactions to the payment gateway. Transactions are typically prepared through refunds/calculate; change their suggested_refund kind to refund before submission. Each transaction has its own processing outcome. A 201 does not prove that every transaction settled or that funds reached the customer; check individual outcomes through the registered refund read.\n\nNotification: the request's notify option sends a refund email to the customer. The declared actions are create, pay, and send at R3.\n\nThe registered cost ceiling limits the amount declared to AAES: the gateway refuses higher declarations and commits the ceiling against the work budget. It does not limit the amount encoded in the provider request. Review currency, minor units, and restock instructions separately.\n\nBind ORDER_ID before enabling. The package excludes refunds/calculate; a human can perform that calculation in Shopify admin without moving money.",
      "requires_review": true
    }
  ],
  "resources": [
    {
      "resource_id": "shopify-store-orders",
      "kind": "api",
      "locator": "https://shopify-store.invalid/admin/api/2026-07/orders.json",
      "data_class": "financial",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN"
    },
    {
      "resource_id": "shopify-store-customers",
      "kind": "api",
      "locator": "https://shopify-store.invalid/admin/api/2026-07/customers.json",
      "data_class": "pii",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN"
    },
    {
      "resource_id": "shopify-store-products",
      "kind": "api",
      "locator": "https://shopify-store.invalid/admin/api/2026-07/products.json",
      "data_class": "internal",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN"
    }
  ],
  "review": {
    "state": "library",
    "date": "2026-09-22",
    "sources": [
      "https://shopify.dev/docs/api/admin-rest/2026-07/resources/order (retrieved 2026-09-21)",
      "https://shopify.dev/docs/api/admin-rest/2026-07/resources/refund (retrieved 2026-09-21)",
      "https://shopify.dev/docs/api/admin-rest/2026-07/resources/customer (retrieved 2026-09-21)",
      "https://shopify.dev/docs/api/admin-rest/2026-07/resources/product (retrieved 2026-09-21)",
      "https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens (retrieved 2026-09-21)"
    ],
    "reviewer_id": "aaes-eng"
  }
}
