> ## Documentation Index
> Fetch the complete documentation index at: https://docs.deploybaseline.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Square

> Payments, refunds, orders, customers, catalog, locations, OAuth, and webhooks.

Stateful Square emulation of the v2 REST API at Square version `2026-09-16`. It covers payments with the sandbox test nonces, delayed capture, completion, and cancellation; refunds, including partial ones; orders whose totals are computed on the server from line items, taxes, discounts, and service charges; customers; the catalog, with temporary `#ids` mapped in `id_mappings`; locations and merchants. Writes take idempotency keys, lists page with cursors, and errors use Square's `errors` envelope. It also covers OAuth (authorization code and PKCE flows, refresh, revoke, token status) with scopes checked per endpoint, and webhook subscriptions whose events are signed in `x-square-hmacsha256-signature`. The tests drive it with the official [`square`](https://github.com/square/square-nodejs-sdk) SDK (46.0.0) and verify webhook signatures with its `WebhooksHelper.verifySignature`.

## At a glance

| | |
| - | - |
| Package | `@deploybaseline/square` |
| Port | `4000` when started alone; named services take consecutive ports |
| Config key | `square` |
| HTTP operations | 61, browsable at `http://localhost:3999/docs/square` while it runs ([API reference](/api-reference.md)) |
| Vendor spec | [square/connect-api-specification](https://github.com/square/connect-api-specification) at `0689c90` |

```bash CLI theme={null}
npx @deploybaseline/cli start --service square
```

```ts Programmatic theme={null}
import { createEmulator } from "@deploybaseline/cli";

const square = await createEmulator({ service: "square" });
// square.url is http://localhost:4000
```

```json baseline.config.json theme={null}
{
  "square": {
    "merchant": {
      "business_name": "Baseline Coffee",
      "currency": "USD"
    },
    "locations": [
      {
        "name": "Baseline Coffee Downtown"
      }
    ],
    "apps": [
      {
        "name": "My app",
        "application_id": "sandbox-sq0idb-my-app",
        "application_secret": "sandbox-sq0csb-my-app-secret",
        "redirect_uris": ["http://localhost:3000/square/callback"],
        "personal_access_token": "EAAAl-baseline-square-token"
      }
    ],
    "customers": [
      {
        "given_name": "Ada",
        "family_name": "Lovelace",
        "email_address": "ada@example.com"
      }
    ],
    "catalog": [
      {
        "type": "CATEGORY",
        "id": "#drinks",
        "category_data": {
          "name": "Drinks"
        }
      },
      {
        "type": "TAX",
        "id": "#sales-tax",
        "tax_data": {
          "name": "Sales tax",
          "percentage": "8.5"
        }
      },
      {
        "type": "ITEM",
        "id": "#latte",
        "item_data": {
          "name": "Latte",
          "categories": [
            {
              "id": "#drinks"
            }
          ],
          "tax_ids": ["#sales-tax"],
          "variations": [
            {
              "type": "ITEM_VARIATION",
              "id": "#latte-regular",
              "item_variation_data": {
                "name": "Regular",
                "price_money": {
                  "amount": 450,
                  "currency": "USD"
                }
              }
            }
          ]
        }
      }
    ]
  }
}
```

## Start

```bash theme={null}
npx @deploybaseline/cli start --service square
```

Square starts only when named in config or with `--service`. When it is the only enabled service, it listens on `http://localhost:4000`. Without config, the seller is `Default Test Account` with one main location and a default application (`sandbox-sq0idb-baseline`).

Point the SDK at the emulator with `baseUrl`:

```ts theme={null}
import { SquareClient } from "square";

const square = new SquareClient({
  token: "EAAAl-baseline-square-token",
  baseUrl: process.env.SQUARE_EMULATOR_URL ?? "http://localhost:4000",
});

const { payment } = await square.payments.create({
  sourceId: "cnon:card-nonce-ok",
  idempotencyKey: crypto.randomUUID(),
  amountMoney: { amount: BigInt(1000), currency: "USD" },
});
```

## URL mapping

| Real Square URL | Emulator URL |
| - | - |
| `https://connect.squareupsandbox.com/v2/...` | `$SQUARE_EMULATOR_URL/v2/...` |
| `https://connect.squareup.com/v2/...` | `$SQUARE_EMULATOR_URL/v2/...` |
| `https://connect.squareupsandbox.com/oauth2/*` | `$SQUARE_EMULATOR_URL/oauth2/*` |

## Auth

Requests send `Authorization: Bearer <token>`.

* A token the CLI accepts (for example `test_token_admin`) acts as the first application's personal access token, with every scope.
* `apps[].personal_access_token` acts as that application, with every scope.
* `tokens[]` seeds extra tokens. With `scopes` it behaves like an OAuth access token limited to those scopes; without them it is a personal token.
* OAuth access tokens carry the scopes the seller approved.

An unknown token answers 401 `UNAUTHORIZED`. Revoked and expired tokens answer `ACCESS_TOKEN_REVOKED` and `ACCESS_TOKEN_EXPIRED`. A token without an endpoint's scope answers 403 `INSUFFICIENT_SCOPES` and names the missing scopes.

## OAuth

* `GET /oauth2/authorize?client_id=...&scope=PAYMENTS_WRITE+ORDERS_READ&state=...` — Authorize: shows a consent page with the requested permissions. Approving redirects to the application's redirect URL with `code`, `response_type=code`, and `state`; declining sends `error=access_denied`.
* `POST /oauth2/token` — Obtain token: with `grant_type=authorization_code`, `client_id`, `client_secret`, and `code`. Public clients send `code_verifier` instead of the secret (PKCE, `S256` or `plain`). Access tokens last 30 days, or 24 hours with `short_lived: true`.
* The same call with `grant_type=refresh_token` refreshes a token. In the code flow the refresh token stays the same; in the PKCE flow it is single use, rotated on every refresh, and expires after 90 days. `scopes` narrows a refresh.
* `POST /oauth2/revoke` — Revoke token: with `Authorization: Client <secret>` and an `access_token` or `merchant_id`, it revokes the grant (`revoke_only_access_token` keeps the refresh token) and sends `oauth.authorization.revoked`.
* `POST /oauth2/token/status` — Retrieve token status: reports a token's scopes, expiry, client, and merchant.

## Requests

### Square-Version

The `Square-Version` header is optional. Without it, requests are answered at `2026-09-16`. A value that is not `YYYY-MM-DD` answers 400 `INVALID_SQUARE_VERSION_FORMAT`. Webhook subscriptions keep the version they were created with.

### Idempotency

Writes take `idempotency_key` in the body. The same key with the same body returns the first answer, including a decline; with a different body it answers 400 `IDEMPOTENCY_KEY_REUSED`. Keys are scoped to the application and the endpoint.

### Pagination

List and search endpoints return `cursor` when there is more. A cursor is tied to the filters it came from; any other value answers `INVALID_CURSOR`.

## Payments

* `POST /v2/payments` — Create payment: `source_id`, `idempotency_key`, `amount_money`, and optionally `tip_money`, `app_fee_money`, `autocomplete`, `delay_duration`, `delay_action`, `order_id`, `customer_id`, `location_id`, `reference_id`, `note`, and `buyer_email_address`
* `GET /v2/payments` — List payments: filters by `begin_time`, `end_time`, `sort_order`, `location_id`, `total`, `last_4`, and `card_brand`, and pages with `cursor` and `limit`
* `GET /v2/payments/:id` — Get payment
* `PUT /v2/payments/:id` — Update payment: amounts and tip of an `APPROVED` payment, with `version_token`
* `POST /v2/payments/:id/complete` — Complete payment
* `POST /v2/payments/:id/cancel` — Cancel payment
* `POST /v2/payments/cancel` — Cancel payment by idempotency key

With `autocomplete: false` a card payment stays `APPROVED` until it is completed or canceled. After `delay_duration` (default `PT168H`) the `delay_action` (`CANCEL` by default, or `COMPLETE`) is applied. Completing or canceling a payment that is not `APPROVED` answers `DELAYED_TRANSACTION_CAPTURED`, `DELAYED_TRANSACTION_CANCELED`, or `DELAYED_TRANSACTION_FAILED`.

A payment against an order must match the order's amount due (`PAYMENT_AMOUNT_MISMATCH`) and location (`LOCATION_MISMATCH`). When the order is fully paid it gets a tender and closes, unless it has fulfillments.

### Test values

| `source_id` | Result |
| - | - |
| `cnon:card-nonce-ok` | Visa, approved |
| `cnon:card-nonce-declined` | `GENERIC_DECLINE` |
| `cnon:card-nonce-rejected-cvv` | `CVV_FAILURE` |
| `cnon:card-nonce-rejected-postalcode` | `ADDRESS_VERIFICATION_FAILURE` |
| `cnon:card-nonce-rejected-expiration` | `INVALID_EXPIRATION` |
| `cnon:card-nonce-already-used` | `CARD_TOKEN_USED` |
| `cnon:gift-card-nonce-ok` | Square gift card, approved |
| `cnon:gift-card-nonce-insufficient-funds` | `GIFT_CARD_AVAILABLE_AMOUNT` |
| `cnon:gift-card-nonce-insufficient-permission` | `INSUFFICIENT_PERMISSIONS` |
| `ccof:customer-card-id-ok` and the other `ccof:` values | Card on file, approved or declined as named |
| `bnon:bank-nonce-ok` | ACH, `PENDING`, completes after a minute |
| `bnon:bank-nonce-failure` and other `bnon:` failures | ACH, fails |
| `wnon:cash-app-ok`, `wnon:afterpay-or-clearpay-ok` | Cash App Pay and Afterpay, approved |
| `CASH`, `EXTERNAL` | Cash and external payments, with `cash_details` or `external_details` |

A declined payment is stored as `FAILED` and answered with 400 `PAYMENT_METHOD_ERROR`, the decline code, and the payment. An unknown nonce answers 404 `NOT_FOUND`. A card ending in `2222` is rated `MODERATE` risk and `3333` `HIGH`.

## Refunds

* `POST /v2/refunds` — Refund payment: all or part of a completed payment
* `GET /v2/refunds` — List payment refunds
* `GET /v2/refunds/:id` — Get payment refund

Refunding more than what is left answers `REFUND_AMOUNT_INVALID`, an `APPROVED` payment `REFUND_ERROR_PAYMENT_NEEDS_COMPLETION`, and a canceled or failed payment `PAYMENT_NOT_REFUNDABLE`. Card refunds start `PENDING` and complete after `refund_delay_ms` (default one second); cash and external refunds complete at once. The payment's `refunded_money` and `refund_ids` update straight away.

## Orders

* `POST /v2/orders` — Create order: line items (catalog variations or ad hoc), taxes (`ADDITIVE` or `INCLUSIVE`), discounts (`FIXED_PERCENTAGE`, `FIXED_AMOUNT`, at order or line item scope), service charges, and fulfillments. Every total is computed on the server
* `POST /v2/orders/calculate` — Calculate order: prices an order without saving it
* `POST /v2/orders/clone` — Clone order: copies one into a new `DRAFT`
* `GET /v2/orders/:id` — Retrieve order
* `POST /v2/orders/batch-retrieve` — Batch retrieve orders
* `PUT /v2/orders/:id` — Update order: with the current `version` (`VERSION_MISMATCH` otherwise): a sparse update that merges lists by `uid` and honors `fields_to_clear`
* `POST /v2/orders/search` — Search orders: `location_ids`, `state_filter`, `date_time_filter`, `customer_filter`, `source_filter`, `fulfillment_filter`, and `sort` (the sort field must match the date filter field), `return_entries`, `cursor`, and `limit`
* `POST /v2/orders/:id/pay` — Pay order: `payment_ids` that cover the amount due

## Customers

* `GET /v2/customers` — List customers
* `POST /v2/customers` — Create customer
* `DELETE /v2/customers/:id` — Delete customer: the same path takes `GET` and `PUT`, all with `version` checks
* `POST /v2/customers/bulk-create` — Bulk create customers: also `bulk-retrieve`, `bulk-update`, and `bulk-delete`
* `POST /v2/customers/search` — Search customers: `email_address`, `phone_number`, `reference_id`, `created_at`, `updated_at`, and `creation_source` filters and a sort

Creating a customer needs a name, company, email, or phone number.

## Catalog

* `POST /v2/catalog/object` — Upsert catalog object
* `POST /v2/catalog/batch-upsert` — Batch upsert catalog objects
* `GET /v2/catalog/list` — List catalog: filtered by `types`
* `GET /v2/catalog/object/:id` — Retrieve catalog object: `include_related_objects`
* `POST /v2/catalog/batch-retrieve` — Batch retrieve catalog objects
* `DELETE /v2/catalog/object/:id` — Delete catalog object: deleting an item deletes its variations
* `POST /v2/catalog/batch-delete` — Batch delete catalog objects: deleting an item deletes its variations
* `POST /v2/catalog/search` — Search catalog objects: `exact_query`, `set_query`, `prefix_query`, `range_query`, `text_query`, `items_for_tax_query`, and `sorted_attribute_query`
* `POST /v2/catalog/search-catalog-items` — Search catalog items
* `POST /v2/catalog/update-item-taxes` — Update item taxes
* `GET /v2/catalog/info` — Catalog info

Upserts take temporary ids such as `#latte`, which may refer to each other; the answer maps them in `id_mappings`. Updating an object needs its current `version`.

Items, variations, categories, taxes, discounts, modifier lists, and modifiers are stored.

## Locations and merchants

* `POST /v2/locations` — Create location: `GET` on the same path lists them
* `PUT /v2/locations/:id` — Update location: `GET` on the same path retrieves one
* `GET /v2/merchants` — List merchants
* `GET /v2/merchants/:id` — Retrieve merchant: `me` works

`main` stands for the main location, the first one in config. The merchants endpoints describe the seller.

## Webhooks

* `GET /v2/webhooks/event-types` — List webhook event types
* `POST /v2/webhooks/subscriptions` — Create webhook subscription: `GET` on the same path lists them
* `DELETE /v2/webhooks/subscriptions/:id` — Delete webhook subscription: the same path takes `GET` and `PUT`
* `POST /v2/webhooks/subscriptions/:id/signature-key` — Update webhook signature key: rotates the key
* `POST /v2/webhooks/subscriptions/:id/test` — Test webhook subscription: sends a test event and returns the receiver's answer

Subscription endpoints need a personal access token, as on Square. The signature key is returned on create and rotation only.

Events: `payment.created`, `payment.updated`, `refund.created`, `refund.updated`, `order.created`, `order.updated`, `customer.created`, `customer.updated`, `customer.deleted`, `catalog.version.updated`, `location.created`, `location.updated`, and `oauth.authorization.revoked`. Each is posted as `{ merchant_id, type, event_id, created_at, data: { type, id, object } }` with `x-square-hmacsha256-signature` (base64 HMAC-SHA256 of the notification URL followed by the raw body, keyed with the signature key), so `WebhooksHelper.verifySignature` accepts it:

```ts theme={null}
import { WebhooksHelper } from "square";

const valid = await WebhooksHelper.verifySignature({
  requestBody: rawBody,
  signatureHeader: req.headers["x-square-hmacsha256-signature"],
  signatureKey: "whsec-baseline",
  notificationUrl: "http://localhost:3000/square/webhooks",
});
```

A subscription hears an application's events when the application owns it and holds a personal token or an OAuth token with the event's read scope. A failed delivery is retried seven times over about two minutes with `square-retry-number` and `square-retry-reason`.

## Seed config

```json theme={null}
{
  "square": {
    "merchant": {
      "business_name": "Baseline Coffee",
      "country": "US",
      "currency": "USD"
    },
    "locations": [
      {
        "name": "Baseline Coffee Downtown"
      },
      {
        "name": "Baseline Coffee Uptown"
      }
    ],
    "apps": [
      {
        "name": "My app",
        "application_id": "sandbox-sq0idb-my-app",
        "application_secret": "sandbox-sq0csb-my-app-secret",
        "redirect_uris": ["http://localhost:3000/square/callback"],
        "personal_access_token": "EAAAl-baseline-square-token"
      }
    ],
    "tokens": [
      {
        "token": "EAAAl-read-only-token",
        "scopes": ["PAYMENTS_READ", "ORDERS_READ"]
      }
    ],
    "customers": [
      {
        "given_name": "Ada",
        "family_name": "Lovelace",
        "email_address": "ada@example.com"
      }
    ],
    "catalog": [
      {
        "type": "TAX",
        "id": "#sales-tax",
        "tax_data": {
          "name": "Sales tax",
          "percentage": "8.5"
        }
      },
      {
        "type": "ITEM",
        "id": "#latte",
        "item_data": {
          "name": "Latte",
          "tax_ids": ["#sales-tax"],
          "variations": [
            {
              "type": "ITEM_VARIATION",
              "id": "#latte-regular",
              "item_variation_data": {
                "name": "Regular",
                "price_money": {
                  "amount": 450,
                  "currency": "USD"
                }
              }
            }
          ]
        }
      }
    ],
    "webhooks": [
      {
        "url": "http://localhost:3000/square/webhooks",
        "event_types": ["payment.created", "payment.updated"],
        "signature_key": "whsec-baseline"
      }
    ],
    "refund_delay_ms": 1000
  }
}
```

* `name`: the first location is the main one

`catalog` entries use Square's upsert shape, so temporary ids may refer to each other. A webhook without `event_types` hears every event Baseline sends; without `signature_key` one is generated.

## Inspector

`GET /_inspector` shows payments with their refunds, orders with their totals, customers, catalog objects, locations, and applications with their tokens and webhook subscriptions. Each payment's `receipt_url` opens a receipt page at `/_square/receipts/:id`.

## Limits

Terminal, invoices, subscriptions, gift cards, loyalty, bookings, team, labor, inventory counts, disputes, payouts, cards on file (`/v2/cards`), and the v1 API. Other `/v2` paths answer 404 `NOT_FOUND`.

## Conformance

| | |
| - | - |
| Routes | 65 supported or partial, 0 planned |
| Route gate | fail |
| Spec gate | fail, 6 allowed ([square/connect-api-specification](https://github.com/square/connect-api-specification) at `0689c90`) |
| SDKs tested | `square@46.0.0` |
| Dual mode | not yet |

See [CONFORMANCE.md](https://github.com/deploybaseline/cli/blob/main/CONFORMANCE.md) for every provider.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.