# Square Developer Platform — Full Documentation > Concatenated developer documentation for AI agent ingestion, per the llmstxt.org spec. > Count: 481 pages > Generated: 2026-07-22T16:54:16.882Z > Index: https://developer.squareup.com/llms.txt --- # Inventory Adjustment Reasons > Source: https://developer.squareup.com/docs/inventory-api/adjustment-reasons > Status: BETA > Languages: All > Platforms: All Learn how to categorize inventory adjustments with standard and custom adjustment reasons and filter inventory change history by reason. **Applies to:** [Inventory API](inventory-api/what-it-does) ## Overview Adjustment reasons let applications record *why* an inventory quantity changed, using the same set of reasons that sellers see in the Square Dashboard and Square Point of Sale. Every [InventoryAdjustment](https://developer.squareup.com/reference/square/objects/InventoryAdjustment) can carry a `reason_id` that identifies a standard reason provided by Square (such as `DAMAGED` or `RECEIVED`) or a custom reason defined by the seller (such as "Donated to charity"). With adjustment reasons, your application can: * Write adjustments with a reason, so they're categorized consistently with first-party Square products. * Manage the seller's custom adjustment reasons (create, update, delete, and restore). * Filter the inventory change history by one or more reasons. ## Reason identifiers A reason is identified by an `InventoryAdjustmentReasonId` object rather than a single ID string, in order to support predefined reason codes alongside the seller's custom reasons: * Standard and system-generated reasons are identified by `type` alone - for example, `{ "type": "DAMAGED" }`. * Custom reasons use `type` `CUSTOM` plus the seller-specific `custom_reason_id` - for example, `{ "type": "CUSTOM", "custom_reason_id": "R5BX3PDCZ6EXAMPLE" }`. The full `InventoryAdjustmentReason` resource adds a `name` (custom reasons only), a `direction` (`INCREASE` or `DECREASE`), timestamps, and an `is_deleted` flag. ### Standard reasons Standard reasons are predefined by Square, available to every seller, and can be written on adjustments by your application. Each standard reason implies a direction: | Reason type | Direction | |-------------|-----------| | `RECEIVED` | `INCREASE` | | `RETURNED` | `INCREASE` | | `DAMAGED` | `DECREASE` | | `THEFT` | `DECREASE` | | `LOST` | `DECREASE` | | `SPOILAGE_WASTE` | `DECREASE` | | `SAMPLES_PROMOTIONAL` | `DECREASE` | | `INTERNAL_USE` | `DECREASE` | | `VENDOR_RETURN` | `DECREASE` | | `PRODUCTION_WASTE` | `DECREASE` | ### System-generated reasons Some reasons are only ever attached to adjustments generated by Square products and cannot be written by your application: `SALE`, `RECOUNT`, `TRANSFER`, `IN_TRANSIT`, and `CANCELED_SALE`. You might encounter them when reading the change history. By default, they're omitted from [ListInventoryAdjustmentReasons](https://developer.squareup.com/reference/square/inventory-api/list-inventory-adjustment-reasons) results; pass `include_system_codes=true` to include them. ### Custom reasons Sellers (and your application, on their behalf) can define custom reasons with a name of up to 50 characters and a direction. Deleting a custom reason is a soft delete: historical adjustments keep referencing it, deleted reasons can still be retrieved by ID, and a deleted reason can be brought back with [RestoreInventoryAdjustmentReason](https://developer.squareup.com/reference/square/inventory-api/restore-inventory-adjustment-reason). ## Endpoints | Endpoint | Route | Permission | |----------|-------|------------| | ListInventoryAdjustmentReasons | `GET /v2/inventory/adjustment-reasons` | `INVENTORY_READ` | | RetrieveInventoryAdjustmentReason | `POST /v2/inventory/adjustment-reasons/retrieve` | `INVENTORY_READ` | | CreateInventoryAdjustmentReason | `POST /v2/inventory/adjustment-reasons/create` | `INVENTORY_WRITE` | | UpdateInventoryAdjustmentReason | `PUT /v2/inventory/adjustment-reasons/update` | `INVENTORY_WRITE` | | DeleteInventoryAdjustmentReason | `POST /v2/inventory/adjustment-reasons/delete` | `INVENTORY_WRITE` | | RestoreInventoryAdjustmentReason | `POST /v2/inventory/adjustment-reasons/restore` | `INVENTORY_WRITE` | ## List the available reasons The following example lists the reasons available to the seller. The response contains the standard reasons and the seller's custom reasons. Include deleted custom reasons with `include_deleted=true`, and system-generated reasons with `include_system_codes=true`. {% tabset %} {% tab id="Request" %} ```bash curl https://connect.squareup.com/v2/inventory/adjustment-reasons \ -H 'Square-Version: 2026-07-15' \ -H 'Authorization: Bearer {ACCESS_TOKEN}' \ -H 'Content-Type: application/json' ``` {% /tab %} {% tab id="Response" %} ```json { "adjustment_reasons": [ { "id": { "type": "RECEIVED" }, "direction": "INCREASE" }, { "id": { "type": "DAMAGED" }, "direction": "DECREASE" }, { "id": { "type": "CUSTOM", "custom_reason_id": "R5BX3PDCZ6EXAMPLE" }, "name": "Donated to charity", "direction": "DECREASE", "created_at": "2026-07-16T18:24:31.000Z", "updated_at": "2026-07-16T18:24:31.000Z", "is_deleted": false } ] } ``` {% /tab %} {% /tabset %} ## Create a custom reason Provide an idempotency key and the reason's `id`, `name`, and `direction`. The `id` must have type `CUSTOM` and omit `custom_reason_id` - Square assigns the ID and returns it in the response. {% tabset %} {% tab id="Request" %} ```bash curl https://connect.squareup.com/v2/inventory/adjustment-reasons/create \ -X POST \ -H 'Square-Version: 2026-07-15' \ -H 'Authorization: Bearer {ACCESS_TOKEN}' \ -H 'Content-Type: application/json' \ -d '{ "idempotency_key": "0e485885-fabc-43cc-ae63-2d3666dc0f27", "adjustment_reason": { "id": { "type": "CUSTOM" }, "name": "Donated to charity", "direction": "DECREASE" } }' ``` {% /tab %} {% tab id="Response" %} ```json { "adjustment_reason": { "id": { "type": "CUSTOM", "custom_reason_id": "R5BX3PDCZ6EXAMPLE" }, "name": "Donated to charity", "direction": "DECREASE", "created_at": "2026-07-16T18:24:31.000Z", "updated_at": "2026-07-16T18:24:31.000Z", "is_deleted": false } } ``` {% /tab %} {% /tabset %} Only the `name` of an existing custom reason can be changed later (with UpdateInventoryAdjustmentReason, passing the reason's full `id` in `adjustment_reason` alongside the new `name`). The `direction` is fixed at creation because historical adjustments already recorded with the reason rely on it. Standard and system-generated reasons cannot be modified. ## Write an adjustment with a reason Set `reason_id` on the adjustment in a [BatchChangeInventory](https://developer.squareup.com/reference/square/inventory-api/batch-change-inventory) request. The reason's direction must match the state transition - for example, a `DECREASE` reason like `DAMAGED` pairs with a transition out of `IN_STOCK`, such as `IN_STOCK` to `WASTE`. ```bash curl https://connect.squareup.com/v2/inventory/changes/batch-create \ -X POST \ -H 'Square-Version: 2026-07-15' \ -H 'Authorization: Bearer {ACCESS_TOKEN}' \ -H 'Content-Type: application/json' \ -d '{ "idempotency_key": "8fc6a5b0-9fe8-4b46-b46b-2ef95dc8a103", "changes": [ { "type": "ADJUSTMENT", "adjustment": { "catalog_object_id": "6F4K33KPNUVDWKZ43KUIFH6K", "from_state": "IN_STOCK", "to_state": "WASTE", "from_location_id": "EF6D9SACKWBKZ", "to_location_id": "EF6D9SACKWBKZ", "quantity": "3", "occurred_at": "2026-07-16T14:20:00.000Z", "reason_id": { "type": "DAMAGED" } } } ] }' ``` To use a custom reason instead, pass its full identifier. Custom reasons apply to plain stock increases and decreases: an `INCREASE` custom reason pairs with a `NONE` to `IN_STOCK` transition, and a `DECREASE` custom reason pairs with `IN_STOCK` to `NONE`. For example, the following change removes 3 units using a seller-defined reason: ```json { "type": "ADJUSTMENT", "adjustment": { "catalog_object_id": "6F4K33KPNUVDWKZ43KUIFH6K", "from_state": "IN_STOCK", "to_state": "NONE", "from_location_id": "EF6D9SACKWBKZ", "to_location_id": "EF6D9SACKWBKZ", "quantity": "3", "occurred_at": "2026-07-16T14:25:00.000Z", "reason_id": { "type": "CUSTOM", "custom_reason_id": "R5BX3PDCZ6EXAMPLE" } } } ``` You can also change the reason on a past adjustment with [UpdateInventoryAdjustment](https://developer.squareup.com/reference/square/inventory-api/update-inventory-adjustment) - see [Update a past adjustment](inventory-api/track-costs-and-vendors#update-a-past-adjustment). The same validity rules apply, and the reason of a system-generated adjustment can't be changed. ## Filter the change history by reason [BatchRetrieveInventoryChanges](https://developer.squareup.com/reference/square/inventory-api/batch-retrieve-inventory-changes) accepts a `reason_ids` filter that returns only adjustments recorded with one of the given reasons. ```bash curl https://connect.squareup.com/v2/inventory/changes/batch-retrieve \ -X POST \ -H 'Square-Version: 2026-07-15' \ -H 'Authorization: Bearer {ACCESS_TOKEN}' \ -H 'Content-Type: application/json' \ -d '{ "catalog_object_ids": ["6F4K33KPNUVDWKZ43KUIFH6K"], "types": ["ADJUSTMENT"], "reason_ids": [ { "type": "DAMAGED" }, { "type": "CUSTOM", "custom_reason_id": "R5BX3PDCZ6EXAMPLE" } ] }' ``` {% aside type="important" %} The `reason_ids` filter cannot be combined with the `states` filter in the same request. {% /aside %} ## Delete and restore custom reasons Deleting a custom reason hides it from sellers and from default `ListInventoryAdjustmentReasons` results, but doesn't remove it from adjustments that already reference it. ```bash curl https://connect.squareup.com/v2/inventory/adjustment-reasons/delete \ -X POST \ -H 'Square-Version: 2026-07-15' \ -H 'Authorization: Bearer {ACCESS_TOKEN}' \ -H 'Content-Type: application/json' \ -d '{ "reason_id": { "type": "CUSTOM", "custom_reason_id": "R5BX3PDCZ6EXAMPLE" } }' ``` Restore a deleted reason with the same request body sent to `POST /v2/inventory/adjustment-reasons/restore`. A deleted reason cannot be updated until it's restored. --- # Track Inventory Costs and Vendors > Source: https://developer.squareup.com/docs/inventory-api/track-costs-and-vendors > Status: BETA > Languages: All > Platforms: All Learn how to record unit costs and vendors on stock-receiving inventory adjustments and update past adjustments with the Inventory API. **Applies to:** [Inventory API](inventory-api/what-it-does) | [Catalog API](catalog-api/what-it-does) | [Vendors API](vendors-api/create-vendors) ## Overview Sellers that track what they pay for their stock can answer questions like "What is my inventory worth?" and "What did I pay this vendor last quarter?" The Inventory API lets your application record that information the same way first-party Square products do: * Record the amount paid (`cost_money`) and the vendor (`vendor_id`) when receiving stock. * Update the quantity, cost, vendor, or reason of a past adjustment with [UpdateInventoryAdjustment](https://developer.squareup.com/reference/square/inventory-api/update-inventory-adjustment). Default costs and vendors for an item variation come from the variation's `vendor_information` in the catalog. See [Manage Vendor Information](catalog-api/manage-vendor-information). {% aside type="info" %} Reading cost fields works for any seller; *writing* cost and vendor data requires the seller to have an active Square for Retail Plus, Square for Restaurants Plus, or Square for Restaurants Premium subscription. {% /aside %} ## Record cost and vendor when receiving stock `cost_money` and `vendor_id` can be written only on stock-receiving adjustments - adjustments whose `from_state` is `NONE` or `UNLINKED_RETURN`. `cost_money` is the total amount paid to the vendor for the units in the adjustment, and `vendor_id` references a vendor managed with the [Vendors API](https://developer.squareup.com/reference/square/vendors-api). The following [BatchChangeInventory](https://developer.squareup.com/reference/square/inventory-api/batch-change-inventory) request receives 100 units purchased for $250.00 from a known vendor: ```bash curl https://connect.squareup.com/v2/inventory/changes/batch-create \ -X POST \ -H 'Square-Version: 2026-07-15' \ -H 'Authorization: Bearer {ACCESS_TOKEN}' \ -H 'Content-Type: application/json' \ -d '{ "idempotency_key": "42c1a557-2b31-4b8b-8ecb-6dcf10ffb2b2", "changes": [ { "type": "ADJUSTMENT", "adjustment": { "catalog_object_id": "6F4K33KPNUVDWKZ43KUIFH6K", "from_state": "NONE", "to_state": "IN_STOCK", "from_location_id": "EF6D9SACKWBKZ", "to_location_id": "EF6D9SACKWBKZ", "quantity": "100", "occurred_at": "2026-07-16T14:20:00.000Z", "cost_money": { "amount": 25000, "currency": "USD" }, "vendor_id": "5ZW5RVANNKV3B3RB", "reason_id": { "type": "RECEIVED" } } } ] }' ``` Cost and vendor values then appear on the adjustment in change-history responses. Reading these fields doesn't require a subscription; adjustments written without cost information simply omit the fields. ## Update a past adjustment Sellers sometimes need to correct a received quantity, a cost, or a vendor after the fact. `UpdateInventoryAdjustment` (`PUT /v2/inventory/adjustments/update`, `INVENTORY_WRITE`) supports sparse updates of a past adjustment, with the same restrictions that apply in the Square Dashboard: * `quantity` must always be provided, but it can be identical to the current quantity if only cost, vendor, or reason changes are desired. * `cost_money` and `vendor_id` can be changed only on adjustments that add stock (`from_state` of `NONE` or `UNLINKED_RETURN`) and on untracked sale adjustments. * `reason_id` can be changed to any reason that's valid for the adjustment's state transition. The reason of a system-generated adjustment (such as `SALE` or `RECOUNT`) can't be changed. See [Inventory Adjustment Reasons](inventory-api/adjustment-reasons). * Adjustments that Square generates from other records can't be updated: inferred adjustments from physical counts, transfer-like cross-location adjustments, and component adjustments. * Adjustments linked to purchase orders can't be updated. Adjustments linked to sales can only have cost and vendor updated, and only for untracked sales. * Restock adjustments linked to an itemized return can have their quantity updated, up to the quantity remaining on the return. * Adjustments older than 1 year cannot be updated. ```bash curl https://connect.squareup.com/v2/inventory/adjustments/update \ -X PUT \ -H 'Square-Version: 2026-07-15' \ -H 'Authorization: Bearer {ACCESS_TOKEN}' \ -H 'Content-Type: application/json' \ -d '{ "idempotency_key": "d6f1cf4e-6d90-4a2b-9df7-2f0ab9a78ee1", "adjustment": { "id": "HA4STFR47RAEORFWXHOYPRG5", "quantity": "100", "cost_money": { "amount": 27500, "currency": "USD" }, "vendor_id": "5ZW5RVANNKV3B3RB" } }' ``` The endpoint is available only to sellers with an active Square for Retail Plus, Square for Restaurants Plus, or Square for Restaurants Premium subscription. --- # Manage Vendor Information on Item Variations > Source: https://developer.squareup.com/docs/catalog-api/manage-vendor-information > Status: BETA > Languages: All > Platforms: All Learn how to set default unit costs, vendors, and vendor codes on catalog item variations with the Catalog API. **Applies to:** [Catalog API](catalog-api/what-it-does) | [Inventory API](inventory-api/what-it-does) | [Vendors API](vendors-api/create-vendors) ## Overview An item variation's `vendor_information` describes how the seller purchases that product: which vendors supply it, the usual cost per unit, and the product's identifier in each vendor's system. Square uses this information to prefill purchase orders and as the default cost when inventory movements need one - for example, when a physical count increases the in-stock quantity or when an untracked variation with a default cost is sold. {% aside type="info" %} Reading `vendor_information` works for any seller; *writing* it requires the seller to have an active Square for Retail Plus, Square for Restaurants Plus, or Square for Restaurants Premium subscription. {% /aside %} ## The vendor_information field [CatalogItemVariation](https://developer.squareup.com/reference/square/objects/CatalogItemVariation) has a repeated `vendor_information` field. Each entry contains: | Field | Description | |-------|-------------| | `unit_cost_money` | The usual amount the seller pays per unit when purchasing from this vendor. | | `vendor_id` | Optional. The ID of a vendor managed with the [Vendors API](https://developer.squareup.com/reference/square/vendors-api). Each entry must reference a different vendor. | | `vendor_code` | Optional. The product's unique identifier (such as a SKU) in the vendor's own system. Used to prefill purchase orders. | Two rules give the list its meaning: * **The first entry is the default.** When Square needs a default cost or vendor for the variation (physical count increases, untracked sales with cost tracking, purchase order prefill), it uses the first entry in the list. * **An entry can omit `vendor_id`.** A single cost-only entry supports sellers who track what they pay for a product without tracking a vendor. Use this when the seller isn't tracking vendors for the variation - don't combine a vendorless cost entry with vendor-specific entries. ## Set vendor information on a variation Write `vendor_information` as part of the variation in an [UpsertCatalogObject](https://developer.squareup.com/reference/square/catalog-api/upsert-catalog-object) (or batch upsert) request: ```bash curl https://connect.squareup.com/v2/catalog/object \ -X POST \ -H 'Square-Version: 2026-07-15' \ -H 'Authorization: Bearer {ACCESS_TOKEN}' \ -H 'Content-Type: application/json' \ -d '{ "idempotency_key": "0a3d5bfb-8325-4a7b-8dcc-2f0a4b3b9c66", "object": { "type": "ITEM_VARIATION", "id": "#small-leather-collar", "item_variation_data": { "item_id": "W62UWFY35CWMTGPTTVGXAMPL", "name": "Small", "pricing_type": "FIXED_PRICING", "price_money": { "amount": 2500, "currency": "USD" }, "vendor_information": [ { "vendor_id": "5ZW5RVANNKV3B3RB", "vendor_code": "LC-SM-0042", "unit_cost_money": { "amount": 1050, "currency": "USD" } }, { "vendor_id": "V6C8QK4M2WX9YB7R", "vendor_code": "8890-SM", "unit_cost_money": { "amount": 1200, "currency": "USD" } } ] } } }' ``` In this example, the variation can be purchased from two vendors. The first entry is the default: Square uses vendor `5ZW5RVANNKV3B3RB` and its $10.50 unit cost when it needs a single default cost or vendor for the variation. The second entry (vendor `V6C8QK4M2WX9YB7R` at $12.00 per unit) appears on the variation in the Square Dashboard and prefills the unit cost when the seller creates a purchase order for that vendor. As with other variation fields, the entire `vendor_information` list is replaced on write. To add or remove an entry, read the variation, modify the list, and upsert it back with the variation's current `version`. ## How vendor information connects to inventory Vendor information supplies *defaults*; the actual cost of received stock is recorded on inventory adjustments: * When your application receives stock with the Inventory API, it can record the actual `cost_money` and `vendor_id` on the receiving adjustment. See [Track Inventory Costs and Vendors](inventory-api/track-costs-and-vendors). * When Square generates stock-increasing adjustments itself (for example, from a physical count), it uses the variation's default cost. --- # Getting Started > Source: https://developer.squareup.com/docs/reporting-api/getting-started > Status: BETA > Languages: All > Platforms: All Learn how to set up and configure the Square Reporting API for querying transaction and business data. ## Prerequisites Before you begin, ensure you have: 1. A Square Developer account 2. A [Square application](https://developer.squareup.com/docs/get-started/create-account-and-application) created in the Developer Console 3. An [access token](https://developer.squareup.com/docs/build-basics/access-tokens) (personal access token or OAuth token with `REPORTING_READ` scope) 4. Basic familiarity with REST APIs and JSON 5. A tool for making HTTP requests (cURL, Postman, or your preferred HTTP client) {% aside type="tip" %} **New to Cube?** The Reporting API is built on Cube's semantic layer. We explain all the key concepts (cubes, measures, dimensions) in this guide, but if you want to dive deeper, see the [Cube documentation](https://cube.dev/docs). {% /aside %} {% aside type="info" %} - **OAuth Support**: The Reporting API supports OAuth tokens with the `REPORTING_READ` permission. Personal access tokens are also supported for testing and development. - **Security Best Practice**: Never commit access tokens to source control. Use environment variables or secure secret management systems. {% /aside %} ## Step 1: Set Up Your Environment Store your access token as an environment variable: ```bash # macOS/Linux export SQUARE_ACCESS_TOKEN="your_access_token_here" export SQUARE_API_BASE="https://connect.squareup.com/reporting" # Windows (PowerShell) $env:SQUARE_ACCESS_TOKEN="your_access_token_here" $env:SQUARE_API_BASE="https://connect.squareup.com/reporting" ``` ## Step 2: Verify connectivity Test your authentication by calling the metadata endpoint: ```bash curl -X GET "${SQUARE_API_BASE}/v1/meta" \ -H "Authorization: Bearer ${SQUARE_ACCESS_TOKEN}" \ -H "Content-Type: application/json" ``` **Expected response**: A JSON object containing cubes, measures, dimensions, and segments. If you receive a `403 Forbidden` error, verify: - Your access token is correct - You're using a personal access token (not OAuth) - Your token has not expired ## Step 3: Explore available data The `/v1/meta` response shows all available data, including **views** and **cubes**. Views are the recommended starting point — they combine data from multiple cubes into a simplified, curated interface. Let's look at the `Sales` view: ```bash curl -X GET "${SQUARE_API_BASE}/v1/meta" \ -H "Authorization: Bearer ${SQUARE_ACCESS_TOKEN}" | jq '.cubes[] | select(.name == "Sales") | {name, type, title, measures: [.measures[].name]}' ``` This command: 1. Fetches metadata 2. Filters to the `Sales` view 3. Shows the view name and available measures **Sample output**: ```json { "name": "Sales", "type": "view", "title": "Sales", "measures": [ "Sales.top_line_product_sales", "Sales.net_sales", "Sales.total_collected_amount", "Sales.order_count", "Sales.avg_net_sales", "Sales.discounts_amount", "Sales.itemized_returns", "Sales.refunds_by_amount_amount", "Sales.tips_amount", "Sales.sales_tax_amount" ] } ``` {% aside type="info" %} **Views vs Cubes**: Views (like `Sales`) pre-join data from multiple cubes and pre-filter for common use cases. Cubes (like `Orders`) provide raw, granular access. For most reporting, start with views. See [Metadata Discovery](/docs/reporting-api/metadata-discovery) for details. {% /aside %} ## Step 4: Run your first query Let's query daily net sales for the last 30 days using the `Sales` view: ```bash curl -X POST "${SQUARE_API_BASE}/v1/load" \ -H "Authorization: Bearer ${SQUARE_ACCESS_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "query": { "measures": ["Sales.net_sales"], "timeDimensions": [{ "dimension": "Sales.local_reporting_timestamp", "dateRange": "last 30 days", "granularity": "day" }] } }' ``` **What this query does**: - **Measures**: Calculates `net_sales` (gross sales minus discounts/returns) - **Time Dimensions**: Groups by day for the last 30 days - **No segment needed**: The `Sales` view automatically filters to closed (completed) orders ### Understanding the Response **Success response** (HTTP 200): ```json { "data": [ { "Sales.local_reporting_timestamp.day": "2024-02-01T00:00:00.000", "Sales.net_sales": "1250.50" }, { "Sales.local_reporting_timestamp.day": "2024-02-02T00:00:00.000", "Sales.net_sales": "980.25" } ], "annotation": { "measures": {}, "dimensions": {}, "timeDimensions": {} }, "slowQuery": false } ``` {% aside type="info" %} **Note**: Access your data via `response.data`. {% /aside %} **Long-running query response** (HTTP 200): ```json { "error": "Continue wait" } ``` If you receive `"Continue wait"`, the query is still processing. **Retry the same request** until you get results. This is expected behavior for complex queries. ## Step 5: Add dimensions for detailed breakdown Let's break down sales by location name: ```bash curl -X POST "${SQUARE_API_BASE}/v1/load" \ -H "Authorization: Bearer ${SQUARE_ACCESS_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "query": { "measures": [ "Sales.net_sales", "Sales.tips_amount", "Sales.order_count" ], "dimensions": ["Sales.location_name"], "timeDimensions": [{ "dimension": "Sales.local_reporting_timestamp", "dateRange": "last 30 days", "granularity": "day" }] } }' ``` **Response structure**: ```json { "data": [ { "Sales.location_name": "Downtown Store", "Sales.local_reporting_timestamp.day": "2024-02-01T00:00:00.000", "Sales.net_sales": "850.50", "Sales.tips_amount": "125.00", "Sales.order_count": "42" }, { "Sales.location_name": "Uptown Store", "Sales.local_reporting_timestamp.day": "2024-02-01T00:00:00.000", "Sales.net_sales": "400.00", "Sales.tips_amount": "50.00", "Sales.order_count": "28" } ], "slowQuery": false } ``` Notice how the `Sales` view gives you `location_name` directly — no need to look up location IDs separately. ## Step 6: Handle long-running queries For queries that take time to process, implement retry logic: ### Using cURL with a retry loop ```bash #!/bin/bash QUERY='{ "measures": ["Sales.net_sales"], "timeDimensions": [{ "dimension": "Sales.local_reporting_timestamp", "dateRange": ["2024-01-01", "2024-12-31"], "granularity": "month" }] }' MAX_RETRIES=10 RETRY_DELAY=2 for i in $(seq 1 $MAX_RETRIES); do echo "Attempt $i of $MAX_RETRIES..." RESPONSE=$(curl -s -X POST "${SQUARE_API_BASE}/v1/load" \ -H "Authorization: Bearer ${SQUARE_ACCESS_TOKEN}" \ -H "Content-Type: application/json" \ -d "{\"query\": $QUERY}") if echo "$RESPONSE" | grep -q "Continue wait"; then echo "Query still processing, waiting ${RETRY_DELAY}s..." sleep $RETRY_DELAY else echo "Results received!" echo "$RESPONSE" | jq '.' exit 0 fi done echo "Query timed out after $MAX_RETRIES attempts" exit 1 ``` ### Using Python ```python import os import time import requests SQUARE_ACCESS_TOKEN = os.environ['SQUARE_ACCESS_TOKEN'] SQUARE_API_BASE = os.environ['SQUARE_API_BASE'] def execute_query_with_retry(query, max_retries=10, retry_delay=2): """Execute a Reporting API query with automatic retry for long-running queries.""" headers = { 'Authorization': f'Bearer {SQUARE_ACCESS_TOKEN}', 'Content-Type': 'application/json' } for attempt in range(1, max_retries + 1): print(f"Attempt {attempt} of {max_retries}...") response = requests.post( f'{SQUARE_API_BASE}/v1/load', headers=headers, json={"query": query} ) if response.status_code != 200: raise Exception(f"API error: {response.status_code} - {response.text}") data = response.json() if 'error' in data and data['error'] == 'Continue wait': print(f"Query still processing, waiting {retry_delay}s...") time.sleep(retry_delay) else: print("Results received!") return data raise TimeoutError(f"Query timed out after {max_retries} attempts") # Example usage query = { "measures": ["Sales.net_sales"], "timeDimensions": [{ "dimension": "Sales.local_reporting_timestamp", "dateRange": ["2024-02-01", "2024-02-29"], "granularity": "day" }] } results = execute_query_with_retry(query) print(results) ``` ### Using JavaScript/Node.js ```javascript const axios = require('axios'); const SQUARE_ACCESS_TOKEN = process.env.SQUARE_ACCESS_TOKEN; const SQUARE_API_BASE = process.env.SQUARE_API_BASE; async function executeQueryWithRetry(query, maxRetries = 10, retryDelay = 2000) { const headers = { 'Authorization': `Bearer ${SQUARE_ACCESS_TOKEN}`, 'Content-Type': 'application/json' }; for (let attempt = 1; attempt <= maxRetries; attempt++) { console.log(`Attempt ${attempt} of ${maxRetries}...`); try { const response = await axios.post( `${SQUARE_API_BASE}/v1/load`, { query }, { headers } ); if (response.data.error === 'Continue wait') { console.log(`Query still processing, waiting ${retryDelay / 1000}s...`); await new Promise(resolve => setTimeout(resolve, retryDelay)); } else { console.log('Results received!'); return response.data; } } catch (error) { throw new Error(`API error: ${error.response?.status} - ${error.message}`); } } throw new Error(`Query timed out after ${maxRetries} attempts`); } // Example usage const query = { measures: ['Sales.net_sales'], timeDimensions: [{ dimension: 'Sales.local_reporting_timestamp', dateRange: ['2024-02-01', '2024-02-29'], granularity: 'day' }] }; executeQueryWithRetry(query) .then(results => console.log(JSON.stringify(results, null, 2))) .catch(error => console.error(error)); ``` ## Data freshness and propagation delay {% aside type="info" %} There is a delay between when a transaction occurs and when it appears in the Reporting API. Most cubes have approximately a 15-minute delay. Some cubes may offer faster freshness but might not be joinable with other cubes. Check the metadata endpoint (`/v1/meta`) for per-cube freshness details. {% /aside %} For real-time order data, use the [Orders API](https://developer.squareup.com/reference/square/orders-api) instead. --- # Views > Source: https://developer.squareup.com/docs/reporting-api/views > Status: BETA > Languages: All > Platforms: All Understand how views organize and structure data in the Square Reporting API for flexible querying. ## Overview **Views are the recommended way to query the Reporting API.** They pre-join data from multiple cubes, pre-filter to relevant records, and expose human-readable dimensions like `location_name` instead of raw IDs. {% aside type="info" %} For most reporting tasks, start with a view. Use [cubes](/docs/reporting-api/cubes/core) directly only when you need raw, unfiltered data or fields not exposed through any view. {% /aside %} ## Available views | View | Measures | Dimensions | Segments | Primary Use Case | |------|----------|------------|----------|-----------------| | **Sales** | 23 | 23 | 7 | Order-level sales, revenue, tips, discounts | | **ItemSales** | 37 | 42 | 5 | Item-level sales with discounts, modifiers, combos | | **ModifierSales** | 10 | 18 | 2 | Modifier-level sales analysis | | **KDS** | 22 | 42 | 4 | Kitchen ticket and item prep times | | **SpeedOfService** | 25 | 21 | 8 | Order entry/service time + sales + KDS metrics | | **PeerBenchmarkHourlySales** | 4 | 9 | 0 | Hourly sales distribution vs peers | | **PeerBenchmarkOrderValue** | 4 | 7 | 0 | Average order value benchmarking vs peers | | **PeerBenchmarkSalesTrend** | 10 | 7 | 0 | Year-over-year sales growth vs peers | ## Sales view The **Sales** view is the recommended starting point for most sales reporting. It reports only on closed (fully settled) orders and includes dimensions for location, channel, customer, and employee analysis. {% aside type="info" %} **No segment needed.** The Sales view automatically filters to closed orders — you don't need to add `segments: ["Orders.closed_checks"]` like you would when querying the Orders cube directly. {% /aside %} ### Measures | Measure | Type | Description | |---------|------|-------------| | `top_line_product_sales` | sum | Gross sales (default "sales" measure — use this unless you need a specific qualifier) | | `net_sales` | sum | Top-line sales minus discounts and itemized returns | | `total_collected_amount` | sum | Total collected from customer (includes tips, tax, gift cards, returns) | | `total_sales_amount` | sum | Total sales amount for the order | | `order_count` | count | Number of orders | | `avg_net_sales` | avg | Average net sales per order | | `avg_top_line_product_sales` | avg | Average gross sales per order | | `avg_total_collected` | avg | Average total collected per order | | `avg_total_sales` | avg | Average total sales per order | | `discounts_amount` | sum | Total discounts applied | | `comps_amount` | sum | Total comps applied | | `itemized_returns` | sum | Itemized returns (excludes refunds, discounts, comps) | | `refunds_by_amount_amount` | sum | Non-itemized refunds (money given back) | | `tips_amount` | sum | Total tips (excludes cash tips and auto-gratuity) | | `sales_tax_amount` | sum | Sales tax collected | | `gift_card_sales_amount` | sum | Revenue from Square Gift Card sales (not purchases made with gift cards) | | `top_line_product_sales_count` | sum | Orders contributing to gross sales | | `net_sales_count` | sum | Orders contributing to net sales | | `total_sales_count` | sum | Orders contributing to total sales | | `location_count` | count | Unique locations | | `unique_customers` | countDistinct | Unique customers | | `customer_visit_count` | count | Customer visits (purchases) | | `employee_count` | count | Unique team members | ### Key dimensions | Dimension | Type | Description | |-----------|------|-------------| | `location_name` | string | Location display name | | `location_id` | string | Location identifier | | `local_reporting_timestamp` | time | Timestamp in location's local timezone | | `local_date` | string | Date in YYYY-MM-dd format, local timezone | | `local_day_of_week` | string | Day of week name, local timezone | | `local_hour` | number | Hour of day (0-23), local timezone | | `channel_name` | string | Sales channel display name | | `channel_category` | string | High-level channel category | | `customer_type` | string | NEW or RETURNING | | `customer_id` | string | Customer identifier | | `employee_full_name` | string | Team member full name | | `order_id` | string | Order identifier | | `merchant_id` | string | Merchant identifier | | `distinct_item_names` | string | Comma-separated item names on order | | `item_quantities` | string | Items with quantities (e.g., "Beer x 2, Burger x 1") | | `distinct_item_count` | number | Number of distinct line items | | `had_loyalty_at_purchase` | boolean | Whether customer had active loyalty | ### Segments | Segment | Description | |---------|-------------| | `in_store` | In-store sales channels | | `online` | Online sales channels | | `has_tip` | Orders with tips | | `no_tip` | Orders without tips | | `has_customer` | Orders linked to a customer | | `new_customers` | First-time customers only | | `returning_customers` | Returning customers only | ### Example: Daily sales by location ```json { "measures": ["Sales.net_sales", "Sales.order_count"], "dimensions": ["Sales.location_name"], "timeDimensions": [{ "dimension": "Sales.local_reporting_timestamp", "dateRange": "last 30 days", "granularity": "day" }] } ``` ### Example: Online vs in-store sales ```json { "measures": ["Sales.net_sales", "Sales.order_count"], "timeDimensions": [{ "dimension": "Sales.local_reporting_timestamp", "dateRange": "last 7 days" }], "segments": ["Sales.online"] } ``` ## ItemSales view The **ItemSales** view provides item-level sales data with related discounts, modifiers, and order-level metrics. Like the Sales view, it reports only on closed orders. ### Key measures | Measure | Type | Description | |---------|------|-------------| | `count` | count | Item transaction count | | `items_sold_count` | sum | Items sold (handles fractional quantities) | | `items_returned_count` | sum | Items returned | | `net_quantity` | sum | Net quantity (sales minus returns) | | `item_net_sales` | sum | Net item sales | | `item_net_sales_with_tax` | sum | Net item sales including tax | | `sales_gross_amount` | sum | Gross sales for sold items | | `returns_gross_amount` | sum | Gross return amount (negative) | | `avg_item_gross_sales` | avg | Average gross sales per item | | `avg_item_net_sales` | avg | Average net sales per item | | `discount_amount` | sum | Net discount amount | | `unique_customers` | countDistinct | Unique customers | ### Key dimensions | Dimension | Type | Description | |-----------|------|-------------| | `item_name` | string | Item name | | `category_name` | string | Item category | | `item_variation_name` | string | Item variation name | | `item_sku` | string | SKU | | `location_name` | string | Location display name | | `local_reporting_timestamp` | time | Local timestamp | | `local_date` | string | Local date (YYYY-MM-dd) | | `modifier_name` | string | Modifier name | | `modifier_list_name` | string | Modifier list/group name | | `discount_name` | string | Discount or comp name | | `channel_name` | string | Sales channel | | `customer_type` | string | NEW or RETURNING | | `transaction_type` | string | SALE or RETURN | | `payment_method` | string | Payment method | ### Segments | Segment | Description | |---------|-------------| | `sales` | Sales transactions only | | `returns` | Return transactions only | | `has_customer` | Orders with a linked customer | | `new_customers` | First-time customers | | `returning_customers` | Returning customers | ### Example: Top-selling items ```json { "measures": ["ItemSales.items_sold_count", "ItemSales.item_net_sales"], "dimensions": ["ItemSales.item_name", "ItemSales.category_name"], "timeDimensions": [{ "dimension": "ItemSales.local_reporting_timestamp", "dateRange": "last 30 days" }], "order": { "ItemSales.item_net_sales": "desc" }, "limit": 20 } ``` ## ModifierSales view The **ModifierSales** view provides modifier-level sales data for closed orders. {% aside type="info" %} **Important:** `modifier_name` is not unique across modifier lists. Always group by both `modifier_list_name` and `modifier_name` when analyzing individual modifiers. For example, "Large" could exist in both a "Size" list and a "Drink Size" list. {% /aside %} ### Measures | Measure | Type | Description | |---------|------|-------------| | `orders_count` | countDistinct | Unique orders with modifiers | | `quantity_sold` | sum | Modifiers sold | | `quantity_returned` | sum | Modifiers returned | | `net_quantity` | sum | Net quantity (sales minus returns) | | `gross_sales` | sum | Gross modifier sales | | `net_sales` | sum | Net modifier sales | | `returns_amount` | sum | Returns amount | | `avg_gross_sales` | avg | Average gross sales per modifier | | `avg_net_sales` | avg | Average net sales per modifier | | `avg_modifier_price` | avg | Average modifier price | ### Key dimensions | Dimension | Type | Description | |-----------|------|-------------| | `modifier_name` | string | Modifier name (not unique — always pair with `modifier_list_name`) | | `modifier_list_name` | string | Modifier list/group name (e.g., "Size", "Toppings") | | `item_name` | string | Item the modifier was applied to | | `location_name` | string | Location display name | | `local_reporting_timestamp` | time | Local timestamp | | `channel_name` | string | Sales channel | ### Example: Top modifier combinations ```json { "measures": ["ModifierSales.quantity_sold", "ModifierSales.net_sales"], "dimensions": [ "ModifierSales.modifier_list_name", "ModifierSales.modifier_name", "ModifierSales.item_name" ], "timeDimensions": [{ "dimension": "ModifierSales.local_reporting_timestamp", "dateRange": "last 30 days" }], "order": { "ModifierSales.quantity_sold": "desc" }, "limit": 20 } ``` ## Other views ### KDS (Kitchen Display System) Kitchen ticket and item performance metrics. Measures include ticket counts, prep/completion times, on-time vs late percentages, and item-level timing. {% aside type="warning" %} Kitchen tickets are not the same as sales orders. Use the Sales view for sales reporting and KDS for kitchen operations analysis. {% /aside %} ### SpeedOfService Combines speed-of-service metrics (order entry time, service time) with sales measures and optional KDS ticket metrics. Useful for analyzing operational efficiency alongside revenue. ### PeerBenchmark views Three views for benchmarking your sales against similar businesses in the same city and category: - **PeerBenchmarkHourlySales** — Sales distribution by hour of day vs peers - **PeerBenchmarkOrderValue** — Average order value vs peers - **PeerBenchmarkSalesTrend** — Year-over-year sales growth vs peers {% aside type="info" %} Not all merchants have a matching peer cohort. These views use `my_` prefix for your metrics and `peer_` prefix for peer-group metrics. Filter `cadence` to exactly one value (`weekly`, `monthly`, or `yearly`). {% /aside %} ## Views vs cubes | | Views | Cubes | |--|-------|-------| | **Pre-filtered** | Yes (e.g., Sales only includes closed orders) | No — you add segments yourself | | **Human-readable** | Yes (`location_name`, `channel_name`) | IDs only (`location_id`) | | **Pre-joined** | Yes (combines data from multiple cubes) | Single data domain | | **Recommended for** | Standard reporting | Advanced/raw data access | | **Segment needed?** | Optional (for further filtering) | Usually required (e.g., `closed_checks`) | --- # Understanding Dynamic Schemas > Source: https://developer.squareup.com/docs/reporting-api/understanding-dynamic-schemas > Status: BETA > Languages: All > Platforms: All Learn how the Reporting API dynamically generates schemas based on your available data and permissions. ## What is a dynamic schema? A **dynamic schema** means the API's queryable data model is discovered at runtime rather than hard-coded into your application. Instead of knowing at development time what fields are available, your application queries the `/v1/meta` endpoint to learn the current structure, then builds queries accordingly. This is fundamentally different from traditional REST APIs where you write code against a fixed set of known fields. ### Why dynamic schemas? The Reporting API schema is **backwards compatible** — Square does not remove or rename fields in breaking ways. However, the schema does **grow over time** as new measures, dimensions, and cubes are added. Dynamic discovery lets your application automatically pick up these additions without redeployment. ## Architecture pattern: Discovery Layer Implement a discovery layer that sits between your application logic and the API:  **Layer Responsibilities**: - **Application Layer**: UI, business logic, and reporting components - **Discovery Layer**: Metadata caching, query validation, and error handling - **API Layer**: Schema discovery and query execution ## Key principles 1. **Discover on startup**: Fetch metadata on application startup or periodically to surface new fields 2. **Cache metadata appropriately**: Balance freshness with API load (1-24 hour TTL) 3. **Build dynamic UIs**: Populate options from metadata, not hard-coded lists 4. **Validate before executing**: Check queries against cached metadata to catch errors early 5. **Refresh after errors**: If a query fails with "not found", invalidate cache and retry For implementation details — caching code, validation patterns, and parsing examples — see [Metadata Discovery](/docs/reporting-api/metadata-discovery). ## Schema change scenarios | Change | Client Impact | Action | |--------|---------------|--------| | Measure added | None | Discover automatically | | Dimension added | None | Discover automatically | | Measure removed | Queries using it fail | Validate queries against metadata before execution | | Dimension removed | Queries using it fail | Validate queries against metadata before execution | | Cube/view added | None | Discover automatically | | Cube/view removed | All queries to it fail | Validate cube names against metadata | | Member renamed | Queries using old name fail | Always discover member names from `/v1/meta` | | Type changed | May affect parsing/display | Check `type` field in metadata | --- # Metadata Discovery > Source: https://developer.squareup.com/docs/reporting-api/metadata-discovery > Status: BETA > Languages: All > Platforms: All Discover available metadata, fields, and data structures in the Reporting API to build effective queries. ## Overview The `/v1/meta` endpoint is the foundation of working with the Reporting API. It returns a complete description of the available data model, including: - **Cubes**: Logical groupings of related metrics - **Measures**: Numeric aggregations you can calculate - **Dimensions**: Attributes for grouping and filtering - **Segments**: Predefined filters for common business logic Understanding the metadata structure is essential for building dynamic, resilient clients. ## Views vs cubes The `/v1/meta` endpoint returns two types of queryable entities: **views** and **cubes**. Each entry has a `type` field (`"view"` or `"cube"`) that tells you which it is. ### Views — the recommended starting point **Views** are curated, pre-joined interfaces designed for specific reporting use cases. They combine data from multiple cubes, pre-filter where appropriate, and expose human-readable dimensions. For example, the `Sales` view: - Pre-filters to closed (fully settled) orders — no need to add `segments: ["Orders.closed_checks"]` - Includes `location_name` instead of just `location_id` - Has intuitive segments like `Sales.online` and `Sales.in_store` - Combines order, location, channel, and customer data in one queryable entity **Use views for most reporting tasks.** They're simpler to query and less error-prone. ### Cubes — raw data for advanced use **Cubes** are the underlying data building blocks. Each cube maps to a specific data domain (orders, items, payments, locations) and exposes its raw measures and dimensions. Cubes are useful when: - You need a field that isn't exposed in any view - You need raw, unfiltered data (e.g., including open or voided orders) - You're doing advanced analysis that doesn't fit a view's pre-defined shape {% aside type="warning" %} Some internal cubes are not intended for direct use. For example, `ClosedOrders` is an internal cube that backs the `Sales` view — its description says "Do not use directly — use Sales view instead." Always check a cube's description before querying it directly. {% /aside %} ### How to tell them apart ```bash # List all views curl -s -H "Authorization: Bearer $TOKEN" \ https://connect.squareup.com/reporting/v1/meta | \ jq '.cubes[] | select(.type == "view") | {name, description}' # List all cubes curl -s -H "Authorization: Bearer $TOKEN" \ https://connect.squareup.com/reporting/v1/meta | \ jq '.cubes[] | select(.type == "cube") | {name, description}' ``` ## Fetching metadata ### Basic Request ```bash curl -X GET "https://connect.squareup.com/reporting/v1/meta" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" ``` ### Response Structure The metadata response is a JSON object with the following top-level structure: ```json { "cubes": [ { "name": "Orders", "title": "Orders", "measures": [...], "dimensions": [...], "segments": [...] } ] } ``` ## Understanding cubes A **cube** represents a logical data model containing related measures and dimensions. Each cube corresponds to a business domain (e.g., Orders, Payments, Customers). ### Cube Structure ```json { "name": "Orders", "title": "Orders", "description": "Sales order data including payments, items, and fulfillment", "measures": [...], "dimensions": [...], "segments": [...] } ``` | Property_Name | Description | |---------------|-------------| | `name` | Unique identifier for the cube (used in queries) | | `title` | Human-readable display name | | `description` | Explanation of what data the cube contains | | `measures` | Array of available numeric aggregations | | `dimensions` | Array of available grouping/filtering attributes | | `segments` | Array of predefined filters | ### Common Views (Recommended) | View | Purpose | Data Freshness | |------|---------|----------------| | `Sales` | Order-level sales, revenue, tips, refunds — pre-filtered to closed orders | ~15 minutes | | `ItemSales` | Item-level sales with discounts, modifiers, and category data | ~15 minutes | | `ModifierSales` | Modifier-level sales with modifier list metadata | ~15 minutes | | `SpeedOfService` | Speed of service metrics and sales data | ~15 minutes | | `KDS` | Kitchen Display System ticket and item performance | ~15 minutes | ### Common Cubes (Advanced) | Cube | Purpose | Data Freshness | |------|---------|----------------| | `Orders` | Raw order data including open/voided orders | ~15 minutes | | `ItemTransactions` | Raw item transaction data | ~15 minutes | | `PaymentAndRefunds` | Payment and refund transaction details | ~15 minutes | | `Location` | Location reference data (name, timezone, status) | N/A (reference) | {% aside type="info" %} Use the `/v1/meta` endpoint or the [Schema Explorer](https://reporting-schema-explorer-production-f.squarecdn.com/schema-explorer.html) for the complete, up-to-date list of available views and cubes. {% /aside %} ## Understanding measures **Measures** are numeric values that can be aggregated (summed, averaged, counted, etc.). They represent the "what you're measuring" in your query. ### Measure Structure ```json { "name": "Orders.net_sales", "title": "Net Sales", "description": "Total sales after discounts and returns, excluding tax", "type": "number", "aggType": "sum", "drillMembers": ["Orders.order_id", "Orders.sale_timestamp"], "format": "currency" } ``` | Property_Name | Description | |---------------|-------------| | `name` | Fully qualified measure name (Cube.measure) | | `title` | Human-readable display name | | `description` | Detailed explanation of what the measure calculates | | `type` | Data type (number, string, time) | | `aggType` | Aggregation type (sum, count, avg, min, max) | | `drillMembers` | Suggested dimensions for drill-down analysis | | `format` | Display format hint (currency, percent, number) | ### Common Sales Measures From the `Orders` cube: | Measure | Description | Use Case | |---------|-------------|----------| | `Orders.net_sales` | Gross sales minus discounts/returns | Primary revenue metric | | `Orders.net_sales_with_tax` | Net sales plus sales tax | Gross revenue in tax-inclusive regions | | `Orders.top_line_product_sales` | Gross product sales before adjustments | Starting point for sales funnel | | `Orders.discounts_amount` | Total discounts applied | Promotional effectiveness | | `Orders.itemized_returns` | Value of returned items | Return rate analysis | | `Orders.sales_tax_amount` | Sales tax collected | Tax reporting | | `Orders.tips_amount` | Tips received (non-cash) | Service performance | | `Orders.comps_amount` | Complimentary items value | Comp tracking | ### Measure Types and Aggregations #### Sum Measures Most monetary measures use `sum` aggregation: ```json { "name": "Orders.net_sales", "aggType": "sum" } ``` #### Count Measures For counting distinct entities: ```json { "name": "Orders.count", "aggType": "count" } ``` #### Average Measures For calculating averages: ```json { "name": "Orders.avg_net_sales", "aggType": "avg" } ``` ## Understanding dimensions **Dimensions** are attributes used to group, filter, or slice your data. They represent the "how you're slicing" your measures. ### Dimension Structure ```json { "name": "Orders.location_id", "title": "Location ID", "description": "Unique identifier for the location where the order was placed", "type": "string", "suggestFilterValues": true } ``` | Property_Name | Description | |---------------|-------------| | `name` | Fully qualified dimension name (Cube.dimension) | | `title` | Human-readable display name | | `description` | Explanation of what the dimension represents | | `type` | Data type (string, number, time) | | `suggestFilterValues` | Whether the API can suggest filter values | ### Dimension Types #### Categorical Dimensions String-based attributes for grouping: ```json { "name": "Orders.location_id", "type": "string" } ``` Common categorical dimensions: - `Orders.location_id` — Group by location - `Orders.merchant_id` — Group by merchant - `Orders.sales_channel_id` — Group by sales channel (online, in-person, etc.) - `Orders.device_id` — Group by device #### Time Dimensions Special dimensions for time-series analysis: ```json { "name": "Orders.sale_timestamp", "type": "time" } ``` Time dimensions support granularity: - `day` — Daily aggregation - `week` — Weekly aggregation - `month` — Monthly aggregation - `quarter` — Quarterly aggregation - `year` — Yearly aggregation #### Local Time Dimensions For business-day alignment: ```json { "name": "Orders.local_date", "type": "time" } ``` Use local time dimensions when you need results aligned to the location's timezone rather than UTC. ### Common Dimensions | Dimension | Type | Description | |-----------|------|-------------| | `Orders.sale_timestamp` | time | UTC timestamp of sale | | `Orders.local_date` | time | Local date in location timezone | | `Orders.location_id` | string | Location identifier | | `Orders.merchant_id` | string | Merchant identifier | | `Orders.sales_channel_id` | string | Sales channel (e.g., online, in-person) | | `Orders.device_id` | string | Device used for transaction | | `Orders.customer_id` | string | Customer identifier | ## Understanding segments **Segments** are predefined filters that encapsulate common business logic. They ensure consistency across queries and simplify complex filtering. ### Segment Structure ```json { "name": "Orders.closed_checks", "title": "Closed Checks", "description": "Filters to fully paid and settled orders, matching Sales Summary behavior" } ``` | Property_Name | Description | |---------------|-------------| | `name` | Fully qualified segment name (Cube.segment) | | `title` | Human-readable display name | | `description` | Explanation of the filter logic | ### Common Segments | Segment | Purpose | |---------|--------| | `Orders.closed_checks` | Fully paid/settled orders (recommended for Sales Summary parity) | | `Orders.open_checks` | Orders awaiting payment | | `Orders.awaiting_capture` | Orders awaiting payment capture | | `Orders.fully_paid` | Fully paid orders | | `Orders.has_tip` | Orders that include a tip | | `Orders.no_tip` | Orders without a tip | ### Why Use Segments? Segments provide several benefits: 1. **Consistency**: Ensures everyone uses the same filter logic 2. **Simplicity**: Encapsulates complex conditions in a single reference 3. **Maintainability**: Filter logic can be updated centrally 4. **Report Parity**: Matches Square dashboard behavior **Example**: Instead of manually filtering by order state: ```json { "filters": [{ "member": "Orders.state", "operator": "equals", "values": ["COMPLETED"] }, { "member": "Orders.check_state", "operator": "equals", "values": ["CLOSED"] }] } ``` Simply use the segment: ```json { "segments": ["Orders.closed_checks"] } ``` ## Understanding meta tags Cubes, measures, dimensions, and segments can include a `meta` field with additional metadata tags. These tags provide UI hints, stability information, and semantic context beyond the basic properties. ### Quick Reference | Tag | Applies To | Purpose | |-----|------------|--------| | `label` | Dimensions, Measures, Segments | Human-readable display label (e.g., "Net sales") | | `tooltip` | Dimensions, Measures, Segments | Short tooltip text for UI hover states | | `context` | Dimensions, Measures, Segments | Extended context for LLMs and advanced UIs | | `stability` | Cubes (required), Dimensions, Measures | Data quality and API stability level | | `deprecated_at` | Cubes, Dimensions, Measures | Deprecation date (ISO 8601, when stability is `deprecated`) | | `values` | Dimensions, Segments | Allowed values for enum-like fields | | `translation_types` | Dimensions | Values requiring localization | | `examples` | Cubes | Example queries demonstrating cube usage | ### Stability levels The `stability` tag communicates the maturity and reliability of a cube or field: | Value | Data Quality | Breaking Changes | Description | |-------|--------------|------------------|-------------| | `preview` | Uncertain (may have missing data) | Highly likely | Early access, under active development | | `beta` | Uncertain (often missing historical data) | Unlikely but possible | Approaching general availability | | `ga` | Verified | None | General Availability — production-ready | | `deprecated` | N/A | N/A | Supported for 12+ months, then removed | Stability is set at the cube level and can be overridden at the field level. For example, a `ga` cube might have an individual measure marked as `preview`. ### Example: Meta tags in metadata response ```json { "name": "Orders.net_sales", "title": "Net Sales", "type": "number", "aggType": "sum", "format": "currency", "meta": { "label": "Net sales", "tooltip": "Amount of gross sales minus discounts, comps, returns, refunds, tips, taxes, fees, or gift card sales.", "stability": "beta" } } ``` ### Example: Dimension with allowed values ```json { "name": "Orders.check_state", "title": "Check State", "type": "string", "meta": { "label": "Check state", "tooltip": "The payment status of the order.", "values": ["AWAITING_CAPTURE_CHECK", "CLOSED_CHECK", "OPEN_CHECK"] } } ``` For the complete meta tags reference with detailed examples and best practices, see the [Meta Tags Reference](https://reporting-schema-explorer-production-f.squarecdn.com/meta-tags.html). ## Parsing metadata programmatically ### Example: Extract All Measures from a Cube ```python import requests import os def get_cube_measures(cube_name): """Fetch and parse measures from a specific cube.""" response = requests.get( 'https://connect.squareup.com/reporting/v1/meta', headers={'Authorization': f'Bearer {os.environ["SQUARE_ACCESS_TOKEN"]}'} ) metadata = response.json() # Find the cube cube = next((c for c in metadata['cubes'] if c['name'] == cube_name), None) if not cube: raise ValueError(f"Cube {cube_name} not found") # Extract measure information measures = [] for measure in cube['measures']: measures.append({ 'name': measure['name'], 'title': measure['title'], 'description': measure.get('description', ''), 'type': measure['type'], 'aggType': measure.get('aggType', 'unknown') }) return measures # Usage measures = get_cube_measures('Orders') for measure in measures: print(f"{measure['name']}: {measure['title']}") ``` ### Example: Build a Dimension Selector ```javascript async function buildDimensionSelector(cubeName) { const response = await fetch('https://connect.squareup.com/reporting/v1/meta', { headers: { 'Authorization': `Bearer ${process.env.SQUARE_ACCESS_TOKEN}` } }); const metadata = await response.json(); const cube = metadata.cubes.find(c => c.name === cubeName); if (!cube) { throw new Error(`Cube ${cubeName} not found`); } // Build selector options return cube.dimensions.map(dimension => ({ value: dimension.name, label: dimension.title, description: dimension.description, type: dimension.type })); } // Usage const dimensions = await buildDimensionSelector('Orders'); console.log(dimensions); ``` ### Example: Validate Query Against Metadata ```python def validate_query(query, metadata): """Validate that a query uses valid measures, dimensions, and segments.""" cube_name = query.get('cube', 'Orders') cube = next((c for c in metadata['cubes'] if c['name'] == cube_name), None) if not cube: return False, f"Cube {cube_name} not found" # Validate measures valid_measures = {m['name'] for m in cube['measures']} for measure in query.get('measures', []): if measure not in valid_measures: return False, f"Invalid measure: {measure}" # Validate dimensions valid_dimensions = {d['name'] for d in cube['dimensions']} for dimension in query.get('dimensions', []): if dimension not in valid_dimensions: return False, f"Invalid dimension: {dimension}" # Validate segments valid_segments = {s['name'] for s in cube['segments']} for segment in query.get('segments', []): if segment not in valid_segments: return False, f"Invalid segment: {segment}" return True, "Query is valid" # Usage query = { "measures": ["Orders.net_sales"], "dimensions": ["Orders.location_id"], "segments": ["Orders.closed_checks"] } is_valid, message = validate_query(query, metadata) print(f"Valid: {is_valid}, Message: {message}") ``` ## Caching metadata Metadata changes infrequently, so caching is recommended: ### Simple Time-Based Cache ```python import time class MetadataCache: def __init__(self, ttl_seconds=3600): self.cache = None self.last_fetch = 0 self.ttl = ttl_seconds def get(self): now = time.time() if self.cache is None or (now - self.last_fetch) > self.ttl: self.cache = self._fetch_metadata() self.last_fetch = now return self.cache def _fetch_metadata(self): response = requests.get( 'https://connect.squareup.com/reporting/v1/meta', headers={'Authorization': f'Bearer {os.environ["SQUARE_ACCESS_TOKEN"]}'} ) return response.json() def invalidate(self): self.cache = None # Usage cache = MetadataCache(ttl_seconds=3600) # 1 hour TTL metadata = cache.get() ``` ## Best practices ### 1. Cache Metadata Appropriately - **Recommended TTL**: 1-24 hours depending on your needs - **Invalidation**: Clear cache after query errors related to schema - **Startup**: Always fetch fresh metadata on application boot ### 2. Build Dynamic UIs Don't hard-code measure/dimension lists. Build them from metadata: ```javascript // Good: Dynamic const measures = metadata.cubes.Orders.measures.map(m => ({ label: m.title, value: m.name })); // Bad: Hard-coded const measures = [ { label: 'Net Sales', value: 'Orders.net_sales' }, { label: 'Tips', value: 'Orders.tips_amount' } ]; ``` ### 3. Validate Before Querying Check that your query components exist before sending to `/v1/load`: ```python # Validate first is_valid, error = validate_query(query, metadata) if not is_valid: raise ValueError(error) # Then execute results = execute_query(query) ``` ### 4. Use Segments for Report Parity Always use `Orders.closed_checks` when building sales reports that should match the Square dashboard: ```json { "measures": ["Orders.net_sales"], "segments": ["Orders.closed_checks"] } ``` ### 5. Document Your Dependencies Track which measures and dimensions your application requires: ```yaml # app-requirements.yml required_measures: - Orders.net_sales - Orders.tips_amount required_dimensions: - Orders.location_id - Orders.sale_timestamp required_segments: - Orders.closed_checks ``` ## Exploring metadata with jq Use `jq` to explore metadata from the command line: ### List all views (recommended) ```bash curl -s -H "Authorization: Bearer $TOKEN" \ https://connect.squareup.com/reporting/v1/meta | \ jq '.cubes[] | select(.type == "view") | .name' ``` ### List all cubes ```bash curl -s -H "Authorization: Bearer $TOKEN" \ https://connect.squareup.com/reporting/v1/meta | \ jq '.cubes[].name' ``` ### Get all measures from Orders cube ```bash curl -s -H "Authorization: Bearer $TOKEN" \ https://connect.squareup.com/reporting/v1/meta | \ jq '.cubes[] | select(.name == "Orders") | .measures[] | {name, title, description}' ``` ### Find dimensions by type ```bash curl -s -H "Authorization: Bearer $TOKEN" \ https://connect.squareup.com/reporting/v1/meta | \ jq '.cubes[] | select(.name == "Orders") | .dimensions[] | select(.type == "time") | .name' ``` ### List all segments ```bash curl -s -H "Authorization: Bearer $TOKEN" \ https://connect.squareup.com/reporting/v1/meta | \ jq '.cubes[] | select(.name == "Orders") | .segments[] | {name, title}' ``` --- # Measures > Source: https://developer.squareup.com/docs/reporting-api/query-construction/measures > Status: BETA > Languages: All > Platforms: All Learn how to define and use measures for aggregating numerical data in Reporting API queries. ## Overview {% aside type="info" %} * The measure names and examples below reflect the current schema. For the most up-to-date list of available measures, use the [Schema Explorer](https://reporting-schema-explorer-production-f.squarecdn.com/schema-explorer.html) or the `/v1/meta` endpoint. * For most sales reporting, use the `Sales` view instead of the `Orders` cube. The `Sales` view pre-filters to closed orders and includes human-readable dimensions. The examples below use `Orders.*` to teach measure concepts, but the same patterns apply to any view or cube. {% /aside %} Measures define **what you want to calculate**. They are numeric aggregations like sums, counts, or averages. ### Understanding Measure Types {% aside type="warning" %} Measure aggregation types (sum, count, average) are **pre-defined in the schema**. When you build a query, you simply select measures by name—the API automatically applies the correct aggregation logic. You **cannot** change a measure's aggregation type in your query. For example, `Orders.net_sales` is always a sum, and `Orders.count` is always a count. {% /aside %} #### How to Discover Measure Types There are two ways to find out what aggregation type a measure uses: 1. **Schema Explorer** (recommended for browsing): - Visit the [Schema Explorer](https://reporting-schema-explorer-production-f.squarecdn.com/schema-explorer.html) - Search for the measure you're interested in - View its `aggType` property 2. **Metadata API** (for programmatic discovery): - Call `/reporting/v1/meta` to get the full schema - Each measure includes an `aggType` field - See [Metadata Discovery](/docs/reporting-api/metadata-discovery) for details #### Common Measure Types | Measure Type | Description | Example Measures | |--------------|-------------|------------------| | `sum` | Adds up all values | `Orders.net_sales`, `Orders.tips_amount`, `Orders.discounts_amount` | | `count` | Counts number of records | `Orders.count`, `PaymentAndRefunds.count` | | `avg` | Calculates average value | `Orders.avg_net_sales`, `Orders.avg_total_sales` | ## Single measure ```json { "measures": ["Orders.net_sales"] } ``` This returns the **sum** of all order net sales (because `Orders.net_sales` has `aggType: "sum"`). ## Multiple measures ```json { "measures": [ "Orders.net_sales", "Orders.net_sales_with_tax", "Orders.tips_amount", "Orders.sales_tax_amount" ] } ``` All of these are sum measures, so you get the total for each. ## Examples by measure type ### Sum Measures (Most Common) Sum measures add up monetary amounts or quantities: ```json { "measures": [ "Orders.net_sales", // Sum of all net sales "Orders.tips_amount", // Sum of all tips "Orders.discounts_amount" // Sum of all discounts ], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": ["2024-01-01", "2024-01-31"] }], "segments": ["Orders.closed_checks"] } ``` **Result**: Total amounts for the entire period. ### Count Measures Count measures tell you how many records exist: ```json { "measures": [ "Orders.count", // Number of orders "PaymentAndRefunds.count" // Number of payments ], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": ["2024-01-01", "2024-01-31"], "granularity": "day" }], "segments": ["Orders.closed_checks"] } ``` **Result**: Daily count of orders and payments. ### Average Measures Average measures calculate mean values: ```json { "measures": [ "Orders.avg_net_sales", // Average net sales per order "Orders.count" // For context ], "dimensions": ["Orders.location_id"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": "last 30 days" }], "segments": ["Orders.closed_checks"] } ``` **Result**: Average order size per location (with order count for context). ### Combining Different Measure Types You can mix measure types in a single query: ```json { "measures": [ "Orders.net_sales", // SUM: Total revenue "Orders.count", // COUNT: Number of orders "Orders.avg_net_sales" // AVG: Average net sales per order ], "dimensions": ["Orders.location_id"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": "this month" }], "segments": ["Orders.closed_checks"], "order": { "Orders.net_sales": "desc" } } ``` **Result**: Complete performance metrics by location. ## Common measure combinations ### Sales Summary Metrics ```json { "measures": [ "Orders.top_line_product_sales", // SUM "Orders.discounts_amount", // SUM "Orders.itemized_returns", // SUM "Orders.net_sales", // SUM "Orders.sales_tax_amount", // SUM "Orders.tips_amount" // SUM ] } ``` ### Payment Analysis ```json { "measures": [ "Orders.net_sales", // SUM "Orders.count", // COUNT "Orders.avg_net_sales" // AVG ] } ``` ## Metadata example When you query `/reporting/v1/meta`, each measure includes its aggregation type: ```json { "name": "Orders.net_sales", "title": "Net Sales", "description": "Total sales after discounts and returns, excluding tax", "type": "number", "aggType": "sum", "format": "currency" } ``` ```json { "name": "Orders.count", "title": "Order Count", "description": "Number of orders", "type": "number", "aggType": "count" } ``` ```json { "name": "Orders.avg_net_sales", "title": "Average Net Sales", "description": "Average net sales per order", "type": "number", "aggType": "avg", "format": "currency" } ``` ## Key takeaways ✅ **Measure types are fixed** — You select measures by name; the API applies the aggregation ✅ **Use Schema Explorer** — Browse all measures and their types interactively ✅ **Use Metadata API** — Programmatically discover measure types for validation ✅ **Mix measure types** — Combine sum, count, and average measures in one query ❌ **Cannot override** — You cannot change a measure's aggregation type in your query --- # Dimensions > Source: https://developer.squareup.com/docs/reporting-api/query-construction/dimensions > Status: BETA > Languages: All > Platforms: All Understand how dimensions categorize and group data in your Reporting API query results. ## Overview Dimensions define **how you want to slice your measures**. They are categorical or time-based attributes that group your data. {% aside type="info" %} **Tip**: For most reporting, use views like `Sales` which provide human-readable dimensions (e.g., `Sales.location_name` instead of `Orders.location_id`). The examples below use `Orders.*` to teach dimension concepts, but the same patterns apply to any view or cube. {% /aside %} ## Single dimension ```json { "measures": ["Orders.net_sales"], "dimensions": ["Orders.location_id"] } ``` This returns net sales grouped by location. ## Multiple dimensions ```json { "measures": ["Orders.net_sales"], "dimensions": [ "Orders.location_id", "Orders.sales_channel_id" ] } ``` This returns net sales grouped by location AND sales channel (creating a cross-tab). {% aside type="info" %} **Dimensions use AND logic**: Multiple dimensions create a cross-tabulation with one row per unique combination. For OR logic (e.g., "location A OR location B"), use filters with the `equals` operator and multiple values instead. {% /aside %} {% aside type="warning" %} **Avoid double counting with multi-cube joins.** When querying across joined cubes (e.g., `Orders` and `ItemTransactions`), make a separate query for each level of aggregation. Summing measures from different cubes in a single query can cause double counting because of the join relationship (e.g., one order with 3 line items would count the order-level measure 3 times). For example, to get both order-level net sales and item-level gross sales: - **Query 1** (order level): `measures: ["Orders.net_sales"]` - **Query 2** (item level): `measures: ["ItemTransactions.sales_gross_amount"]` Do **not** combine them in a single query with item-level dimensions, as this would inflate the order-level totals. {% /aside %} ## Common dimension patterns ### By Location ```json { "measures": ["Orders.net_sales"], "dimensions": ["Orders.location_id"] } ``` ### By Sales Channel ```json { "measures": ["Orders.net_sales"], "dimensions": ["Orders.sales_channel_id"] } ``` ### By Customer ```json { "measures": ["Orders.net_sales", "Orders.count"], "dimensions": ["Orders.customer_id"] } ``` ### Multi-dimensional Analysis ```json { "measures": ["Orders.net_sales"], "dimensions": [ "Orders.location_id", "Orders.sales_channel_id", "Orders.device_id" ] } ``` This returns net sales for every unique combination of location, sales channel, and device. For example: one row for "Location A, Online, iPad", another for "Location A, Online, Terminal", another for "Location A, In-Person, Terminal", and so on. {% aside type="warning" %} **Performance Consideration**: More dimensions = more result rows. A query with 3 dimensions might return thousands of rows. Use `limit` to control result size. {% /aside %} ## Combining with time dimensions ```json { "measures": ["Orders.net_sales"], "dimensions": ["Orders.location_id"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": ["2024-01-01", "2024-01-31"], "granularity": "day" }] } ``` This returns daily sales by location (one row per day per location). --- # Time Dimensions > Source: https://developer.squareup.com/docs/reporting-api/query-construction/time-dimensions > Status: BETA > Languages: All > Platforms: All Configure time-based dimensions to analyze trends and patterns over specific date ranges in the Reporting API. ## Overview Time dimensions enable **[time-series analysis](https://en.wikipedia.org/wiki/Time_series)** with automatic date grouping. {% aside type="info" %} **Tip**: Views like `Sales` and `ItemSales` support the same time dimension patterns shown below. For most reporting, use `Sales.local_reporting_timestamp` for time-series analysis on the `Sales` view. {% /aside %} ## Basic time dimension ```json { "measures": ["Orders.net_sales"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": ["2024-01-01", "2024-01-31"], "granularity": "day" }] } ``` ## Time dimension structure ```json { "dimension": "Cube.time_dimension", // Required: which time field "dateRange": ["start", "end"], // Required: date range "granularity": "day" // Optional: grouping level } ``` ## Granularity options | Granularity | Description | Example Use Case | |-------------|-------------|------------------| | `hour` | Hourly buckets | Intra-day analysis | | `day` | Daily buckets | Daily sales reports | | `week` | Weekly buckets | Weekly trends | | `month` | Monthly buckets | Monthly performance | | `quarter` | Quarterly buckets | Quarterly reports | | `year` | Yearly buckets | Year-over-year analysis | ## Date range formats ### Absolute Dates ```json { "dateRange": ["2024-01-01", "2024-01-31"] } ``` ### Relative Dates ```json { "dateRange": "today" } ``` ```json { "dateRange": "yesterday" } ``` ```json { "dateRange": "this week" } ``` ```json { "dateRange": "last 7 days" } ``` ```json { "dateRange": "last 30 days" } ``` ```json { "dateRange": "this month" } ``` ```json { "dateRange": "last month" } ``` {% aside type="info" %} **All relative date formats tested**: We've verified that all the relative date formats shown above work with the Square Reporting API, including `today`, `yesterday`, `this week`, `last 7 days`, `last 30 days`, `this month`, `last month`, `this year`, `last year`, `last week`, `this quarter`, and `last quarter`. {% /aside %} ## Time dimension examples ### Daily Sales for January ```json { "measures": ["Orders.net_sales"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": ["2024-01-01", "2024-01-31"], "granularity": "day" }] } ``` Returns net sales for each day in January—one row per day with the date and sales total. ### Hourly Sales Today ```json { "measures": ["Orders.net_sales"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": "today", "granularity": "hour" }] } ``` Returns net sales for each hour of today—useful for intra-day monitoring and real-time dashboards. ### Monthly Sales Year-to-Date ```json { "measures": ["Orders.net_sales"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": ["2024-01-01", "2024-12-31"], "granularity": "month" }] } ``` Returns net sales for each month in 2024—one row per month showing monthly totals. ### No Granularity (Total Only) ```json { "measures": ["Orders.net_sales"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": ["2024-01-01", "2024-01-31"] // No granularity = single total for the range }] } ``` Returns a single total for all of January—no time breakdown, just one aggregated value for the entire period. ## Combining time dimensions with regular dimensions ```json { "measures": ["Orders.net_sales"], "dimensions": ["Orders.location_id"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": ["2024-01-01", "2024-01-31"], "granularity": "day" }] } ``` This returns daily sales by location (one row per day per location). ## Local timestamps vs UTC timestamps The Reporting API offers two approaches to time-based queries, and choosing the right one is important for matching merchant expectations. ### Approach 1: UTC timestamps (`sale_timestamp`) `Orders.sale_timestamp` stores the sale time in UTC. When you use this with `timeDimensions`, the API groups data by UTC time boundaries. ```json { "measures": ["Orders.net_sales"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": ["2024-01-01", "2024-01-31"], "granularity": "day" }], "segments": ["Orders.closed_checks"] } ``` **When to use**: Backend analytics, cross-timezone aggregation, or when UTC alignment is acceptable. **Caveat**: For merchants in non-UTC timezones, UTC-based daily grouping won't match what they consider a "business day." A sale at 11pm local time might fall into the next day in UTC. This can cause discrepancies of ~7% or more compared to the Square Dashboard for multi-timezone merchants. ### Approach 2: Local time dimensions (`local_date`, `local_reporting_timestamp`) The `Orders.local_date` dimension (type: `string`) represents the date in the **location's local timezone**. This aligns with what merchants see in the Square Dashboard. ```json { "measures": ["Orders.net_sales"], "dimensions": ["Orders.local_date"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": ["2024-01-01", "2024-01-31"] }], "segments": ["Orders.closed_checks"] } ``` **When to use**: Building reports that should match the Square Dashboard, or any merchant-facing reporting where "today's sales" should align with the merchant's local day. Additional local time dimensions are available for finer-grained local time analysis: - `Orders.local_day_of_week` — Day of the week in local timezone - `Orders.local_hour` — Hour of the day in local timezone - `Orders.local_month` — Month in local timezone - `Orders.local_year` — Year in local timezone ### Which approach should I use? | Scenario | Recommended Approach | Dimension | |----------|---------------------|-----------| | Match Square Dashboard | Local time | `Orders.local_date` | | Merchant-facing daily reports | Local time | `Orders.local_date` | | Hourly analysis (local business hours) | Local time | `Orders.local_hour` | | Cross-timezone backend analytics | UTC | `Orders.sale_timestamp` | | Time-series with granularity grouping | UTC | `Orders.sale_timestamp` | {% aside type="info" %} **Tip**: If your reports need to match the Square Dashboard closely, use `Orders.local_date` for daily grouping rather than `Orders.sale_timestamp` with `day` granularity. {% /aside %} --- # Segments > Source: https://developer.squareup.com/docs/reporting-api/query-construction/segments > Status: BETA > Languages: All > Platforms: All Use segments to filter and isolate specific subsets of data in your Reporting API queries. ## Overview Segments are **predefined filters** that encapsulate business logic. They provide consistent, reusable filtering that matches Square dashboard behavior. {% aside type="info" %} **Tip**: Views like `Sales` include curated segments such as `Sales.online`, `Sales.in_store`, `Sales.new_customers`, and `Sales.returning_customers`. These are often simpler than building equivalent filters on raw cubes. {% /aside %} ## Using a segment ```json { "measures": ["Orders.net_sales"], "segments": ["Orders.closed_checks"] } ``` Returns net sales for only fully paid orders—excludes open checks and unpaid orders. ## Multiple segments ```json { "measures": ["Sales.net_sales"], "segments": [ "Sales.in_store", "Sales.has_tip" ] } ``` Returns net sales for orders that are both in-store AND have a tip. Multiple segments are combined with AND logic. {% aside type="info" %} **Segments vs Filters for AND/OR Logic**: Segments are always combined with AND logic (all conditions must be true). For OR logic within a single condition (e.g., "location A OR location B"), use a filter with the `equals` operator and multiple values. You can combine both—use segments for business logic AND filters for specific OR conditions. {% /aside %} ## Common segments | Segment | Purpose | |---------|---------| | `Orders.closed_checks` | Fully paid/settled orders (recommended for sales reports) | | `Orders.open_checks` | Orders awaiting payment | | `Orders.awaiting_capture` | Orders awaiting payment capture | | `Orders.fully_paid` | Fully paid orders | | `Orders.has_tip` | Orders with a tip | | `Orders.no_tip` | Orders without a tip | | `Orders.has_customer` | Orders linked to a customer | ## View segments Views provide additional curated segments that simplify common filtering patterns. ### Sales view segments | Segment | Description | |---------|-------------| | `Sales.online` | Online sales channels | | `Sales.in_store` | In-store sales channels | | `Sales.has_tip` | Orders with a non-zero tip | | `Sales.no_tip` | Orders with no tip | | `Sales.has_customer` | Orders linked to an identified customer | | `Sales.new_customers` | Only new customers | | `Sales.returning_customers` | Only returning customers | ### ItemSales view segments | Segment | Description | |---------|-------------| | `ItemSales.sales` | Only sales transactions (excludes returns) | | `ItemSales.returns` | Only return transactions | | `ItemSales.has_customer` | Orders linked to an identified customer | | `ItemSales.new_customers` | Only new customers | | `ItemSales.returning_customers` | Only returning customers | {% aside type="info" %} **Tip**: View segments like `Sales.online` and `Sales.in_store` replace the need to filter by sales channel ID manually. Use views for the simplest querying experience. {% /aside %} ## Why use segments? ### Without segment (manual filtering): ```json { "measures": ["Orders.net_sales"], "filters": [{ "member": "Orders.state", "operator": "equals", "values": ["COMPLETED"] }, { "member": "Orders.check_state", "operator": "equals", "values": ["CLOSED"] }] } ``` ### With segment (recommended): ```json { "measures": ["Orders.net_sales"], "segments": ["Orders.closed_checks"] } ``` ### Benefits: - **Simpler queries** - One segment replaces multiple filter conditions - **Consistent logic** - Same business rules across all queries - **Dashboard parity** - Matches Square dashboard behavior - **Centrally maintained** - Filter definitions managed by Square ## Best practices ### 1. Always Use `Orders.closed_checks` for Sales Reports ```json { "measures": ["Orders.net_sales"], "segments": ["Orders.closed_checks"] } ``` This ensures you're only counting fully paid orders, matching dashboard totals. ### 2. Combine Segments with Filters ```json { "measures": ["Orders.net_sales"], "segments": ["Orders.closed_checks"], "filters": [{ "member": "Orders.location_id", "operator": "equals", "values": ["LOC_123"] }] } ``` Use segments for business logic, filters for specific criteria. ### 3. Check Available Segments in Metadata Not all cubes have the same segments. Use the metadata endpoint or Schema Explorer to see what's available for each cube. --- # Filters > Source: https://developer.squareup.com/docs/reporting-api/query-construction/filters > Status: BETA > Languages: All > Platforms: All Apply filters to narrow query results and retrieve precisely the data you need from the Reporting API. ## Overview Filters provide **custom filtering** beyond segments. Use filters for specific IDs, numeric thresholds, or OR logic. {% aside type="info" %} **Understanding Filter Logic**: Multiple filters in an array are combined with AND logic. However, the `equals` operator with multiple values uses OR logic (e.g., "location A OR location B"). This allows you to combine AND logic between different filter conditions while using OR logic within a single condition. {% /aside %} ## Filter structure ```json { "member": "Cube.dimension", "operator": "equals", "values": ["value1", "value2"] } ``` ## Filter operators | Operator | Description | Example | |----------|-------------|---------| | `equals` | Exact match (OR with multiple values) | `"values": ["L123"]` | | `notEquals` | Not equal | `"values": ["L123"]` | | `contains` | String contains | `"values": ["cafe"]` | | `notContains` | String doesn't contain | `"values": ["test"]` | | `startsWith` | String starts with | `"values": ["L"]` | | `endsWith` | String ends with | `"values": ["ABC"]` | | `gt` | Greater than | `"values": ["100"]` | | `gte` | Greater than or equal | `"values": ["100"]` | | `lt` | Less than | `"values": ["100"]` | | `lte` | Less than or equal | `"values": ["100"]` | | `inDateRange` | Date in range | `"values": ["2024-01-01", "2024-01-31"]` | | `notInDateRange` | Date not in range | `"values": ["2024-01-01", "2024-01-31"]` | | `set` | Value is not null | No values needed | | `notSet` | Value is null | No values needed | ## Filter examples ### Filter by Specific Location ```json { "measures": ["Orders.net_sales"], "filters": [{ "member": "Orders.location_id", "operator": "equals", "values": ["L1234567890ABC"] }] } ``` ### Filter by Multiple Locations (OR Logic) ```json { "measures": ["Orders.net_sales"], "filters": [{ "member": "Orders.location_id", "operator": "equals", "values": ["L1234567890ABC", "L9876543210XYZ"] }] } ``` Returns net sales from location A OR location B—the `equals` operator with multiple values uses OR logic. ### Exclude Test Locations ```json { "measures": ["Orders.net_sales"], "filters": [{ "member": "Orders.location_id", "operator": "notContains", "values": ["test"] }] } ``` ### Filter by Sales Threshold ```json { "measures": ["Orders.net_sales"], "dimensions": ["Orders.location_id"], "filters": [{ "member": "Orders.net_sales", "operator": "gte", "values": ["1000"] }] } ``` ### Multiple Filters (AND Logic) ```json { "measures": ["Orders.net_sales"], "filters": [ { "member": "Orders.location_id", "operator": "equals", "values": ["L1234567890ABC"] }, { "member": "Orders.sales_channel_id", "operator": "equals", "values": ["online"] } ] } ``` All filters are combined with AND logic. ## Filters vs segments | Use Filters When | Use Segments When | |------------------|-------------------| | Filtering by specific IDs | Applying common business logic | | Dynamic user-selected filters | Ensuring report consistency | | One-off analysis | Matching dashboard behavior | | Numeric thresholds | Predefined filter combinations | | OR logic needed | AND logic sufficient | --- # Ordering & Pagination > Source: https://developer.squareup.com/docs/reporting-api/query-construction/ordering-and-pagination > Status: BETA > Languages: All > Platforms: All Control the sort order of results and paginate through large datasets in Reporting API responses. {% aside type="info" %} **Tip**: The ordering and pagination patterns below work the same way with views (like `Sales`) and cubes (like `Orders`). {% /aside %} ## Ordering results Control the sort order of your results. ### Order Structure ```json { "order": { "Cube.measure_or_dimension": "asc" // or "desc" } } ``` ### Order Examples #### Sort by Measure (Descending) ```json { "measures": ["Orders.net_sales"], "dimensions": ["Orders.location_id"], "order": { "Orders.net_sales": "desc" } } ``` Returns locations sorted by net sales (highest first). #### Sort by Dimension (Ascending) ```json { "measures": ["Orders.net_sales"], "dimensions": ["Orders.location_id"], "order": { "Orders.location_id": "asc" } } ``` Returns locations sorted alphabetically. #### Sort by Time (Ascending) ```json { "measures": ["Orders.net_sales"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": ["2024-01-01", "2024-01-31"], "granularity": "day" }], "order": { "Orders.sale_timestamp": "asc" } } ``` Returns days in chronological order. #### Multiple Sort Keys ```json { "measures": ["Orders.net_sales"], "dimensions": ["Orders.location_id", "Orders.sales_channel_id"], "order": [ ["Orders.location_id", "asc"], ["Orders.net_sales", "desc"] ] } ``` Sorts by location first, then by net sales within each location. ## Limiting and pagination Control the number of results returned. ### Limit ```json { "measures": ["Orders.net_sales"], "dimensions": ["Orders.location_id"], "limit": 10 } ``` Returns top 10 results. ### Offset ```json { "measures": ["Orders.net_sales"], "dimensions": ["Orders.location_id"], "limit": 10, "offset": 10 } ``` Returns results 11-20 (skips first 10). ### Pagination Pattern ```javascript async function fetchAllResults(baseQuery, pageSize = 100) { let allResults = []; let offset = 0; let hasMore = true; while (hasMore) { const query = { ...baseQuery, limit: pageSize, offset: offset }; const response = await executeQuery(query); allResults.push(...response.data); hasMore = response.data.length === pageSize; offset += pageSize; } return allResults; } ``` {% aside type="warning" %} **Use a stable sort key when paginating.** If results are not sorted by a deterministic key, rows can shift between pages — causing duplicates or missed rows. Always include an `order` clause with a stable key (such as a unique dimension or a combination like timestamp + ID) when using `limit`/`offset` pagination. {% /aside %} {% aside type="warning" %} **Performance Consideration**: Large result sets (>10,000 rows) may be slow. Consider: - Adding filters to reduce scope - Using fewer dimensions - Increasing aggregation granularity (e.g., month instead of day) {% /aside %} ## Best practices ### 1. Order Time Series Chronologically ```json { "order": { "Orders.sale_timestamp": "asc" } } ``` For time-series data, ascending order shows progression over time. ### 2. Order Rankings by Measure ```json { "order": { "Orders.net_sales": "desc" } } ``` For rankings (top locations, top customers), descending order shows highest first. ### 3. Always Set Limits with Multiple Dimensions ```json { "dimensions": ["Orders.location_id", "Orders.sales_channel_id"], "limit": 1000 } ``` Multiple dimensions can create thousands of rows—always limit results. ### 4. Use Reasonable Page Sizes ```json { "limit": 100 // Good for UI display } ``` or ```json { "limit": 1000 // Good for exports } ``` Avoid fetching unlimited results. ### 5. Use Stable Sort Keys for Pagination When paginating through results, always sort by a deterministic key: ```json { "measures": ["Orders.net_sales"], "dimensions": ["Orders.order_id"], "order": { "Orders.order_id": "asc" }, "limit": 100, "offset": 0 } ``` Without a stable sort key, the database may return rows in a different order between pages, leading to duplicates or gaps. --- # Real-World Scenarios > Source: https://developer.squareup.com/docs/reporting-api/real-world-scenarios > Status: BETA > Languages: All > Platforms: All Explore practical examples and common use cases for building Reporting API queries in production applications. ## Overview These examples show how to combine measures, dimensions, segments, and filters to answer complex business questions for restaurant operations. {% aside type="info" %} These examples use **views** (`Sales`, `ItemSales`) which are the recommended way to query the Reporting API. Views pre-join data from multiple cubes and pre-filter for common use cases. For the most up-to-date field names, check the [Schema Explorer](https://reporting-schema-explorer-production-f.squarecdn.com/schema-explorer.html) or the `/v1/meta` endpoint. {% /aside %} ## Scenario 1: Daily sales by location **Business Question**: "How are my locations performing day by day?" ```json { "measures": [ "Sales.net_sales", "Sales.order_count", "Sales.avg_net_sales" ], "dimensions": ["Sales.location_name"], "timeDimensions": [{ "dimension": "Sales.local_reporting_timestamp", "dateRange": "last 30 days", "granularity": "day" }] } ``` **What this returns**: Daily net sales, order count, and average order value for each location over the last 30 days. Notice there's no `segments` needed — the `Sales` view automatically filters to closed orders, and gives you `location_name` directly instead of a raw ID. ## Scenario 2: Online vs in-store sales **Business Question**: "How do my online sales compare to in-store?" **Online sales:** ```json { "measures": [ "Sales.net_sales", "Sales.order_count", "Sales.tips_amount" ], "dimensions": ["Sales.channel_name"], "timeDimensions": [{ "dimension": "Sales.local_reporting_timestamp", "dateRange": "this month", "granularity": "day" }], "segments": ["Sales.online"] } ``` **In-store sales:** ```json { "measures": [ "Sales.net_sales", "Sales.order_count", "Sales.tips_amount" ], "dimensions": ["Sales.channel_name"], "timeDimensions": [{ "dimension": "Sales.local_reporting_timestamp", "dateRange": "this month", "granularity": "day" }], "segments": ["Sales.in_store"] } ``` **What this returns**: Daily sales by channel for the current month. Use the `Sales.online` or `Sales.in_store` segments to filter by sales channel type, or omit the segment and group by `Sales.channel_name` to see all channels. ## Scenario 3: Top-selling items **Business Question**: "What are my best-selling items by revenue?" ```json { "measures": [ "ItemSales.item_net_sales", "ItemSales.items_sold_count" ], "dimensions": [ "ItemSales.item_name", "ItemSales.category_name" ], "timeDimensions": [{ "dimension": "ItemSales.transacted_at", "dateRange": "last 30 days" }], "segments": ["ItemSales.sales"], "order": { "ItemSales.item_net_sales": "desc" }, "limit": 20 } ``` **What this returns**: Top 20 items ranked by net sales, with category and quantity for context. The `ItemSales` view pre-joins item data with order and location information. Use the `ItemSales.sales` segment to exclude returns. ## Scenario 4: Refund analysis by location **Business Question**: "How much was refunded last month, and which locations had the most refunds?" ```json { "measures": [ "Sales.refunds_by_amount_amount", "Sales.net_sales", "Sales.order_count" ], "dimensions": ["Sales.location_name"], "timeDimensions": [{ "dimension": "Sales.local_reporting_timestamp", "dateRange": "last month" }], "filters": [{ "member": "Sales.refunds_by_amount_amount", "operator": "gt", "values": ["0"] }], "order": { "Sales.refunds_by_amount_amount": "desc" } } ``` **What this returns**: Refund totals by location name for orders that had refunds, sorted by highest refund amount. Compare against net sales to understand refund rates per location. ## Scenario 5: Team member performance **Business Question**: "Which team members are collecting the most in sales and tips?" ```json { "measures": [ "Sales.net_sales", "Sales.order_count", "Sales.tips_amount" ], "dimensions": ["Sales.employee_full_name"], "timeDimensions": [{ "dimension": "Sales.local_reporting_timestamp", "dateRange": "this month" }], "segments": ["Sales.has_tip"], "order": { "Sales.net_sales": "desc" } } ``` **What this returns**: Sales and tip totals by team member name for the current month, filtered to orders with tips. The `Sales` view gives you `employee_full_name` directly — no need to look up team member IDs. ## Key patterns in these scenarios | Pattern | Use Case | Components Used | |---------|----------|-----------------| | **Location comparison** | Compare performance across locations | `Sales` view + `location_name` dimension | | **Channel analysis** | Online vs in-store | `Sales` view + `Sales.online` / `Sales.in_store` segments | | **Item-level analysis** | Track menu item performance | `ItemSales` view + `item_name` dimension + `sales` segment | | **Refund filtering** | Find orders with refunds | `Sales` view + filter on `refunds_by_amount_amount > 0` | | **Team performance** | Identify top performers | `Sales` view + `employee_full_name` dimension + `has_tip` segment | ## Tips for building complex queries 1. **Start with a view** — Use `Sales` for order-level reporting, `ItemSales` for item-level analysis 2. **Add time dimensions** — Always scope to a specific date range 3. **Layer in dimensions** — Add location, channel, or team member dimensions for grouping 4. **Apply filters** — Use filters for thresholds or exclusions 5. **Use view segments** — `Sales.online`, `Sales.in_store`, `Sales.has_tip`, `ItemSales.sales`, etc. 6. **Set reasonable limits** — Especially important with multiple dimensions 7. **Order meaningfully** — Sort by the metric that matters most for your analysis 8. **Use the right view** — `Sales` for orders, `ItemSales` for items, `ModifierSales` for modifiers ## Common query templates ### Sales Summary ```json { "measures": [ "Sales.top_line_product_sales", "Sales.discounts_amount", "Sales.itemized_returns", "Sales.net_sales", "Sales.sales_tax_amount", "Sales.tips_amount", "Sales.total_collected_amount" ], "timeDimensions": [{ "dimension": "Sales.local_reporting_timestamp", "dateRange": "last 30 days", "granularity": "day" }] } ``` ### Location Performance ```json { "measures": ["Sales.net_sales", "Sales.order_count"], "dimensions": ["Sales.location_name"], "timeDimensions": [{ "dimension": "Sales.local_reporting_timestamp", "dateRange": "this month" }], "order": {"Sales.net_sales": "desc"} } ``` --- # Error Handling > Source: https://developer.squareup.com/docs/reporting-api/error-handling > Status: BETA > Languages: All > Platforms: All Handle errors, rate limits, and edge cases when integrating with the Square Reporting API endpoints. ## Overview The Reporting API uses standard HTTP status codes and returns structured error responses. Understanding error patterns and implementing proper retry logic is essential for building resilient applications. ## Common first-time issues If you're just getting started and hitting errors, here are the most frequent issues and quick fixes: **403 Forbidden** — Your access token is invalid or missing. 1. Verify your token is correct and hasn't expired 2. Check the header format: `Authorization: Bearer YOUR_TOKEN` 3. Ensure you're using a personal access token or an OAuth token with `REPORTING_READ` scope **400 Bad Request — "Cube not found"** — The cube or view name in your query doesn't exist. 1. Fetch available names via `GET /v1/meta` 2. Names are case-sensitive — use them exactly as shown in metadata 3. Views (like `Sales`) are often easier to start with than cubes **Empty results (`"data": []`)** — Your query succeeded but matched no data. 1. Verify your date range includes actual transaction data 2. Try a broader date range to confirm data exists 3. Make sure you're using the right view/cube for your use case **"Continue wait" never resolves** — Your query is too complex or the date range is too large. 1. Reduce the date range 2. Remove unnecessary dimensions 3. Simplify to fewer measures 4. See [Continue wait pattern](#continue-wait-pattern) below for proper retry logic For detailed explanations, see the specific sections below. ## HTTP status codes | Status Code | Meaning | Common Causes | |-------------|---------|---------------| | 200 | Success | Query executed successfully or "Continue wait" | | 400 | Bad Request | Invalid query structure, unknown measures/dimensions | | 401 | Unauthorized | Missing authentication token | | 403 | Forbidden | Invalid or expired token, insufficient permissions | | 404 | Not Found | Invalid endpoint path | | 429 | Too Many Requests | Rate limit exceeded | | 500 | Internal Server Error | Server-side error, database timeout | | 503 | Service Unavailable | Service temporarily unavailable | ## Error response structure All errors return a JSON object with an `error` field: ```json { "error": "Error message" } ``` ### Example Error Responses #### Invalid Measure ```json { "error": "Measure 'Orders.invalid_measure' not found" } ``` #### Invalid Authentication ```json { "error": "Unauthorized" } ``` #### Database Timeout ```json { "error": "Query execution timeout" } ``` ## Continue wait pattern The most common "error" you'll encounter isn't actually an error—it's the **Continue wait** response. ### What is Continue Wait? When a query takes time to process, the API returns: ```json { "error": "Continue wait" } ``` **HTTP Status**: 200 (Success) This is **not an error**. It means: - Your query is valid - Processing is underway - You should retry the same request ### Why Continue Wait Exists Complex queries may need to: - Scan large amounts of data - Perform multiple aggregations - Wait for pre-aggregation builds Rather than holding the connection open, the API returns quickly and asks you to poll. ### Continue Wait Flow  ### Implementing Continue Wait Retry #### Python Example ```python import time import requests def execute_with_retry(query, max_attempts=10, delay_seconds=2): """Execute query with automatic Continue wait retry.""" url = "https://connect.squareup.com/reporting/v1/load" headers = { "Authorization": f"Bearer {SQUARE_ACCESS_TOKEN}", "Content-Type": "application/json" } for attempt in range(1, max_attempts + 1): response = requests.post(url, headers=headers, json=query) # Check for HTTP errors if response.status_code != 200: raise Exception(f"HTTP {response.status_code}: {response.text}") data = response.json() # Check for Continue wait if data.get("error") == "Continue wait": print(f"Attempt {attempt}/{max_attempts}: Query processing...") time.sleep(delay_seconds) continue # Success! return data raise TimeoutError(f"Query did not complete after {max_attempts} attempts") # Usage try: results = execute_with_retry(my_query) print(f"Received {len(results['data'])} rows") except TimeoutError as e: print(f"Query timed out: {e}") except Exception as e: print(f"Query failed: {e}") ``` #### JavaScript Example ```javascript async function executeWithRetry(query, maxAttempts = 10, delayMs = 2000) { const url = "https://connect.squareup.com/reporting/v1/load"; const headers = { "Authorization": `Bearer ${SQUARE_ACCESS_TOKEN}`, "Content-Type": "application/json" }; for (let attempt = 1; attempt <= maxAttempts; attempt++) { const response = await fetch(url, { method: "POST", headers: headers, body: JSON.stringify(query) }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${await response.text()}`); } const data = await response.json(); if (data.error === "Continue wait") { console.log(`Attempt ${attempt}/${maxAttempts}: Query processing...`); await new Promise(resolve => setTimeout(resolve, delayMs)); continue; } // Success! return data; } throw new Error(`Query did not complete after ${maxAttempts} attempts`); } // Usage try { const results = await executeWithRetry(myQuery); console.log(`Received ${results.data.length} rows`); } catch (error) { console.error(`Query failed: ${error.message}`); } ``` ### Continue Wait Best Practices 1. **Use exponential backoff**: Start with 2s, increase to 5s, then 10s 2. **Set reasonable max attempts**: 10-20 attempts is typical 3. **Log retry attempts**: Help with debugging 4. **Don't retry forever**: Fail gracefully after max attempts 5. **Keep the same query**: Don't modify the query between retries #### Exponential Backoff Example ```python def execute_with_exponential_backoff(query, max_attempts=10): """Execute with exponential backoff.""" delays = [2, 2, 5, 5, 10, 10, 15, 15, 20, 20] # seconds for attempt in range(max_attempts): response = requests.post(url, headers=headers, json=query) if response.status_code != 200: raise Exception(f"HTTP {response.status_code}: {response.text}") data = response.json() if data.get("error") == "Continue wait": delay = delays[attempt] if attempt < len(delays) else 20 print(f"Attempt {attempt + 1}: Waiting {delay}s...") time.sleep(delay) continue return data raise TimeoutError("Query timed out") ``` ## Validation errors Validation errors occur when your query references invalid entities or has structural issues. ### Common Validation Errors #### Unknown Measure **Error**: ```json { "error": "Measure 'Orders.unknown_measure' not found" } ``` **Cause**: The measure doesn't exist in the current schema. **Solution**: 1. Fetch fresh metadata: `GET /v1/meta` 2. Verify the measure name (case-sensitive) 3. Check if the measure was deprecated **Prevention**: ```python def validate_measures(query, metadata): """Validate measures before querying.""" valid_measures = set() for cube in metadata['cubes']: valid_measures.update(m['name'] for m in cube['measures']) for measure in query.get('measures', []): if measure not in valid_measures: raise ValueError(f"Invalid measure: {measure}") ``` #### Unknown Dimension **Error**: ```json { "error": "Dimension 'Orders.unknown_dimension' not found" } ``` **Solution**: Same as unknown measure—validate against metadata. #### Unknown Segment **Error**: ```json { "error": "Segment 'Orders.unknown_segment' not found" } ``` **Solution**: Check available segments in metadata. #### Invalid Date Range **Error**: ```json { "error": "Invalid date range format" } ``` **Cause**: Date format is incorrect. **Solution**: Use `YYYY-MM-DD` format: ```json { "dateRange": ["2024-01-01", "2024-01-31"] } ``` #### Missing Required Fields **Error**: ```json { "error": "Time dimension must include 'dimension' and 'dateRange'" } ``` **Solution**: Ensure time dimensions are complete: ```json { "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": ["2024-01-01", "2024-01-31"] }] } ``` ## Authentication errors ### 401 Unauthorized **Error**: ```json { "error": "Unauthorized" } ``` **Causes**: - Missing `Authorization` header - Malformed header (not `Bearer TOKEN`) - Empty token **Solution**: ```bash # Correct format Authorization: Bearer YOUR_ACCESS_TOKEN # Wrong formats Authorization: YOUR_ACCESS_TOKEN # Missing "Bearer" Authorization: Bearer # Missing token ``` ### 403 Forbidden **Error**: ```json { "error": "Forbidden" } ``` **Causes**: - Invalid access token - Expired token - Token lacks required permissions - Using OAuth token without REPORTING_READ scope **Solution**: 1. Verify token in Developer Console 2. Generate a new personal access token 3. Check token hasn't expired 4. Ensure you're using a personal access token (not OAuth) ## Rate limiting ### 429 Too Many Requests **Error**: ```json { "error": "Rate limit exceeded" } ``` **Solution**: Implement exponential backoff with `Retry-After` header: ```python def execute_with_rate_limit_retry(query, max_retries=3): """Execute with rate limit retry.""" for attempt in range(max_retries): response = requests.post(url, headers=headers, json=query) if response.status_code == 429: retry_after = int(response.headers.get('Retry-After', 60)) print(f"Rate limited. Retrying after {retry_after}s...") time.sleep(retry_after) continue if response.status_code != 200: raise Exception(f"HTTP {response.status_code}") return response.json() raise Exception("Rate limit exceeded after retries") ``` ### Rate Limit Best Practices 1. **Cache results**: Don't re-query for the same data 2. **Batch queries**: Combine multiple measures in one query 3. **Use appropriate cache strategies**: Leverage server-side caching 4. **Respect Retry-After**: Don't retry immediately 5. **Monitor usage**: Track query volume ## Server errors ### 500 Internal Server Error **Error**: ```json { "error": "Internal server error" } ``` **Causes**: - Database timeout - Server-side bug - Resource exhaustion **Solution**: 1. Retry with exponential backoff (may be transient) 2. Simplify the query (reduce date range, dimensions) 3. Contact support if persistent ```python def execute_with_server_error_retry(query, max_retries=3): """Retry on server errors with backoff.""" for attempt in range(max_retries): try: response = requests.post(url, headers=headers, json=query) if response.status_code == 500: delay = 2 ** attempt # Exponential: 1s, 2s, 4s print(f"Server error. Retrying in {delay}s...") time.sleep(delay) continue return response.json() except requests.exceptions.RequestException as e: if attempt == max_retries - 1: raise time.sleep(2 ** attempt) raise Exception("Server error after retries") ``` ### 503 Service Unavailable **Error**: ```json { "error": "Service temporarily unavailable" } ``` **Cause**: Service is down for maintenance or experiencing issues. **Solution**: 1. Retry with exponential backoff 2. Check Square status page 3. Implement circuit breaker pattern ## Query timeout errors ### Database Timeout **Error**: ```json { "error": "Query execution timeout" } ``` **Causes**: - Query is too complex - Date range is too large - Too many dimensions - No pre-aggregations available **Solutions**: #### 1. Reduce Date Range ```json // Instead of {"dateRange": ["2020-01-01", "2024-12-31"]} // Try {"dateRange": ["2024-01-01", "2024-01-31"]} ``` #### 2. Reduce Dimensions ```json // Instead of {"dimensions": ["Orders.location_id", "Orders.customer_id", "Orders.device_id"]} // Try {"dimensions": ["Orders.location_id"]} ``` #### 3. Increase Granularity ```json // Instead of {"granularity": "day"} // Try {"granularity": "month"} ``` #### 4. Add Filters ```json { "filters": [{ "member": "Orders.location_id", "operator": "equals", "values": ["L1234567890ABC"] }] } ``` ## Empty result errors ### No Data Returned **Response**: ```json { "data": [] } ``` **This is not an error**, but you should handle it: ```python results = execute_query(query) if not results.get('data'): print("No data found for the specified criteria") # Handle empty case else: print(f"Found {len(results['data'])} rows") ``` **Common causes**: - Date range has no transactions - Filters are too restrictive - Segment excludes all data - Wrong cube for your use case ## Schema evolution errors ### Deprecated Measure **Error**: ```json { "error": "Measure 'Orders.legacy_measure' is deprecated. Use 'Orders.new_measure' instead." } ``` **Solution**: 1. Update your query to use the new measure 2. Invalidate your metadata cache 3. Re-fetch metadata to discover new measures ```python def handle_deprecated_measure(query, metadata): """Replace deprecated measures with current ones.""" # Map of deprecated -> current measures replacements = { 'Orders.legacy_tax': 'Orders.sales_tax_amount', 'Orders.old_sales': 'Orders.net_sales' } updated_measures = [] for measure in query.get('measures', []): if measure in replacements: print(f"Replacing deprecated {measure} with {replacements[measure]}") updated_measures.append(replacements[measure]) else: updated_measures.append(measure) query['measures'] = updated_measures return query ``` ## Complete error handling example Here's a production-ready error handling implementation: ```python import time import requests from typing import Dict, Any, Optional class ReportingAPIClient: def __init__(self, access_token: str): self.base_url = "https://connect.squareup.com/reporting" self.headers = { "Authorization": f"Bearer {access_token}", "Content-Type": "application/json" } def execute_query( self, query: Dict[str, Any], max_attempts: int = 10, max_retries: int = 3 ) -> Dict[str, Any]: """ Execute a query with comprehensive error handling. Args: query: The query object max_attempts: Max attempts for Continue wait max_retries: Max retries for server errors Returns: Query results Raises: ValueError: Invalid query TimeoutError: Query timed out Exception: Other errors """ # Validate query first self._validate_query(query) # Execute with retries for retry in range(max_retries): try: return self._execute_with_continue_wait(query, max_attempts) except requests.exceptions.HTTPError as e: status_code = e.response.status_code if status_code == 400: # Validation error - don't retry raise ValueError(f"Invalid query: {e.response.text}") elif status_code == 401 or status_code == 403: # Auth error - don't retry raise Exception(f"Authentication failed: {e.response.text}") elif status_code == 429: # Rate limit - respect Retry-After retry_after = int(e.response.headers.get('Retry-After', 60)) print(f"Rate limited. Waiting {retry_after}s...") time.sleep(retry_after) continue elif status_code >= 500: # Server error - retry with backoff if retry < max_retries - 1: delay = 2 ** retry print(f"Server error. Retrying in {delay}s...") time.sleep(delay) continue raise else: raise except requests.exceptions.RequestException as e: # Network error - retry with backoff if retry < max_retries - 1: delay = 2 ** retry print(f"Network error. Retrying in {delay}s...") time.sleep(delay) continue raise raise Exception(f"Query failed after {max_retries} retries") def _execute_with_continue_wait( self, query: Dict[str, Any], max_attempts: int ) -> Dict[str, Any]: """Execute query with Continue wait retry.""" delays = [2, 2, 5, 5, 10, 10, 15, 15, 20, 20] for attempt in range(max_attempts): response = requests.post( f"{self.base_url}/v1/load", headers=self.headers, json=query ) response.raise_for_status() data = response.json() if data.get("error") == "Continue wait": delay = delays[attempt] if attempt < len(delays) else 20 print(f"Attempt {attempt + 1}/{max_attempts}: Waiting {delay}s...") time.sleep(delay) continue return data raise TimeoutError(f"Query did not complete after {max_attempts} attempts") def _validate_query(self, query: Dict[str, Any]): """Basic query validation.""" if not query.get('measures') and not query.get('dimensions'): raise ValueError("Query must include at least one measure or dimension") if 'timeDimensions' in query: for td in query['timeDimensions']: if 'dimension' not in td or 'dateRange' not in td: raise ValueError("Time dimension must include 'dimension' and 'dateRange'") # Usage client = ReportingAPIClient(access_token=SQUARE_ACCESS_TOKEN) try: results = client.execute_query({ "measures": ["Orders.net_sales"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": ["2024-01-01", "2024-01-31"], "granularity": "day" }], "segments": ["Orders.closed_checks"] }) print(f"Success! Received {len(results['data'])} rows") except ValueError as e: print(f"Invalid query: {e}") except TimeoutError as e: print(f"Query timed out: {e}") except Exception as e: print(f"Query failed: {e}") ``` ## Monitoring and logging ### What to Log ```python import logging logger = logging.getLogger(__name__) def execute_with_logging(query): """Execute query with comprehensive logging.""" logger.info(f"Executing query: {json.dumps(query)}") start_time = time.time() try: results = execute_query(query) duration = time.time() - start_time logger.info(f"Query succeeded in {duration:.2f}s, {len(results['data'])} rows") return results except TimeoutError as e: duration = time.time() - start_time logger.error(f"Query timed out after {duration:.2f}s: {e}") raise except Exception as e: duration = time.time() - start_time logger.error(f"Query failed after {duration:.2f}s: {e}") raise ``` ### Metrics to Track - Query success rate - Average query duration - Continue wait frequency - Timeout frequency - Rate limit hits - Error types and frequency ## Troubleshooting checklist When a query fails: 1. **Check HTTP status code** - 401/403: Authentication issue - 400: Invalid query - 429: Rate limit - 500/503: Server issue 2. **Verify authentication** - Token is valid - Header format is correct - Using personal access token (not OAuth) 3. **Validate query structure** - All measures exist in metadata - All dimensions exist in metadata - Date ranges are valid - Required fields are present 4. **Simplify the query** - Reduce date range - Remove dimensions - Increase granularity - Add filters 5. **Check for Continue wait** - Implement retry logic - Use exponential backoff - Set reasonable max attempts 6. **Review metadata** - Refresh metadata cache - Check for deprecated measures - Verify cube names --- # Best Practices > Source: https://developer.squareup.com/docs/reporting-api/best-practices > Status: BETA > Languages: All > Platforms: All Optimize your Reporting API integration with best practices for performance, caching, and query design. ## Overview This guide covers best practices for building reliable applications with the Reporting API. These recommendations focus on patterns specific to the Reporting API's architecture — schema discovery, query construction, the Continue Wait pattern, and data freshness. ## Schema discovery and validation {% aside type="info" %} **Golden Rule**: Never hard-code measures, dimensions, or segments. Always discover them from `/v1/meta`. {% /aside %} **Bad — hard-coded measures:** ```javascript const MEASURES = ['Orders.net_sales', 'Orders.tips_amount']; ``` **Good — discovered at runtime:** ```javascript const metadata = await fetchMetadata(); const measures = metadata.cubes.Orders.measures.map(m => m.name); ``` Schema can evolve as new measures and dimensions are added. Hard-coded values will break silently when they no longer match the API. ### Cache metadata appropriately **Recommended TTL**: 1–24 hours depending on your use case. - Refresh at application startup - Refresh periodically (every 1–24 hours) - Refresh after "measure not found" or similar schema errors If a requested measure or dimension is missing from metadata, fall back to an alternative rather than failing outright. This makes your integration resilient to schema changes. ### Validate queries before execution Before sending a query to `/v1/load`, check that every measure, dimension, and segment in the request exists in the current metadata. This catches typos and stale references early and produces clearer error messages than a failed API call. ## Query construction ### Always use segments for report parity **Bad — manual filtering:** ```json { "measures": ["Orders.net_sales"], "filters": [{ "member": "Orders.state", "operator": "equals", "values": ["COMPLETED"] }] } ``` **Good — use the segment:** ```json { "measures": ["Orders.net_sales"], "segments": ["Orders.closed_checks"] } ``` Segments encapsulate business logic maintained by Square. Using them ensures your results match Square dashboard reports. ### Specify explicit date ranges Always include a `dateRange` in your `timeDimensions`. Open-ended queries can attempt to return years of data and time out. ```json { "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": ["2024-01-01", "2024-01-31"] }] } ``` ### Use appropriate granularity | Use Case | Granularity | |----------|-------------| | Intra-day monitoring | `hour` | | Daily reports | `day` | | Weekly trends | `week` | | Monthly analysis | `month` | | Quarterly reports | `quarter` | | Year-over-year | `year` | Avoid using `day` granularity for a full year of data — use `month` or `quarter` to keep result sets manageable. ### Limit and order results Always set a `limit` to avoid unexpectedly large result sets, especially when using high-cardinality dimensions like `customer_id`. **Recommended limits**: - UI display: 10–100 - Export/analysis: 1,000–10,000 - Pagination: 100–500 per page Use `order` to sort results meaningfully — chronological for time series, descending by measure for rankings. ### Batch multiple measures **Bad — three separate queries:** ```javascript const netSales = await query({ measures: ['Orders.net_sales'] }); const tips = await query({ measures: ['Orders.tips_amount'] }); const tax = await query({ measures: ['Orders.sales_tax_amount'] }); ``` **Good — single query:** ```javascript const data = await query({ measures: [ 'Orders.net_sales', 'Orders.tips_amount', 'Orders.sales_tax_amount' ] }); ``` One query is faster and uses fewer API calls. ### Minimize dimensions Each additional dimension multiplies the result set size. Only include dimensions you actually need for your analysis. ## Data freshness and caching The `Orders` cube has a data freshness of approximately 15 minutes. - **Historical data** (yesterday and earlier) won't change — cache it aggressively (24 hours or longer) - **Today's data** — cache for 15 minutes to match the cube's refresh cycle - **Metadata** (`/v1/meta`) — cache for 1–24 hours This simple tiered caching strategy significantly reduces API calls without sacrificing data accuracy. ## Error handling ### Implement Continue Wait retry The Reporting API uses a "Continue wait" pattern for queries that take time to compute. Your client **must** handle this: ```javascript async function executeWithRetry(query, maxAttempts = 10) { const delays = [2, 2, 5, 5, 10, 10, 15, 15, 20, 20]; for (let attempt = 0; attempt < maxAttempts; attempt++) { const response = await fetch(url, { method: 'POST', headers, body: JSON.stringify(query) }); const data = await response.json(); if (data.error === 'Continue wait') { const delay = delays[attempt] || 20; await new Promise(r => setTimeout(r, delay * 1000)); continue; } return data; } throw new Error('Query timed out after max retries'); } ``` {% aside type="warning" %} Without Continue Wait handling, complex queries will appear to fail on the first attempt. This is the most common integration issue. {% /aside %} ### Handle schema evolution gracefully When a preferred measure or dimension is unavailable in the current metadata, fall back to an alternative rather than crashing. This is especially important during schema transitions when measures may be temporarily renamed or replaced. ## Security Store your Square access token in environment variables — never hard-code tokens in source code. Use separate tokens for sandbox and production environments, and rotate them periodically. Validate any user-supplied input (date ranges, location IDs) before incorporating it into queries. Enforce reasonable limits on date range spans to prevent abuse. ## Summary checklist - [ ] Metadata is discovered at runtime from `/v1/meta`, not hard-coded - [ ] Metadata cache has appropriate TTL (1–24 hours) - [ ] Queries are validated against current metadata before execution - [ ] Continue Wait retry logic is implemented - [ ] Explicit date ranges are specified in all queries - [ ] `Orders.closed_checks` segment is used for sales reports - [ ] Historical data is cached aggressively (24+ hours) - [ ] Result sets are limited to reasonable sizes - [ ] Access tokens are stored securely in environment variables --- # Reporting API Overview > Source: https://developer.squareup.com/docs/reporting-api/overview > Status: BETA > Languages: All > Platforms: All Query sales, payments, customer, and catalog metrics using the Square Reporting API's dynamic REST interface. The **Reporting API** is a Cube-based REST API that enables developers to query sales, payments, customer, and catalog metrics for embedding analytics, operational automations, and back-office workflows. Unlike traditional REST APIs with dozens of fixed endpoints, the Reporting API exposes **only two universal endpoints**: - **`/v1/meta`** — Describes what data is available (schema discovery) - **`/v1/load`** — Queries the actual data based on discovered metadata This design enables a powerful, flexible API surface that can evolve without breaking client applications that properly implement schema discovery. ### Benefits of Dynamic Schemas - **Expansive data catalog**: Access hundreds of measures and dimensions without endpoint proliferation - **Automatic joins**: Combine measures from multiple cubes in a single query—the API handles joins automatically - **Future-proof clients**: Applications that discover schema at runtime adapt automatically to new fields - **Consistent API**: One query pattern works for all reporting needs ### Trade-offs to Consider - **More complex client logic**: You must implement schema discovery and dynamic query building - **Runtime validation required**: Invalid query combinations are caught at query time, not compile time - **Learning curve**: Developers must understand Cube concepts (cubes, measures, dimensions, segments) ## How it differs from traditional REST APIs | Traditional REST API | Reporting API (Dynamic Schema) | |---------------------|--------------------------------| | Fixed endpoints per resource | Two universal endpoints for all queries | | Fields are predetermined | Fields discovered via metadata | | Schema changes require API versioning | Schema evolves; clients adapt via discovery | | Simple to use, limited flexibility | Complex setup, maximum flexibility | | Client knows structure at build time | Client learns structure at runtime | ## Key concepts ### Views A **view** is a curated, pre-joined interface built on top of one or more cubes. For example, the `Sales` view combines data from multiple cubes and pre-filters for common sales reporting. **Views are the recommended starting point** — they provide a simpler query surface with sensible defaults. Use cubes directly when you need cross-cube joins or access to raw data not exposed through a view. ### Cubes A **cube** is a logical grouping of related measures and dimensions (e.g., `Orders`, `PaymentAndRefunds`). Think of a cube as a "data table" optimized for analytics. You can query cubes directly for advanced use cases, but prefer views for standard reporting. ### Measures **Measures** are numeric aggregations you want to calculate (e.g., `Orders.net_sales`, `Orders.tips_amount`). These are the "what you're measuring" in your query. ### Dimensions **Dimensions** are attributes you use to group or filter data (e.g., `Orders.location_id`, `Orders.sale_timestamp`). These are the "how you're slicing" your measures. ### Segments **Segments** are predefined filters that represent common business logic (e.g., `Orders.closed_checks` filters to fully paid orders). Use segments to ensure report parity with Square's dashboard. ### Time Dimensions **Time dimensions** are special dimensions for time-series queries. They support granularity (day, week, month) and date ranges. ## Core workflow Every interaction with the Reporting API follows this pattern:  **Workflow Steps**: 1. **Discover**: Call `/v1/meta` to learn available cubes, measures, dimensions, and segments 2. **Cache**: Store metadata with appropriate TTL (1-24 hours) 3. **Validate**: Check that your desired measures and dimensions are compatible 4. **Execute**: Send a query to `/v1/load` with your selected entities 5. **Handle Long-Running Queries**: Retry if you receive a "Continue wait" response 6. **Process**: Parse and use the returned data ## Automatic multi-cube joins One of the most powerful features of the Reporting API is the ability to combine data from multiple cubes in a single query. Simply include measures from different cubes in your `measures` array, and the API automatically handles the join. **Example: Combine Orders, Payments, and Items data** ```json { "measures": [ "Orders.net_sales", "Orders.count", "PaymentAndRefunds.total_amount", "ItemTransactions.net_quantity" ], "dimensions": ["Orders.location_id"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": "last 30 days" }], "segments": ["Orders.closed_checks"] } ``` This single query returns sales, payment totals, and items sold—all grouped by location. No need to specify join conditions; the API handles it automatically. See [Multi-Cube Joins](/docs/reporting-api/cubes/joins) for more multi-cube join examples. ## Quick example Here's a minimal example showing the complete workflow: ### Step 1: Discover Available Data ```bash curl -X GET "https://connect.squareup.com/reporting/v1/meta" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" ``` This returns metadata describing all available cubes, measures, and dimensions. ### Step 2: Query Daily Sales ```bash curl -X POST "https://connect.squareup.com/reporting/v1/load" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "query": { "measures": ["Orders.net_sales", "Orders.net_sales_with_tax"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": ["2024-01-01", "2024-01-31"], "granularity": "day" }], "segments": ["Orders.closed_checks"] } }' ``` This returns daily net sales for January 2024, filtered to closed (completed) orders. ## When to use the Reporting API The Reporting API is ideal for: - **Custom dashboards and analytics**: Build seller-facing or internal reporting tools - **Operational automations**: Trigger workflows based on sales thresholds or patterns - **Back-office integrations**: Sync sales data to accounting, inventory, or ERP systems - **Ad-hoc analysis**: Query arbitrary combinations of metrics and dimensions **Not recommended for**: - Real-time transaction processing (use the Orders or Payments APIs instead) - Operational CRUD operations (use domain-specific APIs) - High-frequency polling (data freshness is ~15 minutes for most cubes) ## Authentication The Reporting API supports both personal access tokens and OAuth tokens. **For OAuth applications**, request the `REPORTING_READ` permission scope when obtaining authorization from sellers. **For testing and development**, you can use personal access tokens from the Developer Console. Include your access token in the `Authorization` header on every request: ``` Authorization: Bearer YOUR_ACCESS_TOKEN ``` See [Getting Started](/docs/reporting-api/getting-started) for detailed authentication setup and [OAuth API documentation](/docs/oauth-api/overview) for implementing OAuth flows. ## Data freshness - **Standard cubes** (e.g., `Orders`): ~15 minute refresh interval ## Base URL All Reporting API endpoints are under: ``` https://connect.squareup.com/reporting ``` Core endpoints: - `GET /v1/meta` — Schema discovery - `GET/POST /v1/load` — Execute queries ## Additional Resources * [Schema Explorer](https://reporting-schema-explorer-production-f.squarecdn.com/schema-explorer.html) — Browse all cubes, measures, and dimensions interactively * [Meta tag reference](https://reporting-schema-explorer-production-f.squarecdn.com/meta-tags.html) — Browse all fields returned from the metadata response * [Cube Query Format](https://cube.dev/docs/product/apis-integrations/core-data-apis/rest-api/query-format#query-properties) — Complete query property reference * [Error Handling](reporting-api/error-handling) — Handle errors and edge cases * [Best Practices](reporting-api/best-practices) — Production-ready patterns --- # Query Construction Overview > Source: https://developer.squareup.com/docs/reporting-api/query-construction > Status: BETA > Languages: All > Platforms: All Learn how to build and execute queries against the Square Reporting API using the /v1/load endpoint. ## Overview The `/v1/load` endpoint executes queries against your discovered schema. Unlike traditional REST APIs where you call different endpoints for different resources, you construct a JSON query object that specifies: - **What to measure** (measures) - **How to slice it** (dimensions) - **What to filter** (segments and filters) - **When to analyze** (time dimensions) - **How to format results** (order, limit, offset) {% aside type="info" %} * For most reporting, query **views** like `Sales` and `ItemSales` rather than raw cubes. Views pre-join data from multiple cubes and pre-filter for common use cases. See [Views vs Cubes](/docs/reporting-api/metadata-discovery#views-vs-cubes) for details. * **Complete Query Reference**: For detailed information about all query properties and advanced options, see the [Cube Query Format documentation](https://cube.dev/docs/product/apis-integrations/core-data-apis/rest-api/query-format#query-properties). {% /aside %} ## Basic query structure Every query follows this structure: ```json { "measures": ["Cube.measure1", "Cube.measure2"], "dimensions": ["Cube.dimension1"], "timeDimensions": [{ "dimension": "Cube.time_dimension", "dateRange": ["start-date", "end-date"], "granularity": "day" }], "segments": ["Cube.segment1"], "filters": [{ "member": "Cube.dimension", "operator": "equals", "values": ["value"] }], "order": { "Cube.measure1": "desc" }, "limit": 1000, "offset": 0 } ``` {% aside type="info" %} **All fields are optional** except that you must include at least one measure or dimension. However, most useful queries include measures, time dimensions, and segments. {% /aside %} ## Query components Each component serves a specific purpose in your query: | Component | Purpose | Learn More | |-----------|---------|------------| | **Measures** | Define what to calculate (sums, counts, averages) | [Measures Guide](/docs/reporting-api/query-construction/measures) | | **Dimensions** | Define how to group results (location, channel, customer) | [Dimensions Guide](/docs/reporting-api/query-construction/dimensions) | | **Time Dimensions** | Enable time-series analysis with date grouping | [Time Dimensions Guide](/docs/reporting-api/query-construction/time-dimensions) | | **Segments** | Apply predefined business logic filters | [Segments Guide](/docs/reporting-api/query-construction/segments) | | **Filters** | Add custom filtering conditions | [Filters Guide](/docs/reporting-api/query-construction/filters) | | **Order, Limit, Offset** | Control result sorting and pagination | [Ordering & Pagination](/docs/reporting-api/query-construction/ordering-pagination) | ## Quick start examples These examples use the `Sales` view, which is the recommended starting point for most reporting queries. The `Sales` view pre-filters to closed orders, so no `closed_checks` segment is needed. ### Simple Query: Total Sales Today ```json { "measures": ["Sales.net_sales"], "timeDimensions": [{ "dimension": "Sales.local_reporting_timestamp", "dateRange": "today" }] } ``` Returns a single total of all closed sales for today—the simplest query for checking current day revenue. ### Grouped Query: Sales by Location ```json { "measures": ["Sales.net_sales", "Sales.order_count"], "dimensions": ["Sales.location_name"], "timeDimensions": [{ "dimension": "Sales.local_reporting_timestamp", "dateRange": "last 30 days" }] } ``` Returns total sales and order count for each location over the last 30 days—one row per location showing performance comparison. The `Sales` view gives you `location_name` directly. ### Time-Series Query: Daily Sales Trend ```json { "measures": ["Sales.net_sales"], "timeDimensions": [{ "dimension": "Sales.local_reporting_timestamp", "dateRange": ["2024-01-01", "2024-01-31"], "granularity": "day" }], "order": { "Sales.local_reporting_timestamp": "asc" } } ``` Returns daily sales for each day in January—31 rows showing the sales trend over time in chronological order. ### Filtered Query: Online Sales Only ```json { "measures": ["Sales.net_sales", "Sales.tips_amount"], "dimensions": ["Sales.channel_name"], "timeDimensions": [{ "dimension": "Sales.local_reporting_timestamp", "dateRange": "this month", "granularity": "day" }], "segments": ["Sales.online"] } ``` Returns daily online sales and tips by channel this month—uses the `Sales.online` segment to filter to online orders only. ## Executing queries ### Using POST (Recommended) ```bash curl -X POST "https://connect.squareup.com/reporting/v1/load" \ -H "Authorization: Bearer ${SQUARE_ACCESS_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "query": { "measures": ["Sales.net_sales"], "timeDimensions": [{ "dimension": "Sales.local_reporting_timestamp", "dateRange": "today" }] } }' ``` {% aside type="warning" %} **POST Body Format**: The query must be wrapped in a `"query"` field. Without this wrapper, you'll receive an error: `{"error": "Query param is required"}`. {% /aside %} ### Using GET For simple queries, you can use GET with URL-encoded JSON: ```bash QUERY='{"measures":["Sales.net_sales"],"timeDimensions":[{"dimension":"Sales.local_reporting_timestamp","dateRange":"today"}]}' ENCODED=$(echo $QUERY | jq -sRr @uri) curl -X GET "https://connect.squareup.com/reporting/v1/load?query=${ENCODED}" \ -H "Authorization: Bearer ${SQUARE_ACCESS_TOKEN}" ``` ### Response Format **Success response with data**: ```json { "data": [ { "Sales.local_reporting_timestamp.day": "2024-02-05T00:00:00.000", "Sales.net_sales": "1250.50" } ] } ``` **Success response with no data**: ```json { "data": [] } ``` {% aside type="info" %} **Empty Results Are Not Errors**: An empty `data` array means the query executed successfully but found no matching records. This is normal when: - The date range has no sales activity - Filters are too restrictive - The location/channel combination doesn't exist - Segments exclude all records **To troubleshoot empty results:** - Expand the date range (try `"last 30 days"` instead of `"today"`) - Remove or relax filters - Verify the location/customer/device IDs exist - Check if segments are too restrictive (try without segments first) - Use the metadata endpoint to confirm the entities exist {% /aside %} **Long-running query**: ```json { "error": "Continue wait" } ``` Retry the same request until you get results. ## Query building best practices ### 1. Use Views for Sales Reports ```json { "measures": ["Sales.net_sales"] } ``` The `Sales` view pre-filters to closed orders — no segment needed. If querying the `Orders` cube directly, always add `"segments": ["Orders.closed_checks"]`. ### 2. Specify Explicit Date Ranges ```json { "timeDimensions": [{ "dateRange": ["2024-01-01", "2024-01-31"] }] } ``` Avoid open-ended ranges that might return too much data. ### 3. Set Reasonable Limits ```json { "limit": 1000 } ``` Especially important when using multiple dimensions. ### 4. Validate Before Executing Check that all measures, dimensions, and segments exist in your metadata before querying. ## Common mistakes ### ❌ Querying Orders Cube Without Segment ```json { "measures": ["Orders.net_sales"] // Missing: "segments": ["Orders.closed_checks"] } ``` This includes open/unpaid orders, which doesn't match dashboard reports. Use the `Sales` view instead, which pre-filters to closed orders. ### ❌ No Date Range ```json { "timeDimensions": [{ "dimension": "Sales.local_reporting_timestamp" // Missing: "dateRange" }] } ``` This might return all historical data. ### ❌ Too Many Dimensions Without Limit ```json { "dimensions": ["Sales.location_name", "Sales.channel_name", "Sales.employee_full_name"] // Missing: "limit": 1000 } ``` This could return millions of rows. --- # Working with Cubes Overview > Source: https://developer.squareup.com/docs/reporting-api/cubes > Status: BETA > Languages: All > Platforms: All Understand cubes, the building blocks of the Reporting API data model for structured business analytics. ## Overview The Reporting API data model is organized into **cubes**—logical groupings of related metrics and dimensions. Each cube contains: - **Measures** - What to calculate (e.g., `net_sales`, `count`, `tips_amount`) - **Dimensions** - How to slice and group (e.g., `location_id`, `sale_timestamp`, `customer_id`) - **Segments** - Predefined filters for common scenarios (e.g., `closed_checks`, `online_orders`) {% aside type="info" %} **New to the Reporting API?** Start with **views** like `Sales` and `ItemSales` — they're curated interfaces that pre-join data from multiple cubes and pre-filter for common use cases. This section covers the underlying cube architecture for developers who need advanced access. See [Views vs Cubes](/docs/reporting-api/metadata-discovery#views-vs-cubes) for guidance on when to use each. {% /aside %} {% aside type="info" %} **Interactive Schema Explorer**: Browse the complete, up-to-date schema with examples at the [Reporting API Schema Explorer](https://reporting-schema-explorer-production-f.squarecdn.com/schema-explorer.html). The schema explorer provides: - Complete list of all cubes with descriptions - All measures, dimensions, and segments for each cube - Data freshness information - Example queries for each cube - Interactive search and filtering {% /aside %} ## What is a cube? Think of a cube as a **pre-built data mart** optimized for specific analytical questions: - **Orders cube** - Answers questions about sales, revenue, and order patterns - **PaymentAndRefunds cube** - Answers questions about payment methods, tender types, and refunds - **CustomerSnapshots cube** - Answers questions about customer behavior and lifetime value - **ItemTransactions cube** - Answers questions about product performance and inventory Each cube is designed around a specific business entity and includes all the measures and dimensions relevant to analyzing that entity. ## Cube components explained ### Measures (What to Calculate) Measures are pre-aggregated metrics with defined calculation logic: ```json { "name": "Orders.net_sales", "aggType": "sum", "description": "Total sales after discounts and returns, excluding tax" } ``` You select measures by name; the API applies the correct aggregation automatically. ### Dimensions (How to Slice) Dimensions are attributes you can group by or filter on: ```json { "name": "Orders.location_id", "type": "string", "description": "Location where the order was placed" } ``` Use dimensions to break down your measures (e.g., sales by location, by channel, by customer). ### Segments (Predefined Filters) Segments encapsulate common business logic: ```json { "name": "Orders.closed_checks", "description": "Only fully paid and settled orders" } ``` Use segments to ensure consistent filtering across all queries. ## Available cubes The Reporting API currently provides 43 cubes and 8 views for different analytical needs. For complete details, interactive exploration, and the most up-to-date list, visit the [Schema Explorer](https://reporting-schema-explorer-production-f.squarecdn.com/schema-explorer.html). ### Core Cubes (Most Commonly Used) | Cube | Purpose | Key Use Cases | |------|---------|---------------| | **Orders** | Sales analytics | Revenue reporting, order trends, location performance | | **PaymentAndRefunds** | Payment analysis | Payment methods, tender types, refund tracking | | **CustomerSnapshots** | Customer analytics | Customer lifetime value, purchase frequency | | **ItemTransactions** | Product performance | Item-level sales, inventory analysis | ## Quick example Here's a simple query using the Orders cube: ```json { "measures": ["Orders.net_sales", "Orders.count"], "dimensions": ["Orders.location_id"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": "last 30 days" }], "segments": ["Orders.closed_checks"] } ``` This returns total sales and order count for each location over the last 30 days. ## Key Concepts ### 1. Schema Discovery The schema is dynamic and evolves over time. Always use the `/v1/meta` endpoint or Schema Explorer to discover: - Available cubes - Measures and their aggregation types - Dimensions and their data types - Available segments See [Schema Discovery](/docs/reporting-api/cubes/schema-discovery) for details. ### 2. Cube Relationships Cubes can be combined in queries through automatic joins. The API handles join logic based on common dimensions like `order_id`, `location_id`, or `customer_id`. See [Multi-Cube Joins](/docs/reporting-api/cubes/joins) for details. ### 3. Time Handling Cubes provide both UTC and local time dimensions for different reporting needs: - **UTC** - Consistent global time - **Local** - Business day alignment See [Time Handling](/docs/reporting-api/cubes/time) for details. --- # Schema Discovery > Source: https://developer.squareup.com/docs/reporting-api/cubes/schema-discovery > Status: BETA > Languages: All > Platforms: All Programmatically discover cube schemas, available fields, and relationships in the Reporting API. ## Overview The definitive source for the current schema is the `/v1/meta` endpoint. Always use schema discovery to: - **Get the latest schema** - New measures and dimensions are added over time - **Validate your queries** - Ensure measures and dimensions exist before querying - **Discover data freshness** - Check refresh intervals for each cube - **Find available segments** - Use predefined filters for common scenarios For a complete guide to metadata structure, parsing, and caching strategies, see [Metadata Discovery](/docs/reporting-api/metadata-discovery). ## Quick schema discovery ### Get All Available Cubes and Views ```bash curl -X GET "https://connect.squareup.com/reporting/v1/meta" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" | jq '.cubes[] | {name, type}' ``` ### Get Details for a Specific Cube ```bash curl -X GET "https://connect.squareup.com/reporting/v1/meta" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" | \ jq '.cubes[] | select(.name == "Orders")' ``` ### List All Measures for a Cube ```bash curl -X GET "https://connect.squareup.com/reporting/v1/meta" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" | \ jq '.cubes[] | select(.name == "Orders") | .measures[] | {name, title, aggType, description}' ``` ### List All Segments for a Cube ```bash curl -X GET "https://connect.squareup.com/reporting/v1/meta" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" | \ jq '.cubes[] | select(.name == "Orders") | .segments[] | {name, title, description}' ``` ## Interactive schema explorer For interactive browsing and exploration, use the [Schema Explorer](https://reporting-schema-explorer-production-f.squarecdn.com/schema-explorer.html). --- # Core Cubes > Source: https://developer.squareup.com/docs/reporting-api/cubes/core-cubes > Status: BETA > Languages: All > Platforms: All Understand the core data cubes available in the Reporting API and how they model your business data. {% aside type="warning" %} **Start with views, not cubes.** For most reporting use cases, we recommend using **views** like `Sales`, `ItemSales`, and `ModifierSales` instead of querying cubes directly. Views pre-join data from multiple cubes, pre-filter to closed orders, and provide human-readable dimensions. See [Metadata Discovery](/docs/reporting-api/metadata-discovery#views-vs-cubes) for details. Use cubes directly only when you need raw, unfiltered data or fields not available in any view. {% /aside %} ## Overview The two most commonly used cubes are **Orders** and **PaymentAndRefunds**. Understanding these cubes is essential for most reporting scenarios. ## Orders cube The **Orders** cube is the primary source for sales analytics, containing measures and dimensions for completed transactions. ### Key Characteristics - **Data Freshness**: ~15 minutes - **Use For**: Historical analysis (yesterday and earlier), daily/weekly/monthly reports - **Critical Segment**: `closed_checks` (always use for sales reports) ### Key Measures | Measure | Type | Description | |---------|------|-------------| | `net_sales` | sum | Primary revenue metric (after discounts/returns, before tax) | | `net_sales_with_tax` | sum | Revenue including sales tax | | `tips_amount` | sum | Tips received (non-cash) | | `sales_tax_amount` | sum | Sales tax collected | | `count` | count | Number of orders | | `avg_net_sales` | avg | Average net sales per order | | `discounts_amount` | sum | Total discounts applied | | `itemized_returns` | sum | Returns and refunds | ### Key Dimensions | Dimension | Type | Description | |-----------|------|-------------| | `location_id` | string | Location where order was placed | | `sale_timestamp` | time | UTC sale time | | `local_date` | string | Local business date (YYYY-MM-dd format) | | `sales_channel_id` | string | Sales channel (online, in-person) | | `customer_id` | string | Customer identifier | | `device_id` | string | Device that processed the order | | `order_id` | string | Unique order identifier | ### Key Segments | Segment | Description | |---------|-------------| | `closed_checks` | Fully paid orders (recommended for sales reports) | | `open_checks` | Orders awaiting payment | | `awaiting_capture` | Orders awaiting capture | | `fully_paid` | Fully paid orders | | `has_tip` | Orders with tips | | `no_tip` | Orders without tips | | `has_customer` | Orders with a linked customer | ### Example Query ```json { "measures": [ "Orders.net_sales", "Orders.count", "Orders.avg_net_sales" ], "dimensions": ["Orders.location_id"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": "last 30 days" }], "segments": ["Orders.closed_checks"], "order": { "Orders.net_sales": "desc" } } ``` Returns sales performance by location for the last 30 days. ## PaymentAndRefunds cube The **PaymentAndRefunds** cube provides detailed payment transaction data, including payment methods and refund information. ### Key Characteristics - **Data Freshness**: ~15 minutes - **Use For**: Payment method analysis, refund tracking - **Relationship to Orders**: One order can have multiple payments (split payments, partial payments) ### Key Measures | Measure | Type | Description | |---------|------|-------------| | `total_amount` | sum | Total payment amount including tips and tax | | `tip_amount` | sum | Tips on this payment | | `count` | count | Number of payment transactions | | `refund_total_amount` | sum | Total refunds | ### Key Dimensions | Dimension | Type | Description | |-----------|------|-------------| | `payment_id` | string | Unique payment identifier | | `order_id` | string | Associated order identifier | | `type` | string | Payment type (CARD, CASH, etc.) | | `reporting_timestamp` | time | UTC payment time | | `location_id` | string | Location where payment was made | | `payment_method` | string | Payment method (CARD, CASH, etc.) | ### Example Query ```json { "measures": [ "PaymentAndRefunds.total_amount", "PaymentAndRefunds.count" ], "dimensions": ["PaymentAndRefunds.type"], "timeDimensions": [{ "dimension": "PaymentAndRefunds.reporting_timestamp", "dateRange": "last 30 days" }], "order": { "PaymentAndRefunds.total_amount": "desc" } } ``` Returns payment totals by payment type (CARD, CASH, etc.) for the last 30 days. ## Comparing the core cubes | Feature | Orders | PaymentAndRefunds | |---------|--------|-------------------| | **Freshness** | ~15 min | ~15 min | | **Best For** | Sales analysis | Payment method analysis | | **Granularity** | Order level | Payment level | | **Key Metric** | Net sales | Payment amounts | | **Relationship** | One order | Multiple payments per order | ## When to use each cube ### Use Orders When: - Building daily/weekly/monthly reports - Analyzing sales trends and revenue - You need order-level metrics ### Use PaymentAndRefunds When: - Analyzing payment methods - Monitoring refunds - Understanding split payment patterns - Comparing payment amounts to order amounts ## Example queries for each cube ### Orders: Monthly Sales by Location ```json { "measures": ["Orders.net_sales", "Orders.count"], "dimensions": ["Orders.location_id"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": ["2024-01-01", "2024-12-31"], "granularity": "month" }], "segments": ["Orders.closed_checks"] } ``` ### PaymentAndRefunds: Payment Methods This Month ```json { "measures": [ "PaymentAndRefunds.total_amount", "PaymentAndRefunds.count" ], "dimensions": ["PaymentAndRefunds.type"], "timeDimensions": [{ "dimension": "PaymentAndRefunds.reporting_timestamp", "dateRange": "this month" }] } ``` --- # Time Handling > Source: https://developer.squareup.com/docs/reporting-api/cubes/time > Status: BETA > Languages: All > Platforms: All Configure time zones, date ranges, and temporal granularity when working with cube-based queries. ## Overview The Reporting API provides both UTC and local time dimensions to support different reporting needs. Understanding when to use each is crucial for accurate time-based analysis. ## UTC time dimensions UTC time dimensions store timestamps in Coordinated Universal Time (UTC), providing a consistent global reference. ### Available UTC Dimensions - `Orders.sale_timestamp` - UTC time when order was placed - `PaymentAndRefunds.reporting_timestamp` - UTC time when payment was processed - `Orders.created_at` - UTC time when order was created ### When to Use UTC Use UTC time dimensions when you need: - **Consistent global time** across all locations - **Cross-timezone aggregation** (e.g., total sales across all locations globally) - **Precise timestamp ordering** without timezone ambiguity - **API integration** where systems expect UTC ### Example: UTC Query ```json { "measures": ["Orders.net_sales"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": ["2024-01-01T00:00:00Z", "2024-01-01T23:59:59Z"], "granularity": "hour" }], "segments": ["Orders.closed_checks"] } ``` Returns hourly sales for January 1st in UTC time—may span multiple business days for locations in different timezones. ## Local time dimensions Local time dimensions adjust timestamps to the location's timezone, providing business-day alignment. ### Available Local Dimensions - `Orders.local_date` - Business date in location's timezone - `Orders.local_reporting_timestamp` - Reporting timestamp in location's timezone ### When to Use Local Time Use local time dimensions when you need: - **Business day alignment** (e.g., "What were sales on January 1st at this location?") - **Location-specific reporting** that matches what staff see in the dashboard - **Day-of-week analysis** (e.g., "Which day of the week is busiest?") - **Service period tracking** (e.g., lunch vs dinner at local time) ### Example: Local Date Query ```json { "measures": ["Orders.net_sales"], "dimensions": ["Orders.local_date"], "filters": [{ "member": "Orders.local_date", "operator": "inDateRange", "values": ["2024-01-01", "2024-01-31"] }], "segments": ["Orders.closed_checks"] } ``` Returns sales for each business day in January, aligned to each location's local timezone. ## Comparing UTC vs local time ### Example Scenario: Multi-Location Restaurant Chain You have locations in New York (EST), Los Angeles (PST), and London (GMT). **UTC Query**: ```json { "measures": ["Orders.net_sales"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": ["2024-01-01T00:00:00Z", "2024-01-01T23:59:59Z"], "granularity": "hour" }] } ``` **Result**: Sales from 12am-11:59pm UTC, which includes: - New York: 7pm Jan 1 - 6:59pm Jan 2 - Los Angeles: 4pm Jan 1 - 3:59pm Jan 2 - London: 12am Jan 1 - 11:59pm Jan 1 **Local Date Query**: ```json { "measures": ["Orders.net_sales"], "dimensions": ["Orders.location_id", "Orders.local_date"], "filters": [{ "member": "Orders.local_date", "operator": "equals", "values": ["2024-01-01"] }] } ``` **Result**: Sales for January 1st business day at each location: - New York: 12am-11:59pm EST on Jan 1 - Los Angeles: 12am-11:59pm PST on Jan 1 - London: 12am-11:59pm GMT on Jan 1 ## Decision guide: which time dimension to use? | Reporting Need | Use | Reason | |----------------|-----|--------| | Global totals | UTC | Consistent across all timezones | | Business day reports | Local | Matches location's business day | | Cross-timezone trends | UTC | Consistent time reference | | Location-specific reports | Local | Matches staff experience | | API integrations | UTC | Standard for APIs | | Day-of-week analysis | Local | Business day semantics | | Hourly patterns | Local | Service period alignment | ## Common patterns ### Pattern 1: Global Daily Totals (UTC) ```json { "measures": ["Orders.net_sales", "Orders.count"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": ["2024-01-01", "2024-01-31"], "granularity": "day" }], "segments": ["Orders.closed_checks"] } ``` Returns daily totals in UTC—consistent global view. ### Pattern 2: Location Business Day Totals (Local) ```json { "measures": ["Orders.net_sales", "Orders.count"], "dimensions": ["Orders.location_id", "Orders.local_date"], "filters": [{ "member": "Orders.local_date", "operator": "inDateRange", "values": ["2024-01-01", "2024-01-31"] }], "segments": ["Orders.closed_checks"] } ``` Returns sales by location and business day—matches location dashboards. ### Pattern 3: Hourly Patterns by Location (Local) ```json { "measures": ["Orders.net_sales"], "dimensions": ["Orders.location_id"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": "last 7 days", "granularity": "hour" }], "segments": ["Orders.closed_checks"] } ``` Returns hourly sales patterns—use local time dimensions if you want to analyze service periods (lunch, dinner) at local time. ## Best practices ### 1. Use Local Time for Business Day Reports ```json { "dimensions": ["Orders.local_date"], "filters": [{ "member": "Orders.local_date", "operator": "inDateRange", "values": ["2024-01-01", "2024-01-31"] }] } ``` This ensures January 1st means "January 1st business day" at each location. ### 2. Use UTC for Cross-Timezone Aggregation ```json { "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": ["2024-01-01", "2024-01-31"], "granularity": "day" }] } ``` This provides consistent global time buckets. ### 3. Be Consistent Within a Query Don't mix UTC and local time dimensions in the same query—choose one approach and stick with it. ### 4. Document Your Choice When building reports, document whether you're using UTC or local time so future developers understand the semantics. --- # Multi-Cube Joins > Source: https://developer.squareup.com/docs/reporting-api/cubes/joins > Status: BETA > Languages: All > Platforms: All Learn how to join data across multiple cubes to create comprehensive cross-domain analytical reports. ## Overview The Reporting API supports querying multiple cubes in a single request by including measures from different cubes in the `measures` array. The API automatically handles the join server-side. ## How multi-cube queries work When you include measures from multiple cubes (e.g., `Orders.net_sales` and `PaymentAndRefunds.total_amount`), the API: 1. **Identifies the cubes** involved in your query 2. **Determines the join relationships** based on common dimensions between cubes 3. **Executes the join** server-side and returns combined results 4. **Aggregates the measures** according to your specified dimensions and time granularity {% aside type="info" %} **Key benefit**: You don't need to specify join conditions—the API handles this automatically based on the cube schema relationships. {% /aside %} ## Understanding cube relationships Before querying multiple cubes, it's important to understand how they relate: ### Orders ↔ PaymentAndRefunds Relationship - **One order can have multiple payments** (split payments, partial payments, etc.) - Each payment has an `order_id` linking it to an order - Each order has `tenders[].payment_id` references to its payments When you join Orders and PaymentAndRefunds on `order_id`, payment measures are **aggregated** (summed) for all payments belonging to that order. ### Common Join Dimensions | Dimension | Links | Use For | |-----------|-------|---------| | `order_id` | Orders ↔ Payments | Order-level analysis | | `location_id` | All cubes ↔ Locations | Location performance | | `merchant_id` | All cubes ↔ Merchant | Merchant-level aggregation | | `customer_id` | Orders ↔ Customers | Customer analysis | {% aside type="warning" %} **Join Validation**: Not all cubes can be joined together. If you try to query measures from cubes that don't have a join path, the API will return an error: ```json {"error": "Can't find join path to join 'Orders', 'CatalogModifierList'"} ``` The API automatically determines if cubes can be joined based on their schema relationships. Always test your multi-cube queries to ensure the cubes have a valid join path. {% /aside %} ## Two types of multi-cube joins The behavior depends on whether you include a common dimension that links the cubes: ### 1. Aggregated Joins (No Common Dimension) When you query multiple cubes without specifying a linking dimension, the API aggregates both cubes at the same level: ```json { "measures": ["Orders.count", "PaymentAndRefunds.count"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": ["2024-01-01", "2024-01-31"], "granularity": "day" }] } ``` **Response**: Each row shows aggregated totals for both cubes at the same time granularity: ```json { "data": [ { "Orders.sale_timestamp.day": "2024-01-01T00:00:00.000", "Orders.count": 42, "PaymentAndRefunds.count": 45 } ] } ``` **Use case**: Compare overall metrics (e.g., "Did we have more payments than orders today?") ### 2. Row-Level Joins (With Common Dimension) When you include a dimension that exists in both cubes (like `order_id` or `location_id`), the API joins row-by-row: ```json { "measures": ["Orders.net_sales", "PaymentAndRefunds.total_amount"], "dimensions": ["Orders.order_id"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": ["2024-01-01", "2024-01-31"] }], "limit": 10 } ``` **Response**: Each row shows an order with its associated payment data: ```json { "data": [ { "Orders.order_id": "ORDER123", "Orders.net_sales": "125.50", "PaymentAndRefunds.total_amount": "135.50" }, { "Orders.order_id": "ORDER456", "Orders.net_sales": "89.25", "PaymentAndRefunds.total_amount": "96.30" } ] } ``` **Use case**: Analyze individual orders and their payment details (e.g., "Which orders have payment amounts that differ from net sales?") ## Example 1: Aggregated join - daily orders vs payments **Type**: Aggregated join (no common dimension) Compare order counts with payment counts by day to identify discrepancies: ```json { "measures": [ "Orders.count", "Orders.net_sales", "PaymentAndRefunds.count", "PaymentAndRefunds.total_amount" ], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": ["2024-01-01", "2024-01-31"], "granularity": "day" }], "segments": ["Orders.closed_checks"] } ``` **Response**: ```json { "data": [ { "Orders.sale_timestamp.day": "2024-01-01T00:00:00.000", "Orders.count": 42, "Orders.net_sales": "4250.50", "PaymentAndRefunds.count": 45, "PaymentAndRefunds.total_amount": "4590.04" }, { "Orders.sale_timestamp.day": "2024-01-02T00:00:00.000", "Orders.count": 38, "Orders.net_sales": "3980.25", "PaymentAndRefunds.count": 40, "PaymentAndRefunds.total_amount": "4298.67" } ] } ``` **Why this is useful**: Payment count may be higher than order count when orders have multiple payments (e.g., split payments, partial payments). This aggregated view helps identify such patterns over time. ## Example 2: Row-level join - orders by location **Type**: Row-level join (common dimension: `location_id`) Build a comprehensive location performance report combining sales, items, and customer data: ```json { "measures": [ "Orders.net_sales", "Orders.count", "ItemTransactions.net_quantity", "CustomerSnapshots.count" ], "dimensions": ["Orders.location_id"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": "last 30 days" }], "segments": ["Orders.closed_checks"] } ``` **Response**: ```json { "data": [ { "Orders.location_id": "L1234567890ABC", "Orders.net_sales": "125000.50", "Orders.count": 1250, "ItemTransactions.net_quantity": 3500, "CustomerSnapshots.count": 450 }, { "Orders.location_id": "L9876543210XYZ", "Orders.net_sales": "98000.25", "Orders.count": 980, "ItemTransactions.net_quantity": 2800, "CustomerSnapshots.count": 380 } ] } ``` **Why this is useful**: The cubes are joined on `location_id`, so each row shows all metrics for a specific location. This enables location-level KPI calculations: - **Average items per order**: `ItemTransactions.net_quantity / Orders.count` (e.g., 3500 / 1250 = 2.8 items per order) - **Average orders per customer**: `Orders.count / CustomerSnapshots.count` (e.g., 1250 / 450 = 2.78 orders per customer) ## Example 3: Row-level join - orders with aggregated payments **Type**: Row-level join (common dimension: `order_id`) Analyze individual orders with their total payment amounts: ```json { "measures": ["Orders.net_sales", "PaymentAndRefunds.total_amount"], "dimensions": ["Orders.order_id"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": "last 7 days" }], "segments": ["Orders.closed_checks"], "limit": 10 } ``` **Response**: ```json { "data": [ { "Orders.order_id": "ORDER123", "Orders.net_sales": "125.50", "PaymentAndRefunds.total_amount": "135.50" }, { "Orders.order_id": "ORDER456", "Orders.net_sales": "89.25", "PaymentAndRefunds.total_amount": "96.30" } ] } ``` {% aside type="info" %} **Important**: Since one order can have multiple payments, `PaymentAndRefunds.total_amount` is the **sum of all payments** for that order. If an order has 2 payments of \$50 each, the total_amount will be \$100. {% /aside %} **Why this is useful**: Each row represents a specific order with its aggregated payment data. This helps identify: - Orders where payment amount differs from net sales (due to tax, tips, rounding) - Orders with missing payment records (payment amount = 0) - Total payment amounts for orders with split/partial payments ## Example 4: Viewing individual payments **Type**: Row-level join (common dimension: `payment_id`) To see individual payments (not aggregated by order), use `payment_id` as the dimension: ```json { "measures": [ "PaymentAndRefunds.total_amount", "PaymentAndRefunds.tip_amount" ], "dimensions": [ "PaymentAndRefunds.payment_id", "PaymentAndRefunds.order_id", "PaymentAndRefunds.type" ], "timeDimensions": [{ "dimension": "PaymentAndRefunds.reporting_timestamp", "dateRange": "last 7 days" }], "limit": 10 } ``` **Response**: ```json { "data": [ { "PaymentAndRefunds.payment_id": "PAYMENT001", "PaymentAndRefunds.order_id": "ORDER123", "PaymentAndRefunds.type": "CARD", "PaymentAndRefunds.total_amount": "75.50", "PaymentAndRefunds.tip_amount": "10.00" }, { "PaymentAndRefunds.payment_id": "PAYMENT002", "PaymentAndRefunds.order_id": "ORDER123", "PaymentAndRefunds.type": "CASH", "PaymentAndRefunds.total_amount": "60.00", "PaymentAndRefunds.tip_amount": "0.00" } ] } ``` **Why this is useful**: This shows individual payments, including multiple payments for the same order (note both payments have `order_id: "ORDER123"`). Use this when you need to: - Analyze payment method mix at the payment level - Identify split payment patterns - Track individual payment amounts and tips ## Client-side joins (alternative approach) For more complex analysis, you can also query cubes separately and join in your application: ### Step 1: Query Orders ```json { "measures": ["Orders.net_sales"], "dimensions": ["Orders.order_id"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": "last 7 days" }] } ``` ### Step 2: Query Payments ```json { "measures": ["PaymentAndRefunds.total_amount"], "dimensions": ["PaymentAndRefunds.order_id", "PaymentAndRefunds.type"], "timeDimensions": [{ "dimension": "PaymentAndRefunds.reporting_timestamp", "dateRange": "last 7 days" }] } ``` ### Step 3: Join in Your Application Join the results using `order_id` as the key. {% aside type="info" %} **When to use client-side joins**: Use this approach when you need fine-grained control over the join logic or when querying at different granularities. {% /aside %} ## Common join patterns ### Orders + Payments by Location ```json { "measures": ["Orders.net_sales", "PaymentAndRefunds.total_amount"], "dimensions": ["Orders.location_id"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": "last 30 days" }], "segments": ["Orders.closed_checks"] } ``` ### Orders + Items by Location ```json { "measures": ["Orders.net_sales", "ItemTransactions.net_quantity"], "dimensions": ["Orders.location_id"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": "last 30 days" }], "segments": ["Orders.closed_checks"] } ``` ### Orders + Customers by Location ```json { "measures": ["Orders.count", "CustomerSnapshots.count"], "dimensions": ["Orders.location_id"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": "last 30 days" }], "segments": ["Orders.closed_checks"] } ``` ## Troubleshooting join errors ### Error: Can't Find Join Path ```json {"error": "Can't find join path to join 'Orders', 'CatalogModifierList'"} ``` **What this means**: The cubes don't have a direct or indirect join relationship in the schema. **Solutions**: 1. **Query cubes separately** and join in your application 2. **Use a different cube combination** that has a join path 3. **Check the Schema Explorer** to understand cube relationships 4. **Test joins** before deploying to production ### Common Joinable Cube Combinations {% aside type="info" %} Join paths are defined in the server-side Cube.js schema and may change over time. Test multi-cube queries in development before relying on them in production. If a join path doesn't exist, the API returns an error like `"Can't find join path to join 'Cube1', 'Cube2'"`. {% /aside %} | Cube 1 | Cube 2 | Join Key | Works? | |--------|--------|----------|--------| | Orders | PaymentAndRefunds | `order_id`, `location_id` | ✅ Yes | | Orders | ItemTransactions | `order_id`, `location_id` | ✅ Yes | | Orders | CustomerSnapshots | `customer_id`, `location_id` | ✅ Yes | | PaymentAndRefunds | ItemTransactions | `order_id`, `location_id` | ✅ Yes | | Orders | Location | `location_id` | ✅ Yes | | Orders | CatalogModifierList | None | ❌ No | ## Best practices ### 1. Test Multi-Cube Queries Always test multi-cube combinations to ensure a join path exists: ```json { "measures": ["Cube1.measure", "Cube2.measure"], "limit": 1 } ``` If you get a "Can't find join path" error, use client-side joins instead. ### 2. Understand Cardinality Know the relationship between cubes: - **One-to-many**: Orders → Payments (one order, multiple payments) - **Many-to-one**: Orders → Customers (multiple orders, one customer) This affects how measures are aggregated. ### 3. Use Common Dimensions for Row-Level Joins Include dimensions like `order_id` or `location_id` to get row-level detail: ```json { "measures": ["Orders.net_sales", "PaymentAndRefunds.total_amount"], "dimensions": ["Orders.order_id"] } ``` ### 4. Always Set Limits with Multi-Cube Queries Multi-cube queries can return many rows: ```json { "measures": ["Orders.net_sales", "PaymentAndRefunds.count"], "dimensions": ["Orders.location_id"], "limit": 1000 } ``` --- # Measure & Dimension Types > Source: https://developer.squareup.com/docs/reporting-api/cubes/types > Status: BETA > Languages: All > Platforms: All Reference guide for the different measure and dimension types supported across Reporting API cubes. ## Overview Understanding measure and dimension types is crucial for building correct queries and interpreting results. ## Measure types and aggregations ### Sum Measures Most monetary measures use `sum` aggregation: ```json { "name": "Orders.net_sales", "aggType": "sum", "type": "number", "format": "currency" } ``` **Examples**: - `Orders.net_sales` - Total sales after discounts and returns - `Orders.tips_amount` - Total tips received - `Orders.sales_tax_amount` - Total sales tax collected - `Orders.discounts_amount` - Total discounts applied **Behavior**: Values are summed across the selected dimensions and time range. **Example query**: ```json { "measures": ["Orders.net_sales", "Orders.tips_amount"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": "last 30 days" }], "segments": ["Orders.closed_checks"] } ``` Returns the sum of all net sales and tips for the last 30 days. ### Count Measures Count measures use `count` aggregation: ```json { "name": "Orders.count", "aggType": "count", "type": "number" } ``` **Examples**: - `Orders.count` - Number of orders - `Sales.order_count` - Number of orders (via Sales view) - `PaymentAndRefunds.count` - Number of payment/refund transactions **Behavior**: Counts distinct entities (orders, payments, etc.). **Example query**: ```json { "measures": ["Orders.count", "PaymentAndRefunds.count"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": "last 30 days", "granularity": "day" }], "segments": ["Orders.closed_checks"] } ``` Returns daily order and payment counts for the last 30 days. ### Average Measures Average measures use `avg` aggregation: ```json { "name": "Orders.avg_net_sales", "aggType": "avg", "type": "number", "format": "currency" } ``` **Examples**: - `Orders.avg_net_sales` - Average net sales per order - `Orders.avg_total_sales` - Average customer order value **Behavior**: Calculates the mean value across the selected scope. **Example query**: ```json { "measures": [ "Orders.avg_net_sales", "Orders.count" ], "dimensions": ["Orders.location_id"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": "last 30 days" }], "segments": ["Orders.closed_checks"] } ``` Returns average ticket size per location (with order count for context). ### Calculated Measures Some measures are derived from other measures: **Examples**: - `Orders.net_sales` = `top_line_product_sales - discounts_amount - itemized_returns` - `Orders.net_sales_with_tax` = `net_sales + sales_tax_amount` **Behavior**: Calculated on the fly during query execution using the underlying measures. {% aside type="info" %} **Why use calculated measures?** They ensure consistent business logic. For example, `net_sales` is always calculated the same way, ensuring all reports use the same definition. {% /aside %} ## Dimension types ### Categorical Dimensions String-based attributes for grouping and filtering: **Examples**: - `Orders.location_id` - Location identifier - `Orders.sales_channel_id` - Sales channel - `PaymentAndRefunds.payment_method` - Payment method **Usage**: Group by these to break down metrics. **Example query**: ```json { "measures": ["Orders.net_sales"], "dimensions": ["Orders.sales_channel_id"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": "last 30 days" }], "segments": ["Orders.closed_checks"] } ``` Returns sales by channel (online vs in-person). ### Time Dimensions Special dimensions for time-series analysis: **Examples**: - `Orders.sale_timestamp` - UTC sale time - `Orders.local_date` - Local business date - `Orders.local_reporting_timestamp` - Reporting day in local time **Usage**: Use with `timeDimensions` array and `granularity` for time-series aggregation. **Example query**: ```json { "measures": ["Orders.net_sales"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": ["2024-01-01", "2024-01-31"], "granularity": "day" }], "segments": ["Orders.closed_checks"] } ``` Returns daily sales for January. ### Identifier Dimensions Unique identifiers for drill-down analysis: **Examples**: - `Orders.order_id` - Unique order identifier - `Orders.customer_id` - Customer identifier - `PaymentAndRefunds.payment_id` - Payment identifier - `Orders.device_id` - Device identifier **Usage**: Drill down to individual entities or join cubes. **Example query**: ```json { "measures": ["Orders.net_sales"], "dimensions": ["Orders.order_id"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": "last 7 days" }], "segments": ["Orders.closed_checks"], "limit": 100 } ``` Returns individual order details for the last 7 days. ## How aggregations work ### Sum Aggregation Example If you have 3 orders with net_sales of \$10, \$20, and \$30: ```json { "measures": ["Orders.net_sales"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": "today" }] } ``` **Result**: `{"Orders.net_sales": "60.00"}` (10 + 20 + 30) ### Count Aggregation Example Same 3 orders: ```json { "measures": ["Orders.count"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": "today" }] } ``` **Result**: `{"Orders.count": 3}` ### Average Aggregation Example Same 3 orders: ```json { "measures": ["Orders.avg_net_sales"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": "today" }] } ``` **Result**: `{"Orders.avg_net_sales": "20.00"}` ((10 + 20 + 30) / 3) ## Discovering types in metadata When you query `/reporting/v1/meta`, each measure and dimension includes its type: **Measure example**: ```json { "name": "Orders.net_sales", "title": "Net Sales", "description": "Total sales after discounts and returns, excluding tax", "type": "number", "aggType": "sum", "format": "currency" } ``` **Dimension example**: ```json { "name": "Orders.location_id", "title": "Location ID", "type": "string" } ``` **Time dimension example**: ```json { "name": "Orders.sale_timestamp", "title": "Sale Timestamp", "type": "time" } ``` --- # Reporting API Overview > Source: https://developer.squareup.com/docs/reporting-api > Status: BETA > Languages: All > Platforms: All Query sales, payments, customer, and catalog metrics using the Square Reporting API's dynamic REST interface. The **Reporting API** is a Cube-based REST API that enables developers to query sales, payments, customer, and catalog metrics for embedding analytics, operational automations, and back-office workflows. Unlike traditional REST APIs with dozens of fixed endpoints, the Reporting API exposes **only two universal endpoints**: - **`/v1/meta`** — Describes what data is available (schema discovery) - **`/v1/load`** — Queries the actual data based on discovered metadata This design enables a powerful, flexible API surface that can evolve without breaking client applications that properly implement schema discovery. ### Benefits of Dynamic Schemas - **Expansive data catalog**: Access hundreds of measures and dimensions without endpoint proliferation - **Automatic joins**: Combine measures from multiple cubes in a single query—the API handles joins automatically - **Future-proof clients**: Applications that discover schema at runtime adapt automatically to new fields - **Consistent API**: One query pattern works for all reporting needs ### Trade-offs to Consider - **More complex client logic**: You must implement schema discovery and dynamic query building - **Runtime validation required**: Invalid query combinations are caught at query time, not compile time - **Learning curve**: Developers must understand Cube concepts (cubes, measures, dimensions, segments) ## How it differs from traditional REST APIs | Traditional REST API | Reporting API (Dynamic Schema) | |---------------------|--------------------------------| | Fixed endpoints per resource | Two universal endpoints for all queries | | Fields are predetermined | Fields discovered via metadata | | Schema changes require API versioning | Schema evolves; clients adapt via discovery | | Simple to use, limited flexibility | Complex setup, maximum flexibility | | Client knows structure at build time | Client learns structure at runtime | ## Key concepts ### Views A **view** is a curated, pre-joined interface built on top of one or more cubes. For example, the `Sales` view combines data from multiple cubes and pre-filters for common sales reporting. **Views are the recommended starting point** — they provide a simpler query surface with sensible defaults. Use cubes directly when you need cross-cube joins or access to raw data not exposed through a view. ### Cubes A **cube** is a logical grouping of related measures and dimensions (e.g., `Orders`, `PaymentAndRefunds`). Think of a cube as a "data table" optimized for analytics. You can query cubes directly for advanced use cases, but prefer views for standard reporting. ### Measures **Measures** are numeric aggregations you want to calculate (e.g., `Orders.net_sales`, `Orders.tips_amount`). These are the "what you're measuring" in your query. ### Dimensions **Dimensions** are attributes you use to group or filter data (e.g., `Orders.location_id`, `Orders.sale_timestamp`). These are the "how you're slicing" your measures. ### Segments **Segments** are predefined filters that represent common business logic (e.g., `Orders.closed_checks` filters to fully paid orders). Use segments to ensure report parity with Square's dashboard. ### Time Dimensions **Time dimensions** are special dimensions for time-series queries. They support granularity (day, week, month) and date ranges. ## Core workflow Every interaction with the Reporting API follows this pattern:  **Workflow Steps**: 1. **Discover**: Call `/v1/meta` to learn available cubes, measures, dimensions, and segments 2. **Cache**: Store metadata with appropriate TTL (1-24 hours) 3. **Validate**: Check that your desired measures and dimensions are compatible 4. **Execute**: Send a query to `/v1/load` with your selected entities 5. **Handle Long-Running Queries**: Retry if you receive a "Continue wait" response 6. **Process**: Parse and use the returned data ## Automatic multi-cube joins One of the most powerful features of the Reporting API is the ability to combine data from multiple cubes in a single query. Simply include measures from different cubes in your `measures` array, and the API automatically handles the join. **Example: Combine Orders, Payments, and Items data** ```json { "measures": [ "Orders.net_sales", "Orders.count", "PaymentAndRefunds.total_amount", "ItemTransactions.net_quantity" ], "dimensions": ["Orders.location_id"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": "last 30 days" }], "segments": ["Orders.closed_checks"] } ``` This single query returns sales, payment totals, and items sold—all grouped by location. No need to specify join conditions; the API handles it automatically. See [Multi-Cube Joins](/docs/reporting-api/cubes/joins) for more multi-cube join examples. ## Quick example Here's a minimal example showing the complete workflow: ### Step 1: Discover Available Data ```bash curl -X GET "https://connect.squareup.com/reporting/v1/meta" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" ``` This returns metadata describing all available cubes, measures, and dimensions. ### Step 2: Query Daily Sales ```bash curl -X POST "https://connect.squareup.com/reporting/v1/load" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "query": { "measures": ["Orders.net_sales", "Orders.net_sales_with_tax"], "timeDimensions": [{ "dimension": "Orders.sale_timestamp", "dateRange": ["2024-01-01", "2024-01-31"], "granularity": "day" }], "segments": ["Orders.closed_checks"] } }' ``` This returns daily net sales for January 2024, filtered to closed (completed) orders. ## When to use the Reporting API The Reporting API is ideal for: - **Custom dashboards and analytics**: Build seller-facing or internal reporting tools - **Operational automations**: Trigger workflows based on sales thresholds or patterns - **Back-office integrations**: Sync sales data to accounting, inventory, or ERP systems - **Ad-hoc analysis**: Query arbitrary combinations of metrics and dimensions **Not recommended for**: - Real-time transaction processing (use the Orders or Payments APIs instead) - Operational CRUD operations (use domain-specific APIs) - High-frequency polling (data freshness is ~15 minutes for most cubes) ## Authentication The Reporting API supports both personal access tokens and OAuth tokens. **For OAuth applications**, request the `REPORTING_READ` permission scope when obtaining authorization from sellers. **For testing and development**, you can use personal access tokens from the Developer Console. Include your access token in the `Authorization` header on every request: ``` Authorization: Bearer YOUR_ACCESS_TOKEN ``` See [Getting Started](/docs/reporting-api/getting-started) for detailed authentication setup and [OAuth API documentation](/docs/oauth-api/overview) for implementing OAuth flows. ## Data freshness - **Standard cubes** (e.g., `Orders`): ~15 minute refresh interval ## Base URL All Reporting API endpoints are under: ``` https://connect.squareup.com/reporting ``` Core endpoints: - `GET /v1/meta` — Schema discovery - `GET/POST /v1/load` — Execute queries ## Additional Resources * [Schema Explorer](https://reporting-schema-explorer-production-f.squarecdn.com/schema-explorer.html) — Browse all cubes, measures, and dimensions interactively * [Meta tag reference](https://reporting-schema-explorer-production-f.squarecdn.com/meta-tags.html) — Browse all fields returned from the metadata response * [Cube Query Format](https://cube.dev/docs/product/apis-integrations/core-data-apis/rest-api/query-format#query-properties) — Complete query property reference * [Error Handling](reporting-api/error-handling) — Handle errors and edge cases * [Best Practices](reporting-api/best-practices) — Production-ready patterns --- # Brand Guidelines > Source: https://developer.squareup.com/docs/brand-guidelines > Status: PUBLIC > Languages: All > Platforms: All Learn about brand guidelines for building with Square APIs Thank you for choosing to develop with Square! We know integrating is only your first step. Advertising your integration with Square to your customer base is very important. In this section you’ll find marketing assets you can post on your site and style guidelines on how to use them. ## Logos Please download and choose which logo to host on your website when mentioning integration with Square. {% table %} --- *  * [White](https://images.ctfassets.net/1nw4q0oohfju/6eaji5oYvuLMpwUOTkPyRj/e2a672153be4083c9ad7153f14ff06f9/brand_white-edcf1cc8a10da45f6d0e67215261bcd24f83a48336be84307d3ca62314963adf.png) 1125x320, PNG --- *  * [Black](https://images.ctfassets.net/1nw4q0oohfju/7ntgEwkWoRRGdTu91jr9yd/62d9d5b8a06107447c15cbbfffd2abb7/brand_black-f67a0600187e707deb22521e86452dd6746851870f815e9e454423b9e5b8a6d3.png) 1125x320, PNG {% /table %} ## Style Guidelines When using our logo please make sure to follow the below set of guidelines: * Do not alter the button or badge * Do not change the proposition * Do not change the color * Give a good amount of clear space around the logo: at least 40 pixels --- # Card Surcharges > Source: https://developer.squareup.com/docs/payments-api/take-payments/card-payments/card-surcharges > Status: BETA > Languages: All > Platforms: All Learn how to retrieve and understand card surcharge details on payment records. **Applies to:** [Payments API](payments-refunds) | [Terminal API](terminal-api/overview) | [Mobile Payments SDK](/docs/mobile-payments-sdk) ## Overview Card surcharges allow merchants to pass on the cost of card processing to customers who choose to pay with a credit card instead of cash or other payment methods. When a payment includes a surcharge, Square provides detailed information about the surcharge amounts in the payment record through the `applied_card_surcharge_details` ([CardSurchargeDetails](https://developer.squareup.com/reference/square/objects/CardSurchargeDetails)) property. This guide explains how to retrieve and interpret surcharge information when fetching payments that were processed with card surcharges through the Terminal API or Mobile Payments SDK. {% aside type="info" %} Card surcharge details are read-only fields that appear on payment records. These surcharges must be configured and applied through the Terminal API or Mobile Payments SDK when creating the payment. {% /aside %} ## Understanding card surcharge details When you retrieve a payment that includes a card surcharge using [GetPayment](https://developer.squareup.com/reference/square/payments-api/get-payment) or [ListPayments](https://developer.squareup.com/reference/square/payments-api/list-payments), the response includes surcharge information in the `card_details.applied_card_surcharge_details` field. ### Surcharge field structure The [applied_card_surcharge_details](https://developer.squareup.com/reference/square/objects/CardPaymentDetails#definition__property-applied_card_surcharge_details) property contains one money sub-property: - **`card_surcharge_money`** - The base surcharge amount levied by the merchant for using a card payment instead of cash or another payment type. This amount only includes the surcharge itself, not any additional fees or taxes on the surcharge. This amount is specified in the smallest denomination of the applicable currency (for example, US dollar amounts are specified in cents). ## Retrieving payments with surcharges The [applied_card_surcharge_details](https://developer.squareup.com/reference/square/objects/CardPaymentDetails#definition__property-applied_card_surcharge_details) field only appears on payments that have a surcharge applied. For payments without surcharges, this field is not present in the response. ### Example: Get a payment with surcharge details The following example shows how to retrieve a payment and access its surcharge information: ```` ### Example response with surcharge details When a payment includes a surcharge, the response includes the `applied_card_surcharge_details` field: ```json { "payment": { "id": "PAYMENT_ID", "created_at": "2024-12-15T10:30:00.000Z", "updated_at": "2024-12-15T10:30:05.000Z", "amount_money": { "amount": 10300, "currency": "USD" }, "status": "COMPLETED", "source_type": "CARD", "card_details": { "status": "CAPTURED", "card": { "card_brand": "VISA", "last_4": "1234", "exp_month": 12, "exp_year": 2025 }, "applied_card_surcharge_details": { "card_surcharge_money": { "amount": 300, "currency": "USD" } }, "entry_method": "KEYED", "cvv_status": "CVV_ACCEPTED", "avs_status": "AVS_ACCEPTED" }, "location_id": "LOCATION_ID", "total_money": { "amount": 10300, "currency": "USD" } } } ``` In this example: - The base payment amount is $100.00 (10000 cents) - The card surcharge is $3.00 (300 cents) - The total payment amount is $103.00 (10300 cents) ## Working with surcharge data When processing payments with surcharges in your application, consider the following: ### Calculating the base amount To determine the original purchase amount before surcharges: ```javascript // Example in JavaScript const payment = // ... retrieved payment object const cardDetails = payment.card_details; if (cardDetails.applied_card_surcharge_details) { const totalAmount = payment.amount_money.amount; const surchargeAmount = cardDetails.applied_card_surcharge_details.card_surcharge_money.amount; const baseAmount = totalAmount - surchargeAmount; console.log(`Base amount: ${baseAmount}`); console.log(`Surcharge: ${surchargeAmount}`); console.log(`Total charged: ${totalAmount}`); } ``` ### Displaying surcharge information When displaying payment details to customers or in reports, clearly separate: - The base purchase amount - The card processing surcharge - The total amount charged ## Best practices When working with card surcharges: 1. **Check for surcharge presence** - Always verify if `applied_card_surcharge_details` exists before attempting to access its fields, as it's only present on payments with surcharges. 2. **Handle currency properly** - Remember that all money amounts are in the smallest currency unit. Convert appropriately for display purposes. 3. **Maintain transparency** - Clearly communicate surcharge amounts to customers in receipts and payment confirmations. 4. **Consider regional regulations** - Be aware that surcharge regulations vary by region. Some jurisdictions have specific rules about card surcharges. 5. **Reconciliation** - When reconciling payments, account for both the base surcharge and any additional amounts separately for accurate financial reporting. ## Integration considerations ### Terminal API and Mobile Payments SDK Card surcharges are configured and applied when creating payments through: - [Terminal API](terminal-api/overview) - For in-person payments using Square Terminal - [Mobile Payments SDK](mobile-payments-sdk) - For mobile payment applications The surcharge configuration happens at payment creation time through these APIs. The Payments API only provides read access to surcharge details after the payment is created. ### Webhooks When using [payment webhooks](payments-api/webhooks), the `payment.created` and `payment.updated` webhook events include the `applied_card_surcharge_details` field for payments with surcharges. ## Related resources - [Retrieve Payments](payments-api/retrieve-payments) - Learn more about fetching payment details - [Card Payments](payments-api/take-payments/card-payments) - Overview of card payment processing - [Terminal API Documentation](terminal-api/overview) - Configure surcharges for in-person payments - [Mobile Payments SDK](mobile-payments-sdk) - Implement surcharges in mobile applications - [Working with Monetary Amounts](build-basics/common-data-types/working-with-monetary-amounts) - Understanding currency handling in Square APIs --- # Bank Account API Operations > Source: https://developer.squareup.com/docs/bank-accounts-api/retrieve-bank-accounts > Status: PUBLIC > Languages: All > Platforms: All Learn how to retrieve information about a linked bank account. **Applies to:** [Bank Accounts API](https://developer.squareup.com/reference/square/bank-accounts-api) {% subheading %}Use the Bank Accounts API to retrieve seller bank accounts and manage customer bank accounts for ACH payment processing.{% /subheading %} {% toc hide=true /%} ## Overview This guide covers the operational aspects of using the Bank Accounts API to: * Retrieve seller bank account information * Create and manage customer bank accounts for ACH payments * List and filter bank accounts * Handle bank account verification and status changes ## API Operations ### Available Endpoints **For All Bank Accounts:** * [ListBankAccounts](https://developer.squareup.com/reference/square/bank-accounts-api/list-bank-accounts) - Retrieve bank accounts * [GetBankAccount](https://developer.squareup.com/reference/square/bank-accounts-api/get-bank-account) - Retrieve a specific bank account by ID **For Customer Bank Accounts Only:** * [CreateBankAccount](https://developer.squareup.com/reference/square/bank-accounts-api/create-bank-account) - Store a new customer bank account * [DisableBankAccount](https://developer.squareup.com/reference/square/bank-accounts-api/disable-bank-account) - Disable a customer bank account ## Working with Seller Bank Accounts ### Retrieve Seller Bank Accounts List all bank accounts for a specific location: ```bash curl https://connect.squareup.com/v2/bank-accounts?location_id=LOCATION_ID \ -H 'Square-Version: 2025-12-17' \ -H 'Authorization: Bearer ACCESS_TOKEN' \ -H 'Content-Type: application/json' ``` ### Example Seller Bank Account Response ```json { "bank_account": { "id": "w3yRgCGYQnwmdl0R3GB", "account_number_suffix": "971", "country": "US", "currency": "USD", "account_type": "CHECKING", "holder_name": "Jane Doe", "primary_bank_identification_number": "112200303", "location_id": "MER9ST0SUIQ9W", "status": "VERIFIED", "creditable": true, "debitable": true, "version": 3, "bank_name": "Checking and Savings Bank" } } ``` ## Working with Customer Bank Accounts ### Create a Customer Bank Account To create a customer bank account, you'll need a `source_id` (a bank account token) obtained from your payment form or bank account verification flow: ```bash curl https://connect.squareup.com/v2/bank-accounts \ -X POST \ -H 'Square-Version: 2025-12-17' \ -H 'Authorization: Bearer ACCESS_TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "idempotency_key": "4e43559a-f0fd-47d3-9da2-7ea1f97d94be", "source_id": "bnon:CA4SEHsQwr0rx6DbWLD5BQaqMnoYAQ", "customer_id": "HM3B2D5JKGZ69359BTEHXM2V8M" }' ``` {% aside type="info" %} The `source_id` is a bank account token (prefixed with "bnon:") that represents the tokenized bank account details. This token is typically obtained from the Web Payments SDK or other Square payment form implementations. {% /aside %} ### Example Customer Bank Account Response ```json { "bank_account": { "id": "bact:OxfBTiXgByaXds1K4GB", "account_number_suffix": "000", "country": "US", "currency": "USD", "account_type": "CHECKING", "holder_name": "Nicola Snow", "primary_bank_identification_number": "011401533", "status": "VERIFICATION_IN_PROGRESS", "creditable": true, "debitable": true, "fingerprint": "sq-1-mO3XNctJpTLL8uYowOWpioS8nQyTc838gcBo90254XonoEJ_c7Uw6yqL6qihFNY8fA", "version": 1, "bank_name": "Citizens Bank", "customer_id": "HM3B2D5JKGZ69359BTEHXM2V8M" } } ``` {% aside type="info" %} Note the `fingerprint` field in customer bank accounts - this unique identifier helps prevent duplicate bank accounts from being created for the same customer and underlying bank account combination. {% /aside %} ### List Customer Bank Accounts {% aside type="warning" %} Always include the `customer_id` parameter when retrieving customer bank accounts. Omitting this parameter will return the seller's bank accounts instead, potentially exposing sensitive financial information to unauthorized users (such as displaying the business owner's bank account to store employees). {% /aside %} The same `ListBankAccounts` endpoint is used for both seller and customer bank accounts. The `customer_id` parameter determines which accounts are returned: **For customer bank accounts** (include customer_id): ```bash curl https://connect.squareup.com/v2/bank-accounts?customer_id=HM3B2D5JKGZ69359BTEHXM2V8M \ -H 'Square-Version: 2025-12-17' \ -H 'Authorization: Bearer ACCESS_TOKEN' \ -H 'Content-Type: application/json' ``` **For seller bank accounts** (omit customer_id or use location_id): ```bash # Returns seller accounts - DO NOT use this in customer-facing interfaces curl https://connect.squareup.com/v2/bank-accounts \ -H 'Square-Version: 2025-12-17' \ -H 'Authorization: Bearer ACCESS_TOKEN' \ -H 'Content-Type: application/json' ``` Always validate that you're passing the correct customer ID, especially in point-of-sale or customer-facing applications where exposing seller bank accounts would be a serious security breach. ### Disable a Customer Bank Account Bank accounts cannot be deleted once created, but they can be disabled to prevent further use. Disabling a bank account is the recommended approach when: * A customer wants to remove a bank account from their payment methods * A bank account has been compromised or flagged for security reasons * You need to prevent accidental charges to an outdated account Once disabled, the bank account: * Cannot be used for new payments * Remains in the system for historical records and compliance * Will still appear in list operations but with a `DISABLED` status * Cannot be re-enabled (a new bank account must be created if needed) ```bash curl https://connect.squareup.com/v2/bank-accounts/bact:OxfBTiXgByaXds1K4GB/disable \ -X POST \ -H 'Square-Version: 2025-12-17' \ -H 'Authorization: Bearer ACCESS_TOKEN' \ -H 'Content-Type: application/json' ``` ## Processing ACH Payments Once a customer bank account is verified, you can use it to process ACH payments: ```bash curl https://connect.squareup.com/v2/payments \ -X POST \ -H 'Square-Version: 2025-12-17' \ -H 'Authorization: Bearer ACCESS_TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "source_id": "bact:OxfBTiXgByaXds1K4GB", "amount_money": { "amount": 5000, "currency": "USD" }, "customer_id": "HM3B2D5JKGZ69359BTEHXM2V8M", "idempotency_key": "payment-key-456" }' ``` {% aside type="info" %} The Payments API will reject attempts to charge a seller's bank account. If you accidentally pass a seller bank account ID (which has a different format than customer bank account IDs), the API returns a `NOT_FOUND` error. Only customer bank accounts (prefixed with "bact:") can be used as payment sources. {% /aside %} ## Filtering and Pagination ### Filter by Account Type When using `ListBankAccounts`, the response will include both seller and customer bank accounts. You can identify the type by checking for either `location_id` (seller accounts) or `customer_id` (customer accounts). ### Pagination Use cursor-based pagination for large result sets: ```bash curl https://connect.squareup.com/v2/bank-accounts?cursor=CURSOR_VALUE \ -H 'Square-Version: 2025-12-17' \ -H 'Authorization: Bearer ACCESS_TOKEN' \ -H 'Content-Type: application/json' ``` ## Handling Webhooks ### Subscribe to Bank Account Events Configure webhook subscriptions to receive notifications for: * `bank_account.created` - New bank account linked * `bank_account.verified` - Account verification completed * `bank_account.disabled` - Account disabled ### Example Webhook Payload ```json { "merchant_id": "MERCHANT_ID", "location_id": "LOCATION_ID", "type": "bank_account.verified", "event_id": "EVENT_ID", "created_at": "2026-01-14T09:30:00Z", "data": { "type": "bank_account", "id": "bact:OxfBTiXgByaXds1K4GB", "object": { "bank_account": { "id": "bact:OxfBTiXgByaXds1K4GB", "status": "VERIFIED", "customer_id": "HM3B2D5JKGZ69359BTEHXM2V8M", "version": 2 // ... additional fields } } } } ``` ## Error Handling ### Common Error Codes | Error Code | Description | Resolution | | ---------- | ----------- | ---------- | | `INVALID_CUSTOMER_ID` | Customer ID doesn't exist | Verify customer exists before creating bank account | | `DUPLICATE_BANK_ACCOUNT` | Bank account already exists for customer | Check fingerprint to avoid duplicates | | `VERIFICATION_REQUIRED` | Account not verified for payments | Complete verification process | | `BANK_ACCOUNT_DISABLED` | Account has been disabled | Use a different bank account | ### Verification States Monitor the `status` field for verification progress: * `VERIFICATION_IN_PROGRESS` - Awaiting verification * `VERIFIED` - Ready for payments * `DISABLED` - Cannot be used ## Implementation Checklist ### For Customer Bank Accounts * **Implement tokenization** - Set up the Web Payments SDK to securely tokenize bank account details before sending them to your server. * **Create customer profile** - Ensure a customer profile exists in Square before attempting to store their bank account information. * **Handle duplicates** - Use the fingerprint field to detect and prevent creating duplicate bank accounts for the same customer. * **Complete verification** - Implement the verification flow using either micro-deposits or instant verification to activate the account. * **Configure webhooks** - Set up webhook handlers to receive notifications when bank account status changes occur. * **Secure storage** - Store the returned bank account ID securely in your database for processing future ACH payments. * **Enable account management** - Provide functionality for customers to disable bank accounts they no longer wish to use. ### For Seller Bank Accounts * **Request permissions** - Ensure your OAuth application has `BANK_ACCOUNTS_READ` permission to access seller bank accounts. * **Display account info** - Retrieve and display bank account information in your application's seller dashboard. * **Support multiple locations** - Handle scenarios where sellers have different bank accounts linked to different locations. * **Monitor status changes** - Implement webhook handlers to track when seller bank accounts are verified or disabled. ## Related APIs * [Payments API](https://developer.squareup.com/reference/square/payments-api) - Process ACH payments using stored bank accounts * [Customers API](https://developer.squareup.com/reference/square/customers-api) - Manage customer profiles linked to bank accounts * [Webhooks API](https://developer.squareup.com/reference/square/webhooks) - Subscribe to bank account events * [Web Payments SDK](https://developer.squareup.com/reference/sdks/web/payments) - Tokenize bank account details --- # Store and Charge Bank Accounts on File > Source: https://developer.squareup.com/docs/web-payments/bank-on-file > Status: BETA > Languages: All > Platforms: All learn how to store and then charge a bank account on file with the Web Payments SDK, Bank Accounts API, and Payments API Applies to: [Web Payments SDK](web-payments/overview) | [Payments API](payments-refunds) | [Bank Accounts API](bank-accounts-api) {% subheading %}Learn how to store and charge a bank account on file with the Web Payments SDK.{% /subheading %} {% toc hide=true /%} ## Overview The Web Payments SDK and Square APIs enable you to securely store customer bank accounts for one-time or recurring ACH charges, eliminating the need for customers to re-link their accounts on each visit. Common use cases include gyms processing monthly membership fees, utility companies streamlining recurring billing, or any business that needs to collect regular ACH payments. By integrating the Bank Accounts API with the Web Payments SDK, you can implement these payment features efficiently while maintaining security and compliance. This guide covers storing bank accounts and processing both one-time and recurring ACH payments. For ACH payments without storing accounts, see [Take ACH Bank Transfer Payments](web-payments/add-ach). {% aside type="tip" %} Bank accounts cannot be deleted once created due to compliance and record-keeping requirements. Instead, use the [DisableBankAccount](https://developer.squareup.com/reference/square/bank-accounts-api/disable-bank-account) endpoint to prevent future charges while maintaining historical records. {% /aside %} ## Prerequisites Before implementing bank account storage, ensure your application meets these requirements: - **Location**: ACH bank transfers via Web Payments SDK are only supported in the United States. For international bank account storage and payment processing, see [International Development](international-development) - **Permissions**: Your application needs: - `BANK_ACCOUNTS_WRITE` and `BANK_ACCOUNTS_READ` - `CUSTOMERS_WRITE` - `PAYMENTS_WRITE` and `PAYMENTS_READ` - **Setup**: Complete the [ACH Bank Transfer Payments setup](web-payments/add-ach#1-attach-ach-to-the-page) - **Bank Support**: Customer banks must be supported by Plaid (Square's banking partner) ## Store a Bank Account This section walks through linking and storing a customer's bank account to their profile for future use.  Charging a buyer on their linked and stored bank account involves the generation and use of these token types: {% table %} * Token {% width="110px" %} * Definition --- * `BNON` * A token returned by Web Payments SDK for a Plaid-authorized bank account. --- * `BACT` * A token returned by Square for a Plaid-authorized bank account stored with Square. --- * `BAUTH` * A token returned by the Web Payments SDK for a buyer-authorized charge on a linked bank account. Use this token with the [CreatePayment](https://developer.squareup.com/reference/square/payments-api/create-payment) method. {% /table %} ### Step 1: Create a Customer First, create a Square Customer object to associate with the bank account: {% tabset %} {% tab id="Request" %} ```` {% /tab %} {% tab id="Response" %} On success, Square returns the new Customer object: ```json { "customer": { "id": "WC1GYWRIT7STE3GU4ZLQ3X76EF", "created_at": "2024-12-19T00:48:34.398Z", "updated_at": "2024-12-19T00:48:34Z", "given_name": "Lauren", "family_name": "Noble", "preferences": { "email_unsubscribed": false }, "creation_source": "THIRD_PARTY", "version": 0 } } ``` Store the `id` returned in the response. You'll need this customer ID for Step 3. {% /tab %} {% /tabset %} ### Step 2: Tokenize the Bank Account The [ach.tokenize](https://developer.squareup.com/reference/sdks/web/payments/objects/ACH#ACH.tokenize) with [AchStoreOptions.intent](https://developer.squareup.com/reference/sdks/web/payments/objects/AchStoreOptions) set to `'STORE'` presents the customer with Plaid's secure authentication interface. Set up an event listener to capture the BNON token after completion of the `tokenize` call: Use the Web Payments SDK to [initialize ACH](https://developer.squareup.com/reference/sdks/web/payments/objects/ACH) and launch the Plaid authentication flow: ```javascript // Initialize ACH payment method const ach = await payments.ach({ transactionId: '415111211611', // Your unique transaction ID }); ach.addEventListener('ontokenization', async function(event) { const { tokenResult, error } = event.detail; if (error) { throw new Error(`Tokenization failed: ${error}`); } if (tokenResult.status === 'OK') { // Send token to your server await fetch('/create-bank-on-file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token: tokenResult.token }) }); } }); // Launch Plaid authentication flow try { await ach.tokenize({ accountHolderName: 'Lauren Noble', intent: 'STORE', // Always use STORE for saving accounts }); } catch (e) { console.error(e); } ``` ### Step 3: Store with Square From your server, create the bank account record using the BNON token and the following parameters: * **source_id** - The BNON token sent to your server after the call to [ach.tokenize](https://developer.squareup.com/reference/sdks/web/payments/objects/ACH#ACH.tokenize). * **customer_id** - The unique ID for the customer generated during customer creation. {% aside type="info" %} **Testing with Real Tokens Required** The `source_id` parameter must be a valid token generated by the Web Payments SDK's `ach.tokenize()` method. You cannot use placeholder values, example tokens from documentation, or manually created strings for testing. If you attempt to use an invalid or made-up `source_id`, you'll receive this error: ```json { "errors": [ { "code": "NOT_FOUND", "detail": "Bank nonce not found", "category": "INVALID_REQUEST_ERROR" } ] } ``` To test the `CreateBankAccount` endpoint, you must first: 1. Implement the Web Payments SDK on your frontend 1. Complete the Plaid bank linking flow 1. Call `ach.tokenize()` to generate a valid token 1. Use that token immediately in your `CreateBankAccount` request Bank tokens are single-use and expire quickly, so you'll need to generate a fresh token for each test. {% /aside %} {% tabset %} {% tab id="Request" %} ```bash curl https://connect.squareupsandbox.com/v2/bank-accounts \ -X POST \ -H 'Authorization: Bearer {ACCESS_TOKEN}' \ -H 'Content-Type: application/json' \ -d '{ "idempotency_key": "{UNIQUE_KEY}", "source_id": "bnon:Ja85BvcwFYPiDZJV4H", "customer_id": "WC1GYWRIT7STE3GU4ZLQ3X76EF" }' ``` {% /tab %} {% tab id="Response" %} A `BACT` token is returned in the `bank_account` object. Use that token with the Web Payments SDK to `tokenize()` a charge on that bank account. ```json { "bank_account": { "id": "bact:J71CYCQ789KnZnXi5HC", "account_number_suffix": "000", "country": "US", "currency": "USD", "account_type": "CHECKING", "holder_name": "Lauren Noble", "status": "VERIFIED", "debitable": true, "customer_id": "WC1GYWRIT7STE3GU4ZLQ3X76EF" } } ``` The response can include: - `debitable: true` - Indicates the account can be charged (send funds) - `creditable: true` - Indicates the account can receive refunds - `status: "VERIFIED"` - Account is ready for payment processing {% /tab %} {% /tabset %} ## One-Time Payments Process a single charge to a customer's stored bank account with these steps. These steps assume a bank account has already been linked and stored to the customer's account. {% aside type="tip" %} The Payments API automatically rejects attempts to charge seller bank accounts (those without the "bact:" prefix). This prevents accidentally charging business accounts instead of customer accounts. {% /aside %} ### Step 1: Retrieve Bank Accounts Retrieve the customer's stored bank accounts using the [ListBankAccounts](https://developer.squareup.com/reference/square/bank-accounts-api/list-bank-accounts) endpoint. **Important:** You must include the `customer_id` as a query parameter to filter results to only that customer's accounts. Without this parameter, the endpoint returns all bank accounts associated with the seller's Square account, including the seller's own bank accounts. {% tabset %} {% tab id="Request" %} ```` {% /tab %} {% tab id="Response" %} ```json { "bank_accounts": [{ "id": "bact:J71CYCQ789KnZnXi5HC", "account_number_suffix": "000", "account_type": "CHECKING", "holder_name": "Lauren Noble", "bank_name": "Citizens Bank", "status": "VERIFIED", "debitable": true, "customer_id": "WC1GYWRIT7STE3GU4ZLQ3X76EF" }] } ``` {% /tab %} {% /tabset %} ### Step 2: Display Options Present the available bank accounts in your UI to let the customer select which account to charge. Display enough information for the customer to identify their account while maintaining security: **Recommended display fields:** - `holder_name` - Account holder's name - `bank_name` - Financial institution name - `account_type` - CHECKING or SAVINGS - `account_number_suffix` - Last 3-4 digits (e.g., "...000") **Important:** Never display full account or routing numbers. The `account_number_suffix` provides enough information for identification. **Example UI pattern:** ```javascript bankAccounts.forEach(account => { // Display: "Citizens Bank - Checking (...000) - Lauren Noble" const displayText = `${account.bank_name} - ${account.account_type} (...${account.account_number_suffix}) - ${account.holder_name}`; // Verify account is ready for payments if (account.status === 'VERIFIED' && account.debitable) { // Add to payment method selection UI addPaymentOption(displayText, account.id); } }); ``` Capture the selected account's `id` to use in the next step for payment authorization. ### Step 3: Authorize Payment Use the [ach.tokenize](https://developer.squareup.com/reference/sdks/web/payments/objects/ACH#ACH.tokenize) method to generate a BAUTH token for the one-time charge authorization with the following parameters: * **bankAccountId** - The stored bank account token (`BACT`) that was returned when your application stored the bank account with Square. * **intent** - The purpose of the authorization. In this case, to `CHARGE` the bank account. * **amount** - The full price of the purchase, including any taxes and fees. * **currency** - The currency of the bank account to authorize. ```javascript // Initialize ACH const ach = await payments.ach({ transactionId: '415111211611', }); // Authorize one-time charge try { await ach.tokenize({ intent: 'CHARGE', // Use CHARGE for one-time payments amount: '5.00', // Amount as string with dollars and cents currency: 'USD', bankAccountId: "bact:J71CYCQ789KnZnXi5HC", }); } catch (e) { console.error(e); } ``` Capture the BAUTH token using the same event listener pattern shown earlier. ### Step 4: Process Payment On your application server, create the payment with the Payments API [CreatePayment](https://developer.squareup.com/reference/square/payments-api/create-payment) endpoint using the BAUTH token: {% tabset %} {% tab id="Request" %} ```` {% /tab %} {% tab id="Response" %} ```json { "payment": { "id": "grLqqvOQQiXYI2wi9ph5BzP5x5CAS", "created_at": "2024-12-20T00:01:24.106Z", "updated_at": "2024-12-20T00:01:24.434Z", "amount_money": { "amount": 500, "currency": "USD" }, "status": "PENDING", "source_type": "BANK_ACCOUNT", "customer_id": "WC1GYWRIT7STE3GU4ZLQ3X76EF", "total_money": { "amount": 500, "currency": "USD" }, "receipt_number": "nrKp", "bank_account_details": { "bank_name": "Citizens Bank", "transfer_type": "ACH", "account_ownership_type": "INDIVIDUAL", "fingerprint": "sq-2-EYJJ2O13bT1CDmvlkYACunmnyjEtBBwt8HDLLrYDFjR", "country": "US", "ach_details": { "routing_number": "054000017", "account_number_suffix": "000", "account_type": "CHECKING" } }, "application_details": { "square_product": "ECOMMERCE_API", "application_id": "sq0ids-IifESTgTFwVVT8F8yEEARR" }, "version_token": "M3nP7wD8fWzkijFI7USxvUugfqWkNuC0sO6HSi6BuHF6o" } } ``` {% /tab %} {% /tabset %} ## Fixed Recurring Payments Set up automated recurring charges for the same amount at regular intervals.  ### Understanding Recurring Charge Authorization When you use intent: `RECURRING_CHARGE` with the Web Payments SDK, Square does not automatically charge the customer’s bank account on the specified frequency. The `RECURRING_CHARGE` operation only creates a reusable BAUTH token that’s authorized for recurring payments according to the schedule you defined. Your application is fully responsible for implementing the scheduling logic and calling the Payments API `CreatePayment` endpoint with this token whenever a payment is due. Think of it as getting permission to charge on a schedule, not setting up automatic charges. You’ll need to build your own scheduling system (using cron jobs, scheduled tasks, or a third-party scheduling service) to trigger payments at the appropriate times. The frequency parameters you provide during tokenization are for authorization purposes only—they tell the customer what schedule they’re agreeing to, but don’t create any automatic payment processing on Square’s side. {% aside type="info" %} These steps assume a bank account has already been linked and stored to the customer's account. {% /aside %} ### Step 1: Retrieve and Display Accounts Follow the same process as one-time payments to retrieve and display the customer's bank accounts. Consider adding UI elements to capture subscription preferences like frequency and start date. ### Step 2: Authorize Recurring Charges Configure the recurring payment schedule when generating the BAUTH token: {% tabset %} {% tab id="Daily" %} ```javascript // Helper function to create daily recurring frequency configuration function createDailyRecurringFrequency(days) { return { days: days // Charge every [x] days }; } // Example: Charge every 3 days const dailyFrequency = createDailyRecurringFrequency(3); // Use with ach.tokenize() await ach.tokenize({ intent: 'RECURRING_CHARGE', bankAccountId: 'bact:J71CYCQ789KnZnXi5HC', amount: '9.99', currency: 'USD', frequency: dailyFrequency, startDate: '2024-12-21' }); ``` {% /tab %} {% tab id="Weekly" %} ```javascript // Valid day values for weekly recurring payments const DAYS_OF_WEEK = [ 'SUNDAY', 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY' ]; // Helper function to create weekly recurring frequency configuration function createWeeklyRecurringFrequency(daysOfWeek, occurrence = 1) { return { weekly: { daysOfWeek: daysOfWeek, // Array of days, e.g., ['MONDAY', 'FRIDAY'] occurrence: occurrence // Repeat every [x] weeks (default: 1) } }; } // Example: Charge every Monday and Friday const weeklyFrequency = createWeeklyRecurringFrequency( ['MONDAY', 'FRIDAY'], 1 ); // Use with ach.tokenize() await ach.tokenize({ intent: 'RECURRING_CHARGE', bankAccountId: 'bact:J71CYCQ789KnZnXi5HC', amount: '29.99', currency: 'USD', frequency: weeklyFrequency, startDate: '2024-12-21' }); ``` {% /tab %} {% tab id="Monthly" %} ```javascript // Helper function to create monthly recurring frequency configuration function createMonthlyRecurringFrequency(daysOfMonth, endOfMonth = false, occurrence = 1) { return { monthly: { occurrence: occurrence, // Repeat every [x] months (default: 1) days: { daysOfMonth: daysOfMonth, // Array of days, e.g., [1, 15] endOfMonth: endOfMonth // true to include last day of month } } }; } // Example 1: Charge on the 1st and 15th of every month const monthlyFrequency = createMonthlyRecurringFrequency( [1, 15], false, 1 ); // Example 2: Charge on the last day of every month only const endOfMonthFrequency = createMonthlyRecurringFrequency( [], // Empty array when charging only on last day true, // Charge on last day 1 ); // Example 3: Charge on the 15th AND last day of every month const midAndEndMonthFrequency = createMonthlyRecurringFrequency( [15], // Charge on the 15th true, // ALSO charge on last day 1 ); // Use with ach.tokenize() await ach.tokenize({ intent: 'RECURRING_CHARGE', bankAccountId: 'bact:J71CYCQ789KnZnXi5HC', amount: '49.99', currency: 'USD', frequency: midAndEndMonthFrequency, // Charges on 15th and last day startDate: '2024-12-21' }); ``` {% /tab %} {% tab id="Yearly" %} ```javascript // Valid month values for recurring payments const MONTHS = [ 'JANUARY', 'FEBRUARY', 'MARCH', 'APRIL', 'MAY', 'JUNE', 'JULY', 'AUGUST', 'SEPTEMBER', 'OCTOBER', 'NOVEMBER', 'DECEMBER' ]; // Helper function to create yearly recurring frequency configuration function createYearlyRecurringFrequency(months, daysOfMonth, endOfMonth = false, occurrence = 1) { return { yearly: { occurrence: occurrence, // Repeat every [x] years (default: 1) months: months, // Array of months, e.g., ['JANUARY', 'JULY'] days: { daysOfMonth: daysOfMonth, // Array of days, e.g., [1, 15] endOfMonth: endOfMonth // true for last day of month } } }; } // Example: Charge on the 15th of January and July every year const yearlyFrequency = createYearlyRecurringFrequency( ['JANUARY', 'JULY'], [15], false, 1 ); // Use with ach.tokenize() await ach.tokenize({ intent: 'RECURRING_CHARGE', bankAccountId: 'bact:J71CYCQ789KnZnXi5HC', amount: '99.99', currency: 'USD', frequency: yearlyFrequency, startDate: '2024-12-21' }); ``` {% /tab %} {% /tabset %} ### Step 3: Process Recurring Charges Use the BAUTH token to charge the account according to the authorized schedule. The payment process is identical to one-time payments, but you can reuse the BAUTH token for each scheduled charge without re-authorization. {% aside type="info" %} You don't need to charge immediately after obtaining the BAUTH token. Store it securely and use it when each scheduled payment is due. {% /aside %} ## Disable Bank Accounts While you cannot delete stored bank accounts, you can disable them to prevent future charges: {% aside type="important" %} Disabling is permanent for that specific bank account record. To charge the account again, the customer must complete the full onboarding flow as if linking a new account. {% /aside %} {% tabset %} {% tab id="Request" %} ```bash curl https://connect.squareupsandbox.com/v2/bank-accounts/bact:J71CYCQ789KnZnXi5HC/disable \ -X POST \ -H 'Authorization: Bearer {ACCESS-TOKEN}' \ -H 'Content-Type: application/json' ``` {% /tab %} {% tab id="Response" %} ```json { "bank_account": { "id": "bact:J71CYCQ789KnZnXi5HC", "status": "DISABLED", "debitable": false, "customer_id": "WC1GYWRIT7STE3GU4ZLQ3X76EF" } } ``` {% /tab %} {% /tabset %} ## Monitoring Bank Account Status Subscribe to these webhook events to track bank account lifecycle changes: - `bank_account.created` - Triggered when a customer links a new bank account - `bank_account.verified` - Triggered when account verification completes (account ready for payments) - `bank_account.disabled` - Triggered when an account is disabled These events help you update your UI, notify customers, and handle account status changes automatically. See [Webhooks API](https://developer.squareup.com/reference/square/webhooks) for setup instructions. ## Troubleshooting ### Payment Declined - Verify account status is `VERIFIED` before attempting charges - Ensure sufficient funds in customer's account - Check that BAUTH token hasn't expired (tokens have limited validity) ### Duplicate Bank Account Error - Check the `fingerprint` field of existing accounts before creating new ones - Use `ListBankAccounts` to find existing accounts for the customer ### Account Not Found - Verify you're using the correct `customer_id` when listing accounts - Ensure the bank account ID starts with "bact:" prefix - Check that the account hasn't been disabled ## Next Steps - Implement [webhook notifications](payments-api/webhooks) for payment status updates - Set up [error handling](payments-api/error-codes) for failed payments - Configure [testing scenarios](devtools/sandbox/payments) in the Sandbox environment --- # Common Pitfalls and Solutions > Source: https://developer.squareup.com/docs/checkout-api/common-pitfalls > Status: PUBLIC > Languages: All > Platforms: All Learn about some of the common pitfalls encountered when integrating your application with the Checkout API **Applies to:** [Checkout API](checkout-api) | [Orders API](orders-api/what-it-does) | [Payments API](payments-refunds) ## Overview This guide addresses the most common issues developers encounter when implementing the Checkout API, particularly around payment link behavior, Dashboard visibility, and order management. ## Critical Distinction: Dashboard vs API Payment Links {% aside type="important" %} Payment links created through the Square Dashboard and those created via the Checkout API behave very differently. Mixing up these two types is a common source of confusion. {% /aside %} ### Quick identification guide | Creation Method | URL Pattern | Reusability | Dashboard Visible | |----------------|-------------|-------------|-------------------| | **Square Dashboard** | `/checkout/{CHECKOUT_ID}` | Multiple uses | ✅ Yes | | **Checkout API** | `/order/{ORDER_ID}` | Single use only | ❌ No | ### Why this matters - **Customer confusion**: Expecting API links to work multiple times leads to failed payments - **Seller confusion**: Sellers looking for API-generated links in Dashboard won't find them - **Integration issues**: Treating these as interchangeable breaks payment flows ## Common pitfall #1: Expecting reusable payment links ### The problem API-generated payment links (`CreatePaymentLink`) are **single-use only**. After successful payment, the link shows a thank you page and cannot process another payment. ### The solution - Create a new payment link for each transaction - Never share the same API-generated link with multiple customers - If you need reusable links, direct sellers to create them via Square Dashboard ## Common pitfall #2: Looking for API links in dashboard ### The problem Sellers expect to see all payment links in their Square Dashboard, but API-generated links are **never visible there** by design. ### The solution Build your own management interface if sellers need to: - View active payment links - Track payment link usage - Cancel unused links ### Why Square does this - API links are meant for programmatic, one-time transactions - Showing thousands of single-use links would clutter the Dashboard - Dashboard links serve a different purpose (reusable, seller-managed) ## Common pitfall #3: Orders stuck in `OPEN` status ### The problem Orders with fulfillments created via payment links remain in `OPEN` status even after successful payment. They never automatically transition to `COMPLETED`. ### What happens 1. Customer pays successfully → Payment status: `COMPLETED` ✅ 2. Order status remains: `OPEN` ⚠️ 3. Fulfillment cannot be updated programmatically ❌ ### The solution **Current Limitation**: Orders must be manually completed in Square Dashboard's Orders Manager. **Workaround options**: 1. **Educate sellers**: Train them to manually complete orders in Orders Manager 1. **Track separately**: Maintain order completion status in your own system {% aside type="info" %} This is a known limitation. Square currently doesn't support programmatic fulfillment updates for `DIGITAL` type fulfillments created through payment links. {% /aside %} ## Common pitfall #4: Incorrect payment link deletion ### The problem Deleting payment links has different effects depending on timing, which can lead to unintended data loss or orphaned orders. ### Payment link deletion behavior matrix | When Deleted | Payment Link | Order Status | Payment | Use Case | |--------------|--------------|--------------|---------|----------| | **Before payment** | Deleted | `CANCELED` | None exists | Abandon cart cleanup | | **After payment** | Deleted | `OPEN` | `COMPLETED` | Clean up used links | ## Common pitfall #5: Missing order references ### The problem When orders created from payment links don't include `customer_id`, it's hard to associate orders with customers. ### The solution 1. Check for customer ID on order 1. If not on order, retrieve associated payment 1. Read customer ID on payment ## Quick decision tree ### "Should I use Dashboard or API payment links?" | Feature | Square Dashboard | Checkout API | |---------|-----------------|--------------| | **Link Reusability** | ✅ Multiple uses | ❌ Single use only | | **Creation Method** | Manual via UI | Programmatic via API | | **Dashboard Visibility** | ✅ Fully visible | ❌ Never visible | | **Best For** | Reusable products, donations | Dynamic orders, sessions | | **Fulfillment Updates** | ✅ Supported | ⚠️ Manual only | ### "My integration isn't working as expected" If your integration isn't working as expected, follow these debugging steps to find the most likely cause: 1. **Check the URL pattern** - Is it `/checkout` (Dashboard) or `/order` (API)? 2. **Verify single use** - Has this payment link already been used? 3. **Check Dashboard expectations** - Are you looking for API links in Dashboard? 4. **Review order status** - Are you expecting automatic completion with fulfillments? 5. **Examine deletion timing** - Are you deleting before or after payment? ## Best Practices Checklist ✅ **DO:** - Create new payment links for each transaction - Build your own tracking system for API-generated links - Handle order completion manually or track separately - Delete payment links after successful payment (if desired) - Retrieve customer info from payments, not orders ❌ **DON'T:** - Expect API payment links to be reusable - Look for API links in Square Dashboard - Expect orders with fulfillments to auto-complete - Assume Dashboard and API links work the same way {% aside type="warning" %} If you delete a payment link while a buyer is actively completing checkout, they will receive an error message and cannot complete their payment. The underlying order is automatically canceled. {% /aside %} ## See Also - [Checkout API Overview](checkout-api) - Complete API introduction - [Manage Checkout](checkout-api/manage-checkout) - CRUD operations for payment links - [Square Order Checkout](checkout-api/square-order-checkout) - Working with orders - [Guidelines and Limitations](checkout-api/guidelines-and-limitations) - Complete limitations reference --- # Exception Handling > Source: https://developer.squareup.com/docs/web-payments/exception-handling > Status: PUBLIC > Languages: All > Platforms: All Learn about how to handle common Web Payments SDK exceptions. **Applies to:** [Web Payments SDK](https://developer.squareup.com/reference/sdks/web/payments) {% subheading %}Learn about how to handle common Web Payments SDK exceptions.{% /subheading %} ## Overview Each of the [Web Payments SDK](https://developer.squareup.com/reference/sdks/web/payments) methods may throw JavaScript errors for certain exceptional circumstances. While most errors should be used to assist in diagnosing problems with initial SDK setup, there are a few key errors that benefit from a deeper understanding. This topic helps you learn more about these errors. The full list of Web Payments SDK errors can be found in the [SDK reference documentation](https://developer.squareup.com/reference/sdks/web/payments/errors). ## PaymentMethodUnsupportedError **Cause:** The `PaymentMethodUnsupportedError` error is thrown whenever a specific payment method is unusable. The `message` property of this error describes the reason in more detail. Common reasons the Web Payments SDK might throw this error include: - Your application is not set up to support this payment method. **Example:** - Most payment methods require registering them for your application in the developer console. - The payment method is not supported on the buyer's browser. **Example:** - Apple Pay is only supported on Safari browsers. - The payment method is temporarily disabled or otherwise inaccessible. **Example:** - Due to a third-party vendor outage, Square disables the payment method that requires it. This error is normally thrown when the individual payment method is initialized. **Solution:** - Any payment method that can safely be turned off should have logic to handle the `PaymentMethodUnsupportedError` error. **Example:** - ```javascript try { googlePay = await payments.googlePay(); } catch (e) { if (e.name === 'PaymentMethodUnsupportedError') { // disable the Google Pay method } else { // general exception handling for other error types } } ``` ## BrowserNotSupportedError **Cause:** The `BrowserNotSupportedError` is thrown when a buyer's browser cannot use the Web Payments SDK at all. This differs from `PaymentMethodUnsupportedError`, which indicates that specific payment methods aren't supported while the SDK itself remains functional. When `BrowserNotSupportedError` occurs, the buyer must switch to a different browser to complete their transaction. This error is normally thrown when the `payments()` method is called. **Solution:** This browser does not support the Web Payments SDK product. Any error messaging should direct the end-user to use another browser. **Example:** - ```javascript try { payments = await square.payments(); } catch (e) { if (e.name === 'BrowserNotSupportedError') { // provide error message directing the end-user to use another browser } else { // general exception handling } } ``` ## WebSdkEmbedError **Cause:** The Web Payments SDK must be run from a [fully secure HTTPS context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts). This is a standard security measure to protect end-users from MITM (man-in-the-middle) attackers. **Solution:** In order for a context to be considered secure, it must: - be hosted using HTTPS/TLS - use non-deprecated network security settings - if contained within another page, that page must also be HTTPS/TLS You can test your context using the JavaScript expression `window.isSecureContext`. --- # Transfer Orders API > Source: https://developer.squareup.com/docs/transfer-orders-api > Status: BETA > Languages: All > Platforms: All Learn about ordering and receiving items from another seller location by using the Transfer Orders API. **Applies to:** [Transfer Orders API](https://developer.squareup.com/reference/square/transfer-order-api) | [Inventory API](inventory-api/what-it-does) | [Catalog API](catalog-api/what-it-does) | [Locations API](locations-api) | [Team API](team/overview) {% subheading %}Learn about ordering and receiving items from another seller location by using the Transfer Orders API.{% /subheading %} ## Overview The [Transfer Orders API](https://developer.squareup.com/reference/square/transfer-order-api) lets you build applications that manage and track inventory movements between a seller's Square locations. When implementing inventory management features, you can use this API to programmatically handle stock transfers while maintaining consistency with the seller's catalog and inventory data. Building on the foundations of Square's Catalog API and Inventory API, the Transfer Orders API lets you add stock transfer capabilities to your inventory management applications. Your application can create transfer orders using the seller's existing catalog item variations, and Square automatically handles the corresponding inventory adjustments. This means you can implement transfer order functionality without building custom inventory tracking logic or maintaining separate item mappings. Square sellers who subscribe to [Square Plus](https://squareup.com/point-of-sale/retail/pricing) can create and update stock transfer orders from the [Square Stand](https://squareup.com/hardware/stand) or [Square Dashboard](https://app.squareup.com/dashboard). These transfer orders can be retrieved and updated with the Transfer Orders API. You can also create and update transfer orders from your application using the API. ## Prerequisites To implement the Transfer Orders API in your application, you need: - A Square account for testing. - OAuth 2.0 implementation with the following permissions: - `INVENTORY_READ` - `INVENTORY_WRITE` - Catalog items that are: - Active in the seller's catalog. - Enabled for inventory tracking. - Stocked at the source location. ## What is a transfer order? A transfer order is an object that tracks the movement of inventory items between a seller's locations. When creating a transfer order in your application, you specify: - The source location sending the items. - The destination location receiving the items. - An array of catalog item variations and their quantities. - Optional metadata such as expected delivery dates and tracking information. Square maintains the transfer order's status and automatically handles inventory adjustments when your application updates the transfer order through its lifecycle. ## Core features The Transfer Orders API provides endpoints that let you implement inventory transfer functionality in your applications, with automatic inventory adjustments handled by Square at each stage of the transfer lifecycle. You can: - Create transfer orders using existing catalog item variations. - Track the transfer status through a well-defined state machine. - Record item receipts with support for damaged or missing items. - Search and retrieve the transfer order history. - Let Square handle all inventory adjustments automatically. ## How transfer orders work ### Status lifecycle Your application interacts with transfer orders through a defined state machine.  At several points in the transfer order lifecycle, Square creates Inventory API `InventoryChange` or `InventoryCount` records to document the movement of transferred items. 1. **DRAFT** - The initial state after creation. - You can modify or delete the order. - No inventory adjustments occur. - This is useful for staged order creation. 2. **STARTED** - The transfer is initiated. - Square decrements inventory from the source location. - An [InventoryAdjustment](https://developer.squareup.com/reference/square/objects/InventoryAdjustment) is created for each item to document its in-transit status. - Deletion is no longer allowed. - Call when the physical transfer begins. 3. **PARTIALLY_RECEIVED** - A partial receipt is recorded. - Your application has recorded some received quantities. - Remaining quantities stay in transit. - Additional receipt calls are allowed. - Use for multi-part deliveries. 4. **COMPLETED** - The transfer is finished. - All quantities are received or canceled. - No pending quantities remain. - Only metadata updates are allowed. - The final state for successful transfers. 5. **CANCELED** - The transfer is canceled. - Square returns pending quantities to their source. - Only metadata updates are allowed. - Use when a transfer won't complete. ### Inventory state transitions Square automatically handles these inventory state changes when your application updates a transfer order: 1. When you start a transfer: - Source location - `IN_STOCK` → `IN_TRANSIT` 2. When you record receipt: - Good condition - `IN_TRANSIT` → `IN_STOCK` (at its destination) - Damaged items - `IN_TRANSIT` → `WASTE` (at its destination) - Canceled items - `IN_TRANSIT` → `IN_STOCK` (returned to its source) ## Implementation use cases The Transfer Orders API supports various inventory management features you might need to implement: ### Inventory rebalancing - Automate stock redistribution between locations - Implement min/max inventory-level maintenance - Build demand-based transfer workflows ### Location management - Automate new location stocking - Handle location closure inventory redistribution - Support seasonal location transfers ### Event support - Manage temporary location transfers - Implement return workflows - Track event-specific inventory ### Distribution management - Build warehouse management features - Implement distribution center workflows - Support bulk transfer operations ## API integration points When implementing the Transfer Orders API, you interact with these related APIs: - **Catalog API** - Transfer order line items refer to the catalog item variations that are being transferred. - **Inventory API** - Check stock levels, adjustments, and transfers. - **Locations API** - Search for transfer orders by location ID. - **Team API** - Associate team members with transfer operations. ## Implementation steps To integrate the Transfer Orders API into your application: 1. Review the [Manage Transfer Orders](transfer-orders-api/transfer-orders-guide) for implementation basics. 2. Study the [Inventory Management Reporting](transfer-orders-api/inventory-management-reporting) for reporting on inventory transfer activity. 3. Follow the [Transfer Order Webhooks](transfer-orders-api/webhooks) guide to learn how to respond to inventory transfer events. ## Version and support The Transfer Orders API is in Beta. While developing your integration: - Monitor the release notes for updates. - Test extensively in the Square Sandbox environment. - Implement robust error handling. - Follow Square's versioning guidelines. - Consider future Beta phase changes. --- # Transfer Order Webhooks > Source: https://developer.squareup.com/docs/transfer-orders-api/webhooks > Status: BETA > Languages: All > Platforms: All Learn how to handle webhooks emitted by the Transfer Orders API on an inventory transfer event. **Applies to:** [Transfer Orders API](https://developer.squareup.com/reference/square/transfer-order-api) | [Webhook Subscriptions API](webhooks/webhook-subscriptions-api) {% subheading %}Learn how to handle webhooks emitted by the Transfer Orders API on an inventory transfer event.{% /subheading %} ## Overview The Transfer Orders API provides three webhook events that fire when transfer orders change: - **`transfer_order.created`** - Triggered when a new transfer order is created. - **`transfer_order.updated`** - Triggered when a transfer order is modified (items are added or removed, quantities are changed, statuses are updated, or items are received). - **`transfer_order.deleted`** - Triggered when a draft transfer order is deleted. For the Fleet Foot Running Co. multi-location inventory management system, these webhooks enable: - Immediate notifications to source location managers when another store requests their inventory. - Real-time updates when transfer quantities or items change. - Alerts when transfers are canceled or completed. - Automatic synchronization across all store management systems. For more information about how to implement Square API webhooks, see [Square Webhooks](webhooks/overview). ## Transfer order webhooks for multi-location notifications Fleet Foot Running Co. operates multiple store locations and managers need real-time visibility when other stores request inventory transfers from their location. By implementing transfer order webhooks, your application can automatically notify managers when transfer orders affecting their inventory are created, modified, or canceled—ensuring that they stay informed about stock movements across the entire retail network. ## Setting up webhook subscriptions To receive transfer order notifications across all Fleet Foot Running Co. locations, create a webhook subscription that monitors all three transfer order events: {% tabset %} {% tab id="Request" %} ```` {% /tab %} {% tab id="Response" %} ```json { "subscription": { "id": "wbhk_a7c08f3d-0b2e-4f91-b4f2-1234567890ab", "name": "Fleet Foot Transfer Order Notifications", "enabled": true, "event_types": [ "transfer_order.created", "transfer_order.updated", "transfer_order.deleted" ], "notification_url": "https://fleetfoot.example.com/webhooks/transfer-orders", "api_version": "2025-09-24", "signature_key": "1k3n4p5q6r7s8t9u0v1w2x3y4z5a6b7c", "created_at": "2025-10-09T10:00:00.000Z", "updated_at": "2025-10-09T10:00:00.000Z" } } ``` {% /tab %} {% /tabset %} {% aside type="important" %} Store the `signature_key` securely. You need it to verify that webhook notifications are genuinely from Square. {% /aside %} ## Webhook event payloads ### transfer_order.created event When the Olympia store creates a transfer order requesting New Balance FuelCell shoes from Tacoma, the webhook fires immediately. ```json { "merchant_id": "FLEET_FOOT_MERCHANT_ID", "type": "transfer_order.created", "event_id": "evt_123e4567-e89b-12d3-a456-426614174000", "created_at": "2025-10-09T10:15:00.000Z", "data": { "type": "transfer_order", "id": "ZXOLSDDQKOCBMTWY", "object": { "transfer_order": { "id": "ZXOLSDDQKOCBMTWY", "source_location_id": "EWVV7AYQC45SS", "destination_location_id": "90A9W5RRYD2GQ", "status": "DRAFT", "created_at": "2025-10-09T10:15:00.000Z", "updated_at": "2025-10-09T10:15:00.000Z", "expected_at": "2025-10-10T09:00:00Z", "created_by_team_member_id": "tm_olympia_manager", "line_items": [ { "transfer_order_line_uid": "HVU4OIW64ZMKPGTA", "item_variation_id": "XPBDUOG3VQBRASADVRSOYS67", "quantity_ordered": "5" }, { "transfer_order_line_uid": "JKL5OIW64ZMKPGTB", "item_variation_id": "R6C5CP6JXBZMA22FSXYVUC5W", "quantity_ordered": "3" } ], "version": 1 } } } } ``` ### transfer_order.updated event The webhook fires for various update scenarios: **Scenario 1: Adding items to a draft** ```json { "merchant_id": "FLEET_FOOT_MERCHANT_ID", "type": "transfer_order.updated", "event_id": "evt_223e4567-e89b-12d3-a456-426614174001", "created_at": "2025-10-09T10:30:00.000Z", "data": { "type": "transfer_order", "id": "ZXOLSDDQKOCBMTWY", "object": { "transfer_order": { "id": "ZXOLSDDQKOCBMTWY", "source_location_id": "EWVV7AYQC45SS", "destination_location_id": "90A9W5RRYD2GQ", "status": "DRAFT", "created_at": "2025-10-09T10:15:00.000Z", "updated_at": "2025-10-09T10:30:00.000Z", "expected_at": "2025-10-10T09:00:00Z", "created_by_team_member_id": "tm_olympia_manager", "line_items": [ { "transfer_order_line_uid": "HVU4OIW64ZMKPGTA", "item_variation_id": "XPBDUOG3VQBRASADVRSOYS67", "quantity_ordered": "5" }, { "transfer_order_line_uid": "JKL5OIW64ZMKPGTB", "item_variation_id": "R6C5CP6JXBZMA22FSXYVUC5W", "quantity_ordered": "3" }, { "transfer_order_line_uid": "MNO6OIW64ZMKPGTC", "item_variation_id": "J4H4PL3UGRAWCUDW3JS73LT6", "quantity_ordered": "4" } ], "version": 2 } } } } ``` **Scenario 2: Starting the transfer** ```json { "merchant_id": "FLEET_FOOT_MERCHANT_ID", "type": "transfer_order.updated", "event_id": "evt_323e4567-e89b-12d3-a456-426614174002", "created_at": "2025-10-09T11:00:00.000Z", "data": { "type": "transfer_order", "id": "ZXOLSDDQKOCBMTWY", "object": { "transfer_order": { "id": "ZXOLSDDQKOCBMTWY", "source_location_id": "EWVV7AYQC45SS", "destination_location_id": "90A9W5RRYD2GQ", "status": "STARTED", "created_at": "2025-10-09T10:15:00.000Z", "updated_at": "2025-10-09T11:00:00.000Z", "started_at": "2025-10-09T11:00:00.000Z", "expected_at": "2025-10-10T09:00:00Z", "tracking_number": "FLEET-042", "created_by_team_member_id": "tm_olympia_manager", "started_by_team_member_id": "tm_tacoma_staff", "line_items": [ { "transfer_order_line_uid": "HVU4OIW64ZMKPGTA", "item_variation_id": "XPBDUOG3VQBRASADVRSOYS67", "quantity_ordered": "5", "quantity_pending": "5", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "0" }, { "transfer_order_line_uid": "JKL5OIW64ZMKPGTB", "item_variation_id": "R6C5CP6JXBZMA22FSXYVUC5W", "quantity_ordered": "3", "quantity_pending": "3", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "0" }, { "transfer_order_line_uid": "MNO6OIW64ZMKPGTC", "item_variation_id": "J4H4PL3UGRAWCUDW3JS73LT6", "quantity_ordered": "4", "quantity_pending": "4", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "0" } ], "version": 3 } } } } ``` **Scenario 3: Receiving items (partial receipt)** ```json { "merchant_id": "FLEET_FOOT_MERCHANT_ID", "type": "transfer_order.updated", "event_id": "evt_423e4567-e89b-12d3-a456-426614174003", "created_at": "2025-10-10T09:30:00.000Z", "data": { "type": "transfer_order", "id": "ZXOLSDDQKOCBMTWY", "object": { "transfer_order": { "id": "ZXOLSDDQKOCBMTWY", "source_location_id": "EWVV7AYQC45SS", "destination_location_id": "90A9W5RRYD2GQ", "status": "PARTIALLY_RECEIVED", "created_at": "2025-10-09T10:15:00.000Z", "updated_at": "2025-10-10T09:30:00.000Z", "started_at": "2025-10-09T11:00:00.000Z", "expected_at": "2025-10-10T09:00:00Z", "tracking_number": "FLEET-042", "created_by_team_member_id": "tm_olympia_manager", "started_by_team_member_id": "tm_tacoma_staff", "received_by_team_member_id": "tm_olympia_receiver", "line_items": [ { "transfer_order_line_uid": "HVU4OIW64ZMKPGTA", "item_variation_id": "XPBDUOG3VQBRASADVRSOYS67", "quantity_ordered": "5", "quantity_pending": "0", "quantity_received": "4", "quantity_damaged": "1", "quantity_canceled": "0" }, { "transfer_order_line_uid": "JKL5OIW64ZMKPGTB", "item_variation_id": "R6C5CP6JXBZMA22FSXYVUC5W", "quantity_ordered": "3", "quantity_pending": "0", "quantity_received": "3", "quantity_damaged": "0", "quantity_canceled": "0" }, { "transfer_order_line_uid": "MNO6OIW64ZMKPGTC", "item_variation_id": "J4H4PL3UGRAWCUDW3JS73LT6", "quantity_ordered": "4", "quantity_pending": "2", "quantity_received": "2", "quantity_damaged": "0", "quantity_canceled": "0" } ], "version": 4 } } } } ``` ### transfer_order.deleted event When a draft transfer order is deleted. ```json { "merchant_id": "FLEET_FOOT_MERCHANT_ID", "type": "transfer_order.deleted", "event_id": "evt_523e4567-e89b-12d3-a456-426614174004", "created_at": "2025-10-09T10:45:00.000Z", "data": { "type": "transfer_order", "id": "ABC123DELETEEXAMPLE", "deleted": true } } ``` ## Processing webhook notifications Your webhook endpoint should process notifications and alert the appropriate store managers. ```javascript // Example webhook handler for Fleet Foot Running Co. app.post('/webhooks/transfer-orders', async (req, res) => { // Verify the webhook signature if (!verifyWebhookSignature(req)) { return res.status(401).send('Unauthorized'); } const event = req.body; const transferOrder = event.data.object?.transfer_order; try { switch(event.type) { case 'transfer_order.created': // Notify source location manager about new transfer request await notifySourceLocation(transferOrder); break; case 'transfer_order.updated': // Determine what changed and notify relevant parties await processTransferUpdate(transferOrder); break; case 'transfer_order.deleted': // Notify that transfer was canceled await notifyCancellation(event.data.id); break; } // Acknowledge receipt res.status(200).send('OK'); } catch (error) { console.error('Webhook processing error:', error); res.status(500).send('Internal Server Error'); } }); async function notifySourceLocation(transferOrder) { const sourceLocationId = transferOrder.source_location_id; const destinationLocationId = transferOrder.destination_location_id; // Look up location details const sourceLocation = await getLocationDetails(sourceLocationId); const destinationLocation = await getLocationDetails(destinationLocationId); // Get item details for the notification const itemDetails = await getItemDetails(transferOrder.line_items); // Send notification to source location manager const notification = { type: 'TRANSFER_REQUEST', priority: transferOrder.status === 'STARTED' ? 'HIGH' : 'MEDIUM', message: `${destinationLocation.name} has requested a transfer of ${itemDetails.totalItems} items`, details: { transferOrderId: transferOrder.id, requestingStore: destinationLocation.name, status: transferOrder.status, expectedDate: transferOrder.expected_at, items: itemDetails.breakdown } }; await sendManagerNotification(sourceLocation.manager_email, notification); } async function processTransferUpdate(transferOrder) { // Check what changed by examining the status if (transferOrder.status === 'STARTED') { // Transfer has been started - notify destination await notifyDestinationLocation(transferOrder, 'TRANSFER_STARTED'); } else if (transferOrder.status === 'PARTIALLY_RECEIVED') { // Some items received - notify source about discrepancies await checkForDiscrepancies(transferOrder); } else if (transferOrder.status === 'COMPLETED') { // Transfer complete - notify both locations await notifyTransferComplete(transferOrder); } else if (transferOrder.status === 'CANCELED') { // Transfer canceled - notify both locations await notifyTransferCanceled(transferOrder); } } ``` ## Best practices for webhook implementation ### Idempotent processing Transfer order webhooks might be delivered multiple times. Use the `event_id` to ensure that you process each event only once. ```javascript async function processWebhook(event) { // Check if we've already processed this event if (await hasProcessedEvent(event.event_id)) { return { status: 'already_processed' }; } // Process the event await handleTransferOrderEvent(event); // Mark as processed await markEventProcessed(event.event_id); } ``` ### Quick response times Respond to webhook notifications within one minute to avoid receiving a retry request from Square. ```javascript app.post('/webhooks/transfer-orders', async (req, res) => { // Immediately acknowledge receipt res.status(200).send('OK'); // Process asynchronously processWebhookAsync(req.body); }); ``` ### Signature verification Always verify webhook signatures to ensure that notifications are from Square. ```javascript function verifyWebhookSignature(request) { const signature = request.headers['x-square-hmacsha256-signature']; const body = JSON.stringify(request.body); const hash = crypto .createHmac('sha256', webhookSignatureKey) .update(request.headers['x-square-request-timestamp'] + body) .digest('base64'); return signature === hash; } ``` ### Location-specific routing Route notifications to the appropriate store managers based on their role. ```javascript async function routeNotification(transferOrder) { const notifications = []; // Always notify source location when items are requested if (transferOrder.source_location_id) { notifications.push({ locationId: transferOrder.source_location_id, type: 'OUTBOUND_TRANSFER', priority: 'HIGH' }); } // Notify destination for status updates if (transferOrder.status !== 'DRAFT') { notifications.push({ locationId: transferOrder.destination_location_id, type: 'INBOUND_TRANSFER', priority: 'MEDIUM' }); } return notifications; } ``` ## Testing webhook notifications Square provides a webhook tester in the Developer Console. To test your Fleet Foot Running Co. notification system: 1. Navigate to the **Webhooks** section in the Square Developer Console. 2. Select your transfer order webhook subscription. 3. Choose **Send test event** and choose the event type. 4. Verify that your endpoint receives the notification and that managers get alerts. You can also trigger real webhook events in the Square Sandbox environment by creating, updating, and deleting test transfer orders between your test locations. ## Webhook event flow example The following shows a typical sequence of webhook events for a complete transfer between Fleet Foot Running Co. stores: 1. **10:15 AM** - Olympia creates a draft transfer → `transfer_order.created` (the Tacoma manager is notified). 2. **10:30 AM** - Olympia adds more items → `transfer_order.updated` (the Tacoma manager is notified of the changes). 3. **11:00 AM** - Tacoma starts the transfer → `transfer_order.updated` (the Olympia manager is notified of the shipment). 4. **Next day 9:30 AM** - Olympia receives items → `transfer_order.updated` (both managers are notified of the completion). This webhook-driven notification system ensures that all Fleet Foot Running Co. locations stay synchronized and that managers have real-time visibility into inventory movements affecting their stores. --- # Channels API > Source: https://developer.squareup.com/docs/channels-api > Status: BETA > Languages: All > Platforms: All Learn how to retrieve a seller's sales, marketing, and fulfillment channels with the Channels API. **Applies to:** [Channels API](https://developer.squareup.com/reference/square/channels-api) {% subheading %}Learn how to use a Square API to retrieve channel information for a Square seller.{% /subheading %} {% toc hide=true /%} ## Overview The Square Channels API allows you to manage and retrieve information about channels associated with a seller's account. A channel represents the link between a seller and the platforms or services where they conduct business — the medium through which buyers and sellers interact. Conceptually, a channel serves as an abstraction or interface that can be implemented by various objects or entities within the Square ecosystem. A channel can be one of the following classes: * **Sales channel** - The mechanism that created an [Order](https://developer.squareup.com/reference/square/objects/order), order line items, or both. * **Marketing channel** - The mechanism that sourced the traffic that resulted in an order. This is generally limited to channels (internal or external) that directly pull product or service listings from the Square platform. * **Fulfillment channel** - The mechanism that fulfilled the Order. A [Channel](https://developer.squareup.com/reference/square/objects/Channel) object can be associated with various platform concepts through its `reference` field. The [Reference](https://developer.squareup.com/reference/square/objects/Reference) object includes: - `type` - The type of platform concept (required). - `id` - The ID of the referenced entity (required). {% aside type="info" %} The Channels API is not supported in the Sandbox. {% /aside %} ## Available endpoints The Channels API provides the following endpoints for retrieving channel information: - [BulkRetrieveChannels](https://developer.squareup.com/reference/square/channels-api/bulk-retrieve-channels) - Looks up multiple channels in a single request. - [ListChannels](https://developer.squareup.com/reference/square/channels-api/list-channels) - Retrieves a list of all channels with optional filtering. - [RetrieveChannel](https://developer.squareup.com/reference/square/channels-api/retrieve-channel) - Retrieves detailed information about a specific channel. ## Working with channels The Channels API gives your application the ability to report on the marketing, sales, and fulfillment channels available to a seller and which products and services are available through each channel. ## Use cases Integrating the Channels API with the Catalog and Location APIs allows for many use cases that a seller limit the visibility of catalog objects to channels such as a seller's locations or the point of sale used to make a sale. ### Item visibility The Channels API is essential for understanding how catalog items are distributed across a seller’s sales channels. When used alongside a catalog item variation’s `channels_integrations` properties, it enables applications to determine exactly where specific products can be sold or services can be offered. For example, consider a coffee shop that sells ceramic mugs. The Channels API lists all available sales channels (from physical store locations to online shops), while the catalog data identifies which mugs are available through each channel. Together, they allow applications to accurately display product availability, whether an item is limited to certain store locations, available at all POS systems, or listed online. Beyond catalog management, the API also helps applications discover enabled Square services (like gift cards or invoicing) and identify authorized third-party integrations. This allows applications to dynamically adapt their features to match the seller's capabilities and channel configuration. ### Menu visibility Unlike other Catalog objects, `CatalogCategory` objects cannot be directly assigned to locations. Instead, they use a **channel-based visibility system** to determine where menus appear across a seller's locations. #### Key Concepts - **Menu Categories**: `CatalogCategory` objects with `category_type` set to `MENU_CATEGORY` form the structure of menus created in the Square Dashboard - **Channels**: Represent different distribution points for catalog content, including physical locations - **Channel References**: Link channels to specific entities like locations ### How Menu Visibility Works When a seller creates and manages menus in the Square Dashboard: 1. They define menu categories (`CatalogCategory` objects) 2. They assign these categories to specific locations through the Dashboard interface 3. Behind the scenes, Square uses **channels** to manage this visibility ### Retrieving Location-Specific Menus via API To find which menu categories are visible at a specific location using the Catalog API: #### Step 1: Get the Seller's Channels First, retrieve all channels for the seller. Look for channels where `reference.type` equals `LOCATION` - these represent the seller's physical locations. **Example Response:** ```json { "channels": [ { "id": "CH_IT55vdjtd81xkZvF68NXQqPO54qnOXkyQRkiBQlQuYC", "merchant_id": "25PR2SBM8PV5D", "name": "FlamingRed", "version": 1, "reference": { "type": "LOCATION", "id": "E78VDDVFW1EYX" // This is the Location ID }, "status": "ACTIVE", "created_at": "2023-04-26T13:10:24.808Z", "updated_at": "2023-04-26T13:10:24.808Z" }, { "id": "CH_4s6dFPG7nd0Axgdcu8NXQqPO54qnOXkyQRkiBQlQuYC", "merchant_id": "25PR2SBM8PV5D", "name": "Seed Data test", "reference": { "type": "LOCATION", "id": "LFF3HK0DN6A61" }, "status": "ACTIVE" }, { "id": "CH_leic5ZC8kuuyAVAIQUlxuEG72EYFNBoxFCz5574e9945o", "merchant_id": "25PR2SBM8PV5D", "name": "Online store", "reference": { "type": "LOCATION", "id": "LRAXEJYQZ0JED" }, "status": "ACTIVE" } ] } ``` #### Step 2: Map Channels to Locations From the channels response, create a mapping: | Channel ID | Channel Name | Location ID | |------------|--------------|-------------| | `CH_IT55vdjtd81xkZvF68NXQqPO54qnOXkyQRkiBQlQuYC` | FlamingRed | `E78VDDVFW1EYX` | | `CH_4s6dFPG7nd0Axgdcu8NXQqPO54qnOXkyQRkiBQlQuYC` | Seed Data test | `LFF3HK0DN6A61` | | `CH_leic5ZC8kuuyAVAIQUlxuEG72EYFNBoxFCz5574e9945o` | Online store | `LRAXEJYQZ0JED` | #### Step 3: Check Menu Category Visibility When you retrieve a menu category, check its `channels` array to see which locations can display it. **Example: Breakfast Menu Category** ```json { "object": { "type": "CATEGORY", "id": "3H3ADZMYJU6U27JYO2PZFANQ", "updated_at": "2025-08-06T07:21:55.923Z", "created_at": "2025-06-02T18:24:46.958Z", "version": 1754464915923, "is_deleted": false, "present_at_all_locations": true, "category_data": { "name": "Breakfast menu", "category_type": "MENU_CATEGORY", "parent_category": { "ordinal": 137439154960 }, "is_top_level": true, "channels": [ "CH_IT55vdjtd81xkZvF68NXQqPO54qnOXkyQRkiBQlQuYC" // FlamingRed channel ], "online_visibility": false } } } ``` This breakfast menu category includes the channel ID `CH_IT55vdjtd81xkZvF68NXQqPO54qnOXkyQRkiBQlQuYC`, which corresponds to the "FlamingRed" location (`E78VDDVFW1EYX`). ## How to list channels To retrieve a list of all channels, use the `ListChannels` endpoint. This endpoint supports filtering and pagination. Filters are useful when a seller has many channels of various types. You can filter the channels list by: - `reference_type` - Filter by the type of reference associated with the channel. - `status` - Filter by channel status. The two filter types can be combined into an `AND` query but you cannot request multiple values for a given filter. For example, you can request channels of `reference_type` == `LOCATION` and `status` == `ACTIVE` but you cannot request channels of (`reference_type` == `LOCATION` OR `reference_type` == `KIOSK`) and `status` == `ACTIVE`. {% tabset %} {% tab id="Request" %} This example asks for a list of all active channel locations: ```` {% /tab %} {% tab id="Response" %} The response shows that this seller has two active locations. ```json { "channels": [ { "id": "CH_cjVl8mqAdQmv0hCdaF16agtdnoQ9JrkS4gEd5PlQuYC", "merchant_id": "0DCJPNSXMA29K", "name": "My Business", "version": 1, "reference": { "type": "LOCATION", "id": "90A9W5RRYD2GQ" }, "status": "ACTIVE", "created_at": "2023-04-07T09:55:48.777Z", "updated_at": "2023-04-07T09:55:48.777Z", "processed_gdpr_forget": false }, { "id": "CH_PxXxNVd4hBTcIBPErF16agtdnoQ9JrkS4gEd5PlQuYC", "merchant_id": "0DCJPNSXMA29K", "name": "My 2nd location", "version": 1, "reference": { "type": "LOCATION", "id": "EWVV7AYQC45SS" }, "status": "ACTIVE", "created_at": "2023-04-07T08:33:34.708Z", "updated_at": "2023-04-07T08:33:34.708Z", "processed_gdpr_forget": false } ] } ``` {% /tab %} {% /tabset %} ## How to bulk retrieve channels Use the `BulkRetrieveChannels` endpoint when you need to retrieve information about multiple specific channels at once. This is more efficient than making individual requests for each channel. The source for the channel ID list might be the results of a [SearchCatalogObjects](catalog-api/search-catalog-objects) query that returns a long list of catalog objects. Each object that has channel ID information can be parsed to get the referenced ID and then added to the `BulkRetrieveChannels` query. An application might also cache the most frequently referenced channel IDs and then run the `BulkRetrieveChannels` request to get the current state of these channels. {% tabset %} {% tab id="Request" %} In this example, the application caches the channel IDs for a seller's locations and the third-party applications that they're using. With one request, the application gets the channel information for these locations and applications. ```` {% /tab %} {% tab id="Response" %} ```json { "responses": { "CH_cjVl8mqAdQmv0hCdaF16agtdnoQ9JrkS4gEd5PlQuYC": { "channel": { "id": "CH_cjVl8mqAdQmv0hCdaF16agtdnoQ9JrkS4gEd5PlQuYC", "merchant_id": "0DCJPNSXMA29K", "name": "My Business", "version": 1, "reference": { "type": "LOCATION", "id": "90A9W5RRYD2GQ" }, "status": "ACTIVE", "created_at": "2023-04-07T09:55:48.777Z", "updated_at": "2023-04-07T09:55:48.777Z", "processed_gdpr_forget": false } }, "CH_PxXxNVd4hBTcIBPErF16agtdnoQ9JrkS4gEd5PlQuYC": { "channel": { "id": "CH_PxXxNVd4hBTcIBPErF16agtdnoQ9JrkS4gEd5PlQuYC", "merchant_id": "0DCJPNSXMA29K", "name": "My 2nd location", "version": 1, "reference": { "type": "LOCATION", "id": "EWVV7AYQC45SS" }, "status": "ACTIVE", "created_at": "2023-04-07T08:33:34.708Z", "updated_at": "2023-04-07T08:33:34.708Z", "processed_gdpr_forget": false } }, "CH_jL8ic7qvKWTHgpJOEKJghCeMP62C4VVP4pXfvJPd9945o": { "channel": { "id": "CH_jL8ic7qvKWTHgpJOEKJghCeMP62C4VVP4pXfvJPd9945o", "merchant_id": "0DCJPNSXMA29K", "name": "Point of Sale", "version": 1, "reference": { "type": "POINT_OF_SALE" }, "status": "ACTIVE", "created_at": "2025-06-12T01:11:46.200Z", "updated_at": "2025-06-12T01:11:46.200Z", "processed_gdpr_forget": false } }, "CH_KH0VQTd9HzLmOWRGrF16agtdnoQ9JrkS4gEd5PlQuYC": { "channel": { "id": "CH_KH0VQTd9HzLmOWRGrF16agtdnoQ9JrkS4gEd5PlQuYC", "merchant_id": "0DCJPNSXMA29K", "name": "paymentform_test", "version": 1, "reference": { "type": "OAUTH_APPLICATION", "id": "sq0ids-DjHCld9txNH4ekueiPQwyg" }, "status": "ACTIVE", "created_at": "2023-11-08T01:13:29.705Z", "updated_at": "2023-11-08T01:13:29.705Z", "processed_gdpr_forget": false } } } } ``` {% /tab %} {% /tabset %} Important notes: - You can request up to 100 channels in a single request. - The response includes a map of channel IDs to their corresponding channel information. - If any channels cannot be retrieved, the response includes error details for those specific channels. ## How to retrieve a single channel When you need information about a specific channel, use the `RetrieveChannel` endpoint. {% tabset %} {% tab id="Request" %} This example requests a channel whose ID is `CH_KH0VQTd9HzLmOWRGrF16agtdnoQ9JrkS4gEd5PlQuYC`. ```` {% /tab %} {% tab id="Response" %} The response shows that this seller is currently using a third-party application called "Kiosk Manager". ```json { "channel": { "id": "CH_KH0VQTd9HzLmOWRGrF16agtdnoQ9JrkS4gEd5PlQuYC", "merchant_id": "0DCJPNSXMA29K", "name": "Kiosk Manager, "version": 1, "reference": { "type": "OAUTH_APPLICATION", "id": "sq0ids-DjHCld9txNH4ekueiPQwyg" }, "status": "ACTIVE", "created_at": "2023-11-08T01:13:29.705Z", "updated_at": "2023-11-08T01:13:29.705Z", "processed_gdpr_forget": false } } ``` {% /tab %} {% /tabset %} ## Authentication requirements All Channels API endpoints: - Support OAuth 2.0 access tokens. - Require `CHANNELS_READ` permission for OAuth 2.0 authentication. ## Error handling Common error codes across all endpoints include: - `UNAUTHORIZED` - Invalid authentication credentials. - `FORBIDDEN` - Insufficient permissions. - `MISSING_REQUIRED_PARAMETER` - Required parameters are missing from the request. The `RetrieveChannel` endpoint might also return: - `NOT_FOUND` - The requested channel ID doesn't exist. ## API versions All endpoints are currently in Beta status except where noted. The current API version for these endpoints is `2025-10-16`. --- # Manage Transfer Orders > Source: https://developer.squareup.com/docs/transfer-orders-api/transfer-orders-guide > Status: BETA > Languages: All > Platforms: All Learn how to use the Transfer Orders API to manage inventory transfers between a seller's locations. **Applies to:** [Transfer Orders API](https://developer.squareup.com/reference/square/transfer-order-api) | [Inventory API](inventory-api/what-it-does) | [Catalog API](catalog-api/what-it-does) | [Locations API](locations-api) {% subheading %}Learn how to use the Transfer Orders API to manage inventory transfers between a seller's locations.{% /subheading %} ## Overview This guide walks you through a complete transfer order workflow using the Transfer Orders API. The example uses a fictional running shoe retailer with stores in Tacoma, WA and Olympia, WA, as they transfer inventory between locations. The example shows how to: * Create a draft transfer order. * Add or remove shoe variations from the draft. * Delete a draft if plans change. * Start the transfer when shoes leave Tacoma for Olympia. * Cancel an in-progress transfer if needed. * Receive the shipment at the Olympia store to complete the transfer. Each step includes real API requests and responses, demonstrating how the Transfer Orders API works with the Inventory API to track item movement, update stock states (from `IN_STOCK` to `IN_TRANSIT` to `IN_STOCK`), and maintain accurate inventory counts at both locations throughout the transfer lifecycle. For more details about the transfer order lifecycle, see [How transfer orders work](transfer-orders-api#how-transfer-orders-work). ## Create a draft order The Olympia store manager at Fleet Foot Running Co. needs to restock the popular New Balance FuelCell racing flat in multiple sizes. After checking the Square Dashboard, they find that the Tacoma store has sufficient stock to transfer half their inventory without creating shortages. The Olympia store manager asks the Tacoma store manager to create a draft transfer order. Keeping it in draft status allows time to review other inventory needs and potentially add more items to optimize the shipment. ### Get shoe catalog IDs Before creating the transfer order, the Tacoma store manager runs a [SearchCatalogItems](https://developer.squareup.com/reference/square/catalog-api/search-catalog-items) query to get the variation IDs for the New Balance FuelCell shoe variations in all sizes. {% tabset %} {% tab id="Request" %} ```` {% /tab %} {% tab id="Response" %} All sizes of the FuelCell shoe are returned in the response with an item variation per size. ```json { "items": [ { "type": "ITEM", "id": "M2GW24SH2CI65FSRAN3RX4YP", "updated_at": "2025-06-12T01:11:46.24Z", "created_at": "2020-02-05T18:42:15.88Z", "version": 1749690706240, "is_deleted": false, "present_at_all_locations": false, "present_at_location_ids": [ "90A9W5RRYD2GQ", "EWVV7AYQC45SS" ], "item_data": { "name": "New Balance FuelCell", "description": "Lightweight racing flat designed for speed. Features FuelCell foam technology for maximum energy return and a carbon fiber plate for propulsion. Perfect for race day and fast training runs.", "is_taxable": true, "tax_ids": [ "MD66EEZEZPG7JJTB7IXUT2DO" ], "variations": [ { "type": "ITEM_VARIATION", "id": "C4N5RAZPHMHUMW72DB5K73O5", "updated_at": "2025-06-12T01:11:46.24Z", "created_at": "2020-02-05T18:42:15.88Z", "version": 1749690706240, "is_deleted": false, "present_at_all_locations": false, "present_at_location_ids": [ "90A9W5RRYD2GQ", "EWVV7AYQC45SS" ], "item_variation_data": { "item_id": "M2GW24SH2CI65FSRAN3RX4YP", "name": "Size 7", "sku": "NBFC-7", "ordinal": 0, "pricing_type": "FIXED_PRICING", "price_money": { "amount": 18000, "currency": "USD" }, "location_overrides": [ { "location_id": "90A9W5RRYD2GQ", "track_inventory": true, "inventory_alert_type": "LOW_QUANTITY", "inventory_alert_threshold": 2 }, { "location_id": "EWVV7AYQC45SS", "track_inventory": true, "inventory_alert_type": "LOW_QUANTITY", "inventory_alert_threshold": 2 } ], "sellable": true, "stockable": true, "default_unit_cost": { "amount": 9000, "currency": "USD" }, "channels": [ "CH_YaVkJT09dd6NHfrvsF16agtdnoQ9JrkS4gEd5PlQuYC", "CH_jL8ic7qvKWTHgpJOEKJghCeMP62C4VVP4pXfvJPd9945o" ] } }, { "type": "ITEM_VARIATION", "id": "PLACNBU425UEWUYSCZ6DGK2N", "updated_at": "2025-06-12T01:11:46.24Z", "created_at": "2020-02-05T18:42:15.88Z", "version": 1749690706240, "is_deleted": false, "present_at_all_locations": false, "present_at_location_ids": [ "90A9W5RRYD2GQ", "EWVV7AYQC45SS" ], "item_variation_data": { "item_id": "M2GW24SH2CI65FSRAN3RX4YP", "name": "Size 7.5", "sku": "NBFC-7.5", "ordinal": 1, "pricing_type": "FIXED_PRICING", "price_money": { "amount": 18000, "currency": "USD" }, "location_overrides": [ { "location_id": "90A9W5RRYD2GQ", "track_inventory": true, "inventory_alert_type": "LOW_QUANTITY", "inventory_alert_threshold": 2 }, { "location_id": "EWVV7AYQC45SS", "track_inventory": true, "inventory_alert_type": "LOW_QUANTITY", "inventory_alert_threshold": 2 } ], "sellable": true, "stockable": true, "default_unit_cost": { "amount": 9000, "currency": "USD" }, "channels": [ "CH_YaVkJT09dd6NHfrvsF16agtdnoQ9JrkS4gEd5PlQuYC", "CH_jL8ic7qvKWTHgpJOEKJghCeMP62C4VVP4pXfvJPd9945o" ] } }, { "type": "ITEM_VARIATION", "id": "CGWLR42RT7WDRI52CZO6SQO3", "updated_at": "2025-06-12T01:11:46.24Z", "created_at": "2020-02-05T18:42:15.88Z", "version": 1749690706240, "is_deleted": false, "present_at_all_locations": false, "present_at_location_ids": [ "90A9W5RRYD2GQ", "EWVV7AYQC45SS" ], "item_variation_data": { "item_id": "M2GW24SH2CI65FSRAN3RX4YP", "name": "Size 8", "sku": "NBFC-8", "ordinal": 2, "pricing_type": "FIXED_PRICING", "price_money": { "amount": 18000, "currency": "USD" }, "location_overrides": [ { "location_id": "90A9W5RRYD2GQ", "track_inventory": true, "inventory_alert_type": "LOW_QUANTITY", "inventory_alert_threshold": 3 }, { "location_id": "EWVV7AYQC45SS", "track_inventory": true, "inventory_alert_type": "LOW_QUANTITY", "inventory_alert_threshold": 3 } ], "sellable": true, "stockable": true, "default_unit_cost": { "amount": 9000, "currency": "USD" }, "channels": [ "CH_YaVkJT09dd6NHfrvsF16agtdnoQ9JrkS4gEd5PlQuYC", "CH_jL8ic7qvKWTHgpJOEKJghCeMP62C4VVP4pXfvJPd9945o" ] } }, { "type": "ITEM_VARIATION", "id": "VWHK2HSJJKFM6RE2ETWZYUMD", "updated_at": "2025-06-12T01:11:46.24Z", "created_at": "2020-02-05T18:42:15.88Z", "version": 1749690706240, "is_deleted": false, "present_at_all_locations": false, "present_at_location_ids": [ "90A9W5RRYD2GQ", "EWVV7AYQC45SS" ], "item_variation_data": { "item_id": "M2GW24SH2CI65FSRAN3RX4YP", "name": "Size 8.5", "sku": "NBFC-8.5", "ordinal": 3, "pricing_type": "FIXED_PRICING", "price_money": { "amount": 18000, "currency": "USD" }, "location_overrides": [ { "location_id": "90A9W5RRYD2GQ", "track_inventory": true, "inventory_alert_type": "LOW_QUANTITY", "inventory_alert_threshold": 3 }, { "location_id": "EWVV7AYQC45SS", "track_inventory": true, "inventory_alert_type": "LOW_QUANTITY", "inventory_alert_threshold": 3 } ], "sellable": true, "stockable": true, "default_unit_cost": { "amount": 9000, "currency": "USD" }, "channels": [ "CH_YaVkJT09dd6NHfrvsF16agtdnoQ9JrkS4gEd5PlQuYC", "CH_jL8ic7qvKWTHgpJOEKJghCeMP62C4VVP4pXfvJPd9945o" ] } }, { "type": "ITEM_VARIATION", "id": "XPBDUOG3VQBRASADVRSOYS67", "updated_at": "2025-06-12T01:11:46.24Z", "created_at": "2020-02-05T18:42:15.88Z", "version": 1749690706240, "is_deleted": false, "present_at_all_locations": false, "present_at_location_ids": [ "90A9W5RRYD2GQ", "EWVV7AYQC45SS" ], "item_variation_data": { "item_id": "M2GW24SH2CI65FSRAN3RX4YP", "name": "Size 9", "sku": "NBFC-9", "ordinal": 4, "pricing_type": "FIXED_PRICING", "price_money": { "amount": 18000, "currency": "USD" }, "location_overrides": [ { "location_id": "90A9W5RRYD2GQ", "track_inventory": true, "inventory_alert_type": "LOW_QUANTITY", "inventory_alert_threshold": 4 }, { "location_id": "EWVV7AYQC45SS", "track_inventory": true, "inventory_alert_type": "LOW_QUANTITY", "inventory_alert_threshold": 4 } ], "sellable": true, "stockable": true, "default_unit_cost": { "amount": 9000, "currency": "USD" }, "channels": [ "CH_YaVkJT09dd6NHfrvsF16agtdnoQ9JrkS4gEd5PlQuYC", "CH_jL8ic7qvKWTHgpJOEKJghCeMP62C4VVP4pXfvJPd9945o" ] } }, { "type": "ITEM_VARIATION", "id": "R6C5CP6JXBZMA22FSXYVUC5W", "updated_at": "2025-06-12T01:11:46.24Z", "created_at": "2020-02-05T18:42:15.88Z", "version": 1749690706240, "is_deleted": false, "present_at_all_locations": false, "present_at_location_ids": [ "90A9W5RRYD2GQ", "EWVV7AYQC45SS" ], "item_variation_data": { "item_id": "M2GW24SH2CI65FSRAN3RX4YP", "name": "Size 9.5", "sku": "NBFC-9.5", "ordinal": 5, "pricing_type": "FIXED_PRICING", "price_money": { "amount": 18000, "currency": "USD" }, "location_overrides": [ { "location_id": "90A9W5RRYD2GQ", "track_inventory": true, "inventory_alert_type": "LOW_QUANTITY", "inventory_alert_threshold": 4 }, { "location_id": "EWVV7AYQC45SS", "track_inventory": true, "inventory_alert_type": "LOW_QUANTITY", "inventory_alert_threshold": 4 } ], "sellable": true, "stockable": true, "default_unit_cost": { "amount": 9000, "currency": "USD" }, "channels": [ "CH_YaVkJT09dd6NHfrvsF16agtdnoQ9JrkS4gEd5PlQuYC", "CH_jL8ic7qvKWTHgpJOEKJghCeMP62C4VVP4pXfvJPd9945o" ] } }, { "type": "ITEM_VARIATION", "id": "J4H4PL3UGRAWCUDW3JS73LT6", "updated_at": "2025-06-12T01:11:46.24Z", "created_at": "2020-02-05T18:42:15.88Z", "version": 1749690706240, "is_deleted": false, "present_at_all_locations": false, "present_at_location_ids": [ "90A9W5RRYD2GQ", "EWVV7AYQC45SS" ], "item_variation_data": { "item_id": "M2GW24SH2CI65FSRAN3RX4YP", "name": "Size 10", "sku": "NBFC-10", "ordinal": 6, "pricing_type": "FIXED_PRICING", "price_money": { "amount": 18000, "currency": "USD" }, "location_overrides": [ { "location_id": "90A9W5RRYD2GQ", "track_inventory": true, "inventory_alert_type": "LOW_QUANTITY", "inventory_alert_threshold": 5 }, { "location_id": "EWVV7AYQC45SS", "track_inventory": true, "inventory_alert_type": "LOW_QUANTITY", "inventory_alert_threshold": 5 } ], "sellable": true, "stockable": true, "default_unit_cost": { "amount": 9000, "currency": "USD" }, "channels": [ "CH_YaVkJT09dd6NHfrvsF16agtdnoQ9JrkS4gEd5PlQuYC", "CH_jL8ic7qvKWTHgpJOEKJghCeMP62C4VVP4pXfvJPd9945o" ] } }, { "type": "ITEM_VARIATION", "id": "GTPCF6YEFC6PJ2XCU2SVIAFY", "updated_at": "2025-06-12T01:11:46.24Z", "created_at": "2020-02-05T18:42:15.88Z", "version": 1749690706240, "is_deleted": false, "present_at_all_locations": false, "present_at_location_ids": [ "90A9W5RRYD2GQ", "EWVV7AYQC45SS" ], "item_variation_data": { "item_id": "M2GW24SH2CI65FSRAN3RX4YP", "name": "Size 10.5", "sku": "NBFC-10.5", "ordinal": 7, "pricing_type": "FIXED_PRICING", "price_money": { "amount": 18000, "currency": "USD" }, "location_overrides": [ { "location_id": "90A9W5RRYD2GQ", "track_inventory": true, "inventory_alert_type": "LOW_QUANTITY", "inventory_alert_threshold": 3 }, { "location_id": "EWVV7AYQC45SS", "track_inventory": true, "inventory_alert_type": "LOW_QUANTITY", "inventory_alert_threshold": 3 } ], "sellable": true, "stockable": true, "default_unit_cost": { "amount": 9000, "currency": "USD" }, "channels": [ "CH_YaVkJT09dd6NHfrvsF16agtdnoQ9JrkS4gEd5PlQuYC", "CH_jL8ic7qvKWTHgpJOEKJghCeMP62C4VVP4pXfvJPd9945o" ] } }, { "type": "ITEM_VARIATION", "id": "XVM5FR3JS4RFBXNCX6MHC76B", "updated_at": "2025-06-12T01:11:46.24Z", "created_at": "2020-02-05T18:42:15.88Z", "version": 1749690706240, "is_deleted": false, "present_at_all_locations": false, "present_at_location_ids": [ "90A9W5RRYD2GQ", "EWVV7AYQC45SS" ], "item_variation_data": { "item_id": "M2GW24SH2CI65FSRAN3RX4YP", "name": "Size 11", "sku": "NBFC-11", "ordinal": 8, "pricing_type": "FIXED_PRICING", "price_money": { "amount": 18000, "currency": "USD" }, "location_overrides": [ { "location_id": "90A9W5RRYD2GQ", "track_inventory": true, "inventory_alert_type": "LOW_QUANTITY", "inventory_alert_threshold": 3 }, { "location_id": "EWVV7AYQC45SS", "track_inventory": true, "inventory_alert_type": "LOW_QUANTITY", "inventory_alert_threshold": 3 } ], "sellable": true, "stockable": true, "default_unit_cost": { "amount": 9000, "currency": "USD" }, "channels": [ "CH_YaVkJT09dd6NHfrvsF16agtdnoQ9JrkS4gEd5PlQuYC", "CH_jL8ic7qvKWTHgpJOEKJghCeMP62C4VVP4pXfvJPd9945o" ] } }, { "type": "ITEM_VARIATION", "id": "TSIV2RS64IS26LUZA3HIL6TX", "updated_at": "2025-06-12T01:11:46.24Z", "created_at": "2020-02-05T18:42:15.88Z", "version": 1749690706240, "is_deleted": false, "present_at_all_locations": false, "present_at_location_ids": [ "90A9W5RRYD2GQ", "EWVV7AYQC45SS" ], "item_variation_data": { "item_id": "M2GW24SH2CI65FSRAN3RX4YP", "name": "Size 11.5", "sku": "NBFC-11.5", "ordinal": 9, "pricing_type": "FIXED_PRICING", "price_money": { "amount": 18000, "currency": "USD" }, "location_overrides": [ { "location_id": "90A9W5RRYD2GQ", "track_inventory": true, "inventory_alert_type": "LOW_QUANTITY", "inventory_alert_threshold": 2 }, { "location_id": "EWVV7AYQC45SS", "track_inventory": true, "inventory_alert_type": "LOW_QUANTITY", "inventory_alert_threshold": 2 } ], "sellable": true, "stockable": true, "default_unit_cost": { "amount": 9000, "currency": "USD" }, "channels": [ "CH_YaVkJT09dd6NHfrvsF16agtdnoQ9JrkS4gEd5PlQuYC", "CH_jL8ic7qvKWTHgpJOEKJghCeMP62C4VVP4pXfvJPd9945o" ] } }, { "type": "ITEM_VARIATION", "id": "ILBMJ7XFSMBAOVDU5FZIDBKO", "updated_at": "2025-06-12T01:11:46.24Z", "created_at": "2020-02-05T18:42:15.88Z", "version": 1749690706240, "is_deleted": false, "present_at_all_locations": false, "present_at_location_ids": [ "90A9W5RRYD2GQ", "EWVV7AYQC45SS" ], "item_variation_data": { "item_id": "M2GW24SH2CI65FSRAN3RX4YP", "name": "Size 12", "sku": "NBFC-12", "ordinal": 10, "pricing_type": "FIXED_PRICING", "price_money": { "amount": 18000, "currency": "USD" }, "location_overrides": [ { "location_id": "90A9W5RRYD2GQ", "track_inventory": true, "inventory_alert_type": "LOW_QUANTITY", "inventory_alert_threshold": 2 }, { "location_id": "EWVV7AYQC45SS", "track_inventory": true, "inventory_alert_type": "LOW_QUANTITY", "inventory_alert_threshold": 2 } ], "sellable": true, "stockable": true, "default_unit_cost": { "amount": 9000, "currency": "USD" }, "channels": [ "CH_YaVkJT09dd6NHfrvsF16agtdnoQ9JrkS4gEd5PlQuYC", "CH_jL8ic7qvKWTHgpJOEKJghCeMP62C4VVP4pXfvJPd9945o" ] } }, { "type": "ITEM_VARIATION", "id": "KWTYJITTTYSE56DMH4ZXPGDF", "updated_at": "2025-06-12T01:11:46.24Z", "created_at": "2020-02-05T18:42:15.88Z", "version": 1749690706240, "is_deleted": false, "present_at_all_locations": false, "present_at_location_ids": [ "90A9W5RRYD2GQ", "EWVV7AYQC45SS" ], "item_variation_data": { "item_id": "M2GW24SH2CI65FSRAN3RX4YP", "name": "Size 13", "sku": "NBFC-13", "ordinal": 11, "pricing_type": "FIXED_PRICING", "price_money": { "amount": 18000, "currency": "USD" }, "location_overrides": [ { "location_id": "90A9W5RRYD2GQ", "track_inventory": true, "inventory_alert_type": "LOW_QUANTITY", "inventory_alert_threshold": 1 }, { "location_id": "EWVV7AYQC45SS", "track_inventory": true, "inventory_alert_type": "LOW_QUANTITY", "inventory_alert_threshold": 1 } ], "sellable": true, "stockable": true, "default_unit_cost": { "amount": 9000, "currency": "USD" }, "channels": [ "CH_YaVkJT09dd6NHfrvsF16agtdnoQ9JrkS4gEd5PlQuYC", "CH_jL8ic7qvKWTHgpJOEKJghCeMP62C4VVP4pXfvJPd9945o" ] } } ], "product_type": "REGULAR", "skip_modifier_screen": false, "ecom_uri": "https://fleetfoot.square.site/product/new-balance-fuelcell", "ecom_available": true, "ecom_visibility": "VISIBLE", "categories": [ { "id": "RFECWJWLWUGARTRQ6ZXY4DG7", "ordinal": -2251799813685248 } ], "description_html": "
Lightweight racing flat designed for speed. Features FuelCell foam technology for maximum energy return and a carbon fiber plate for propulsion. Perfect for race day and fast training runs.
", "description_plaintext": "Lightweight racing flat designed for speed. Features FuelCell foam technology for maximum energy return and a carbon fiber plate for propulsion. Perfect for race day and fast training runs.", "channels": [ "CH_YaVkJT09dd6NHfrvsF16agtdnoQ9JrkS4gEd5PlQuYC", "CH_jL8ic7qvKWTHgpJOEKJghCeMP62C4VVP4pXfvJPd9945o" ], "is_archived": false, "requires_subscription_to_sell": false, "reporting_category": { "id": "RFECWJWLWUGARTRQ6ZXY4DG7", "ordinal": -2251799813685248 }, "is_alcoholic": false } } ], "cursor": "", "matched_variation_ids": [ "C4N5RAZPHMHUMW72DB5K73O5", "PLACNBU425UEWUYSCZ6DGK2N", "CGWLR42RT7WDRI52CZO6SQO3", "VWHK2HSJJKFM6RE2ETWZYUMD", "XPBDUOG3VQBRASADVRSOYS67", "R6C5CP6JXBZMA22FSXYVUC5W", "J4H4PL3UGRAWCUDW3JS73LT6", "GTPCF6YEFC6PJ2XCU2SVIAFY", "XVM5FR3JS4RFBXNCX6MHC76B", "TSIV2RS64IS26LUZA3HIL6TX", "ILBMJ7XFSMBAOVDU5FZIDBKO", "KWTYJITTTYSE56DMH4ZXPGDF" ] } ``` {% /tab %} {% /tabset %} ### Create the draft transfer order The Tacoma store manager uses your application to call [CreateTransferOrder](https://developer.squareup.com/reference/square/transfer-order-api/create-transfer-order) to create a draft transfer order for pairs of FuelCell shoes in sizes 8, 8.5, 9, and 9.5. {% tabset %} {% tab id="Request" %} ```` {% /tab %} {% tab id="Response" %} ```json { "transfer_order": { "id": "EXAMPLE_TRANSFER_ORDER_ID_123", "source_location_id": "TACOMA_STORE_LOCATION_ID", "destination_location_id": "OLYMPIA_STORE_LOCATION_ID", "status": "DRAFT", "created_at": "2025-01-15T10:30:00Z", "updated_at": "2025-01-15T10:30:00Z", "expected_at": "2025-11-09T05:00:00Z", "notes": "Example transfer order for men's New Balance FuelCells to Olympia", "tracking_number": "TRACK123456789", "created_by_team_member_id": "EXAMPLE_TEAM_MEMBER_ID_789", "line_items": [ { "transfer_order_line_uid": "1", "item_variation_id": "CGWLR42RT7WDRI52CZO6SQO3", "quantity_ordered": "1", "quantity_pending": "1", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "0" }, { "transfer_order_line_uid": "2", "item_variation_id": "VWHK2HSJJKFM6RE2ETWZYUMD", "quantity_ordered": "1", "quantity_pending": "1", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "0" }, { "transfer_order_line_uid": "3", "item_variation_id": "XPBDUOG3VQBRASADVRSOYS67", "quantity_ordered": "1", "quantity_pending": "1", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "0" }, { "transfer_order_line_uid": "4", "item_variation_id": "R6C5CP6JXBZMA22FSXYVUC5W", "quantity_ordered": "1", "quantity_pending": "1", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "0" } ], "version": 1753109537351 } } ``` {% /tab %} {% /tabset %} ## Update a draft transfer order The Olympia store manager gets a call from the local high school track coach asking for women's racing flats for their 5,000 meter runners. There isn't enough stock in the needed sizes so the manager adds the women's FuelCell shoes to the draft transfer order. Your application calls the [UpdateTransferOrder](https://developer.squareup.com/reference/square/transfer-order-api/update-transfer-order) endpoint. The following example makes the request with the ID of the transfer order to update, its current version number, and the new line item to add: {% tabset %} {% tab id="Request" %} ```` {% /tab %} {% tab id="Response" %} The updated transfer order has a new line item for the women's shoe in size 9 and an incremented version number. ```json { "transfer_order": { "id": "EXAMPLE_TRANSFER_ORDER_ID_123", "source_location_id": "TACOMA_STORE_LOCATION_ID", "destination_location_id": "OLYMPIA_STORE_LOCATION_ID", "status": "DRAFT", "created_at": "2025-01-15T10:30:00Z", "updated_at": "2025-01-15T10:30:00Z", "expected_at": "2025-11-09T05:00:00Z", "notes": "Example transfer order for men's New Balance FuelCells to Olympia", "tracking_number": "TRACK123456789", "created_by_team_member_id": "EXAMPLE_TEAM_MEMBER_ID_789", "line_items": [ { "transfer_order_line_uid": "1", "item_variation_id": "CGWLR42RT7WDRI52CZO6SQO3", "quantity_ordered": "1", "quantity_pending": "1", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "0" }, { "transfer_order_line_uid": "2", "item_variation_id": "VWHK2HSJJKFM6RE2ETWZYUMD", "quantity_ordered": "1", "quantity_pending": "1", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "0" }, { "transfer_order_line_uid": "3", "item_variation_id": "XPBDUOG3VQBRASADVRSOYS67", "quantity_ordered": "1", "quantity_pending": "1", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "0" }, { "transfer_order_line_uid": "4", "item_variation_id": "R6C5CP6JXBZMA22FSXYVUC5W", "quantity_ordered": "1", "quantity_pending": "1", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "0" }, { "transfer_order_line_uid": "5", "item_variation_id": "M2GW24SH2CI65FSRAN3RX4YP", "quantity_ordered": "2", "quantity_pending": "2", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "0" } ], "version": 17531095353343 } } ``` {% /tab %} {% /tabset %} ## Delete a draft transfer order If the Olympia store manager decides to cancel their request for the shoe transfer, your application uses the following [DeleteTransferOrder](https://developer.squareup.com/reference/square/transfer-order-api/delete-transfer-order) endpoint call to delete the transfer order: {% tabset %} {% tab id="Request" %} ```` {% /tab %} {% tab id="Response" %} When the order is deleted, the response shows an empty object. The transfer order is deleted as well. ```json {} ``` {% /tab %} {% /tabset %} ## Start a transfer Now that the Olympia store manager is satisfied with the list of shoes to be transferred, they let the Tacoma store know that there are no additional shoes to be transferred. The Tacoma store manager starts the transfer order using the [StartTransferOrder](https://developer.squareup.com/reference/square/transfer-order-api/start-transfer-order) endpoint, prints a physical copy of the transfer using your application, and asks their shipping department to send the shoes to Olympia. {% tabset %} {% tab id="Request" %} ```` {% /tab %} {% tab id="Response" %} The response contains the newly started order. The `status` is now `STARTED` and the version number is incremented. ```json { "transfer_order": { "id": "EXAMPLE_TRANSFER_ORDER_ID_123", "source_location_id": "TACOMA_STORE_LOCATION_ID", "destination_location_id": "OLYMPIA_STORE_LOCATION_ID", "status": "STARTED", "created_at": "2025-01-15T10:30:00Z", "updated_at": "2025-01-15T10:30:00Z", "expected_at": "2025-11-09T05:00:00Z", "notes": "Example transfer order for men's New Balance FuelCells to Olympia", "tracking_number": "TRACK123456789", "created_by_team_member_id": "EXAMPLE_TEAM_MEMBER_ID_789", "line_items": [ { "transfer_order_line_uid": "1", "item_variation_id": "CGWLR42RT7WDRI52CZO6SQO3", "quantity_ordered": "1", "quantity_pending": "1", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "0" }, { "transfer_order_line_uid": "2", "item_variation_id": "VWHK2HSJJKFM6RE2ETWZYUMD", "quantity_ordered": "1", "quantity_pending": "1", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "0" }, { "transfer_order_line_uid": "3", "item_variation_id": "XPBDUOG3VQBRASADVRSOYS67", "quantity_ordered": "1", "quantity_pending": "1", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "0" }, { "transfer_order_line_uid": "4", "item_variation_id": "R6C5CP6JXBZMA22FSXYVUC5W", "quantity_ordered": "1", "quantity_pending": "1", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "0" }, { "transfer_order_line_uid": "5", "item_variation_id": "M2GW24SH2CI65FSRAN3RX4YP", "quantity_ordered": "2", "quantity_pending": "2", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "0" } ], "version": 19331095353343 } } ``` {% /tab %} {% /tabset %} Now that the transfer order has started, it can no longer be deleted. Instead, the order can be canceled and any shoes that haven't been received by the Olympia store are returned to inventory in Tacoma. ## Cancel a transfer order Unlike a draft transfer order, which can be deleted, a transfer order that is started and potentially even partially received can be only be canceled or completed. When canceled, any non-received items are returned to the source location's inventory. Any already received items remain at the receiving location. Call the [CancelTransferOrder](https://developer.squareup.com/reference/square/transfer-order-api/cancel-transfer-order) endpoint to cancel the transfer order. {% tabset %} {% tab id="Request" %} ```` {% /tab %} {% tab id="Response" %} The canceled order is returned with the `status` of `CANCELED` and an incremented version number. ```json { "transfer_order": { "id": "EXAMPLE_TRANSFER_ORDER_ID_123", "source_location_id": "TACOMA_STORE_LOCATION_ID", "destination_location_id": "OLYMPIA_STORE_LOCATION_ID", "status": "CANCELED", "created_at": "2025-01-15T10:30:00Z", "updated_at": "2025-01-15T10:30:00Z", "expected_at": "2025-11-09T05:00:00Z", "notes": "Example transfer order for men's New Balance FuelCells to Olympia", "tracking_number": "TRACK123456789", "created_by_team_member_id": "EXAMPLE_TEAM_MEMBER_ID_789", "line_items": [ { "transfer_order_line_uid": "1", "item_variation_id": "CGWLR42RT7WDRI52CZO6SQO3", "quantity_ordered": "1", "quantity_pending": "0", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "1" }, { "transfer_order_line_uid": "2", "item_variation_id": "VWHK2HSJJKFM6RE2ETWZYUMD", "quantity_ordered": "1", "quantity_pending": "0", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "1" }, { "transfer_order_line_uid": "3", "item_variation_id": "XPBDUOG3VQBRASADVRSOYS67", "quantity_ordered": "1", "quantity_pending": "0", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "1" }, { "transfer_order_line_uid": "4", "item_variation_id": "R6C5CP6JXBZMA22FSXYVUC5W", "quantity_ordered": "1", "quantity_pending": "0", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "1" }, { "transfer_order_line_uid": "5", "item_variation_id": "M2GW24SH2CI65FSRAN3RX4YP", "quantity_ordered": "2", "quantity_pending": "0", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "2" } ], "version": 19331095353343 } } ``` {% /tab %} {% /tabset %} ## Receive goods At the Olympia store, the manager opens your application to acknowledge the receipt of all pairs of shoes except for the women's shoes that Tacoma didn't send. The Olympia store manager decided to cancel the transfer of the two pairs of women's shoes. The call to the [ReceiveTransferOrder](https://developer.squareup.com/reference/square/transfer-order-api/receive-transfer-order) endpoint includes a list of [TransferOrderGoodsReceiptLineItem](https://developer.squareup.com/reference/square/objects/TransferOrderGoodsReceiptLineItem) objects, one for each item variation in the transfer order. You put the actual count of items received, damaged in transit, or canceled in these objects. {% tabset %} {% tab id="Request" %} This receipt request references the `uid` values from the transfer order line items to be received. ```` {% /tab %} {% tab id="Response" %} The completed transfer order is returned. The new `status` is `COMPLETED`, the version is incremented, and the quantity status for each line item is updated to show that no line items are pending, four line items are received, and one line item is canceled. ```json { "transfer_order": { "id": "EXAMPLE_TRANSFER_ORDER_ID_123", "source_location_id": "TACOMA_STORE_LOCATION_ID", "destination_location_id": "OLYMPIA_STORE_LOCATION_ID", "status": "COMPLETED", "created_at": "2025-01-15T10:30:00Z", "updated_at": "2025-01-15T10:30:00Z", "expected_at": "2025-11-09T05:00:00Z", "notes": "Example transfer order for men's New Balance FuelCells to Olympia", "tracking_number": "TRACK123456789", "created_by_team_member_id": "EXAMPLE_TEAM_MEMBER_ID_789", "line_items": [ { "transfer_order_line_uid": "1", "item_variation_id": "CGWLR42RT7WDRI52CZO6SQO3", "quantity_ordered": "1", "quantity_pending": "0", "quantity_received": "1", "quantity_damaged": "0", "quantity_canceled": "0" }, { "transfer_order_line_uid": "2", "item_variation_id": "VWHK2HSJJKFM6RE2ETWZYUMD", "quantity_ordered": "1", "quantity_pending": "0", "quantity_received": "1", "quantity_damaged": "0", "quantity_canceled": "0" }, { "transfer_order_line_uid": "3", "item_variation_id": "XPBDUOG3VQBRASADVRSOYS67", "quantity_ordered": "1", "quantity_pending": "0", "quantity_received": "1", "quantity_damaged": "0", "quantity_canceled": "0" }, { "transfer_order_line_uid": "4", "item_variation_id": "R6C5CP6JXBZMA22FSXYVUC5W", "quantity_ordered": "1", "quantity_pending": "0", "quantity_received": "1", "quantity_damaged": "0", "quantity_canceled": "0" }, { "transfer_order_line_uid": "5", "item_variation_id": "M2GW24SH2CI65FSRAN3RX4YP", "quantity_ordered": "2", "quantity_pending": "0", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "2" } ], "version": 21331095353343 } } ``` {% /tab %} {% /tabset %} ## Get the transfer order status {% tabset %} {% tab id="Request" %} ```` {% /tab %} {% tab id="Response" %} ```json { "transfer_order": { "id": "EXAMPLE_TRANSFER_ORDER_ID_123", "source_location_id": "TACOMA_STORE_LOCATION_ID", "destination_location_id": "OLYMPIA_STORE_LOCATION_ID", "status": "STARTED", "created_at": "2025-01-15T10:30:00Z", "updated_at": "2025-01-15T10:30:00Z", "expected_at": "2025-11-09T05:00:00Z", "notes": "Example transfer order for men's New Balance FuelCells to Olympia", "tracking_number": "TRACK123456789", "created_by_team_member_id": "EXAMPLE_TEAM_MEMBER_ID_789", "line_items": [ { "transfer_order_line_uid": "1", "item_variation_id": "CGWLR42RT7WDRI52CZO6SQO3", "quantity_ordered": "1", "quantity_pending": "1", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "0" }, { "transfer_order_line_uid": "2", "item_variation_id": "VWHK2HSJJKFM6RE2ETWZYUMD", "quantity_ordered": "1", "quantity_pending": "1", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "0" }, { "transfer_order_line_uid": "3", "item_variation_id": "XPBDUOG3VQBRASADVRSOYS67", "quantity_ordered": "1", "quantity_pending": "1", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "0" }, { "transfer_order_line_uid": "4", "item_variation_id": "R6C5CP6JXBZMA22FSXYVUC5W", "quantity_ordered": "1", "quantity_pending": "1", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "0" }, { "transfer_order_line_uid": "5", "item_variation_id": "M2GW24SH2CI65FSRAN3RX4YP", "quantity_ordered": "2", "quantity_pending": "2", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "0" } ], "version": 19331095353343 } } ``` {% /tab %} {% /tabset %} ## Confirm the current stock levels at each location The managers of the Tacoma and Olympia stores might want to review the current stock levels of all FuelCell sizes after the transfer is complete. The Transfer Orders API only reports on the quantity of items being transferred and not the new stock levels at either location after the transfer. To learn about reporting on inventory stock levels after a transfer, see [Inventory Management Reporting](transfer-orders-api/inventory-management-reporting). ## See also * [Transfer Orders API](transfer-orders-api) * [Inventory Management Reporting](transfer-orders-api/inventory-management-reporting) * [Transfer Orders Webhooks](transfer-orders-api/webhooks) --- # Inventory Management Reporting > Source: https://developer.squareup.com/docs/transfer-orders-api/inventory-management-reporting > Status: BETA > Languages: All > Platforms: All Learn how to manage inventory transferred between a Square seller's locations. **Applies to:** [Transfer Orders API](https://developer.squareup.com/reference/square/transfer-order-api) | [Inventory API](inventory-api/what-it-does) | [Catalog API](catalog-api/what-it-does) | [Locations API](locations-api) | [Team API](team/overview) {% subheading %}Learn how to manage inventory transferred between a Square seller's locations.{% /subheading %} ## Overview As you build inventory management features for multi-location retailers like Fleet Foot Running Co., it's important to provide detailed reporting for transfer orders that extends beyond basic status tracking. Your application should account for real-world scenarios where the quantities received differ from the quantities ordered—such as when shoe boxes are damaged in transit, pairs go missing, or size counts are incorrect at either store. Square provides two complementary APIs to help you build robust transfer reporting: - The [Transfer Orders API](transfer-orders-api) gives you detailed transfer lifecycle data (what was ordered, received, damaged, or lost). For more information, see [Get the transfer order status](#get-the-transfer-order-status). - The [Inventory API](inventory-api/what-it-does) provides real-time stock positions at each location. This guide shows you how to leverage both APIs to build reports that answer your sellers' critical questions: - What's the status of each shoe size in my transfer? - How many pairs do I have at each store after the transfer completes? - Are there any size discrepancies I need to investigate? For more information, see [Get inventory adjustments, transfers, and counts](#get-inventory-adjustments-transfers-and-counts). ## Inventory management report strategies When Fleet Foot Running Co. completes a transfer order between their Tacoma and Olympia stores, the application needs visibility into two critical aspects: * **Transfer status details** - The size-by-size quantities by state (ordered, received, damaged, and lost). * **Location inventory balances** - The current stock levels at both the Tacoma and Olympia stores. ### Transfer Orders API (transfer status details) Use the Transfer Orders API when you need comprehensive transfer details: **What it provides:** - Quantities ordered, pending, received, damaged, or lost for each shoe size. - Transfer order summaries (dates, store locations, and status). - Line-item level granularity for each size variation. **Best for:** Audit trails, transfer reconciliation, and exception reporting ### Inventory API (current stock levels) Use the Inventory API to determine post-transfer inventory positions: **What it provides:** - Current available quantities at each store location. - Real-time stock levels after all adjustments. - Consolidated view across all inventory movements. **Best for:** Stock availability, replenishment decisions, and store balancing ### Recommended reporting strategy For complete transfer visibility at Fleet Foot Running Co., combine both approaches: 1. **During transfer** - Use the Transfer Orders API to track in-transit shoes and monitor transfer progress between stores. 2. **Post-transfer** - Use the Inventory API to confirm final stock positions at both Tacoma and Olympia locations. 3. **Reconciliation** - Compare transfer order quantities with inventory changes to identify any missing or damaged pairs. This dual-API approach ensures that you can answer both "What happened during the transfer?" and "What's our current inventory position?". These are critical questions for effective retail inventory management. ## Get the transfer order status You can call the [SearchTransferOrders](https://developer.squareup.com/reference/square/transfer-order-api/search-transfer-orders) endpoint, which returns an array of transfer order summaries (lightweight objects containing essential transfer metadata without line-item details). To retrieve the complete transfer order including line items for each shoe size, pass the summary's ID to the [RetrieveTransferOrder](https://developer.squareup.com/reference/square/transfer-order-api/retrieve-transfer-order) endpoint. {% tabset %} {% tab id="Request" %} The following query example returns any transfer order between Fleet Foot Running Co. stores that isn't in a draft state and is sorted by the date the order is created: ```` {% /tab %} {% tab id="Response" %} The returned objects don't include transferred line items (shoe sizes and quantities), so you need to call the [RetrieveTransferOrder](https://developer.squareup.com/reference/square/transfer-order-api/retrieve-transfer-order) endpoint for each transfer order as shown in this example: ```json { "transfer_orders": [ { "id": "VOE53GHMAKZSRJH4", "source_location_id": "EWVV7AYQC45SS", "destination_location_id": "90A9W5RRYD2GQ", "status": "COMPLETED", "created_at": "2025-10-07T21:53:35.528Z", "updated_at": "2025-10-07T21:54:08.278Z", "expected_at": "2025-10-07T21:52:00Z", "completed_at": "2025-10-07T21:54:08.278Z", "tracking_number": "FLEET-001", "created_by_team_member_id": "bxrMsKrMAMzWgYGGo8BL", "version": 1759874048278 }, { "id": "4TZGTNKKNB2VJPP7", "source_location_id": "90A9W5RRYD2GQ", "destination_location_id": "EWVV7AYQC45SS", "status": "COMPLETED", "created_at": "2025-10-08T18:04:48.673Z", "updated_at": "2025-10-08T18:07:43.655Z", "expected_at": "2025-10-08T18:04:00Z", "completed_at": "2025-10-08T18:07:43.655Z", "notes": "Return overstock sizes to Tacoma", "tracking_number": "FLEET-003", "created_by_team_member_id": "bxrMsKrMAMzWgYGGo8BL", "version": 1759946863655 }, { "id": "ZXOLSDDQKOCBMTWY", "source_location_id": "EWVV7AYQC45SS", "destination_location_id": "90A9W5RRYD2GQ", "status": "STARTED", "created_at": "2025-10-08T22:02:22.531Z", "updated_at": "2025-10-08T22:02:23.499Z", "expected_at": "2025-10-08T22:01:00Z", "created_by_team_member_id": "bxrMsKrMAMzWgYGGo8BL", "version": 1759960943499 } ] } ``` {% /tab %} {% /tabset %} With each transfer order returned by the previous query, you need to call `RetrieveTransferOrder`. {% tabset %} {% tab id="Request" %} ```bash curl https://connect.squareupsandbox.com/v2/transfer-orders/ZXOLSDDQKOCBMTWY \ -H 'Square-Version: 2025-09-24' \ -H 'Authorization: Bearer EAAAlkuWf9PJ7EEeV7PSMza45IqJuvI6iuPexW-yLoSx6-a6J5xOOwnNDOniKhUD' \ -H 'Content-Type: application/json' ``` {% /tab %} {% tab id="Response" %} The response shows the full transfer order with its line items for New Balance FuelCell racing flats: ```json { "transfer_order": { "id": "ZXOLSDDQKOCBMTWY", "source_location_id": "EWVV7AYQC45SS", "destination_location_id": "90A9W5RRYD2GQ", "status": "STARTED", "created_at": "2025-10-08T22:02:22.531Z", "updated_at": "2025-10-08T22:02:23.499Z", "expected_at": "2025-10-08T22:01:00Z", "created_by_team_member_id": "bxrMsKrMAMzWgYGGo8BL", "line_items": [ { "transfer_order_line_uid": "HVU4OIW64ZMKPGTA", "item_variation_id": "XPBDUOG3VQBRASADVRSOYS67", "quantity_ordered": "5", "quantity_pending": "5", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "0" } ], "version": 1759960943499 } } ``` In this example, you can see that five pairs of size 9 New Balance FuelCell shoes are in the transfer order and all five are still in transit from Tacoma to Olympia. {% /tab %} {% /tabset %} ## Get inventory adjustments, transfers, and counts When a transfer order is started and the inventory is received by Fleet Foot Running Co.'s Olympia store, inventory adjustments are created. To get those adjustments, call the [BatchRetrieveInventoryChanges](https://developer.squareup.com/reference/square/inventory-api/batch-retrieve-inventory-changes) endpoint to track the movement of shoes between stores: - 20 pairs of New Balance FuelCell (size 9) are received from the vendor at Tacoma (`InventoryAdjustment`) - 10 pairs are transferred from Tacoma to Olympia (`InventoryTransfer`) - 10 pairs are moved from `IN_STOCK` to `IN_TRANSIT` at the Tacoma store (`InventoryAdjustment`) After the 10 pairs are received at the Olympia store: - 10 pairs are moved from `IN_TRANSIT` to `IN_STOCK` at Olympia (`InventoryAdjustment`) ### Get transfers and adjustments {% tabset %} {% tab id="Request" %} This `BatchRetrieveInventoryChanges` request specifies both Fleet Foot Running Co. locations (Tacoma and Olympia) that are involved in the shoe transfer. The query filters for states of `IN_TRANSIT` or `IN_STOCK` and the adjustment type of `ADJUSTMENT`. The date range is set to October 7 to October 8 to capture the transfer activity. ```` {% /tab %} {% tab id="Response" %} The response shows the complete inventory movement for New Balance FuelCell shoes: 1. The initial receipt of 20 pairs from the vendor at the Tacoma store. 2. The transfer of 10 pairs from Tacoma to Olympia. 3. The state change from in-stock to in-transit for those 10 pairs at Tacoma. 4. The state change from in-transit to in-stock for those 10 pairs at Olympia. ```json { "changes": [ { "type": "ADJUSTMENT", "adjustment": { "id": "YYJ3H4F2UTCPVRIGOJU3TGII", "from_state": "ORDERED_FROM_VENDOR", "to_state": "IN_STOCK", "location_id": "EWVV7AYQC45SS", "catalog_object_id": "XPBDUOG3VQBRASADVRSOYS67", "catalog_object_type": "ITEM_VARIATION", "quantity": "20", "occurred_at": "2025-10-07T21:52:41.824Z", "created_at": "2025-10-07T21:52:41.912Z", "team_member_id": "bxrMsKrMAMzWgYGGo8BL", "purchase_order_id": "IBLBJ4UKV4E7XCNA" } }, { "type": "TRANSFER", "transfer": { "id": "IF3VZIJS4NP7N7ONWFO7JGQB_0", "state": "IN_TRANSIT", "from_location_id": "EWVV7AYQC45SS", "to_location_id": "90A9W5RRYD2GQ", "catalog_object_id": "XPBDUOG3VQBRASADVRSOYS67", "catalog_object_type": "ITEM_VARIATION", "quantity": "10", "occurred_at": "2025-10-07T21:53:36.246Z", "created_at": "2025-10-07T21:53:36.349Z" } }, { "type": "ADJUSTMENT", "adjustment": { "id": "IF3VZIJS4NP7N7ONWFO7JGQB_1", "from_state": "IN_STOCK", "to_state": "IN_TRANSIT", "location_id": "EWVV7AYQC45SS", "catalog_object_id": "XPBDUOG3VQBRASADVRSOYS67", "catalog_object_type": "ITEM_VARIATION", "quantity": "10", "occurred_at": "2025-10-07T21:53:36.246Z", "created_at": "2025-10-07T21:53:36.349Z" } }, { "type": "ADJUSTMENT", "adjustment": { "id": "4K5T6BICCZ3ZSPNSZDQC4E2R", "from_state": "IN_TRANSIT", "to_state": "IN_STOCK", "location_id": "90A9W5RRYD2GQ", "catalog_object_id": "XPBDUOG3VQBRASADVRSOYS67", "catalog_object_type": "ITEM_VARIATION", "quantity": "10", "occurred_at": "2025-10-07T21:54:08.14Z", "created_at": "2025-10-07T21:54:08.204Z" } }, { "type": "TRANSFER", "transfer": { "id": "3L4XQA4LNODN5EMXMCA6QCMS_0", "state": "IN_TRANSIT", "from_location_id": "90A9W5RRYD2GQ", "to_location_id": "EWVV7AYQC45SS", "catalog_object_id": "XPBDUOG3VQBRASADVRSOYS67", "catalog_object_type": "ITEM_VARIATION", "quantity": "10", "occurred_at": "2025-10-08T18:04:49.605Z", "created_at": "2025-10-08T18:04:49.742Z" } }, { "type": "ADJUSTMENT", "adjustment": { "id": "3L4XQA4LNODN5EMXMCA6QCMS_1", "from_state": "IN_STOCK", "to_state": "IN_TRANSIT", "location_id": "90A9W5RRYD2GQ", "catalog_object_id": "XPBDUOG3VQBRASADVRSOYS67", "catalog_object_type": "ITEM_VARIATION", "quantity": "10", "occurred_at": "2025-10-08T18:04:49.605Z", "created_at": "2025-10-08T18:04:49.742Z" } }, { "type": "ADJUSTMENT", "adjustment": { "id": "Z3TWWWYJGEJBINFW2QOP5Q7I", "from_state": "IN_TRANSIT", "to_state": "IN_STOCK", "location_id": "EWVV7AYQC45SS", "catalog_object_id": "XPBDUOG3VQBRASADVRSOYS67", "catalog_object_type": "ITEM_VARIATION", "quantity": "10", "occurred_at": "2025-10-08T18:07:43.436Z", "created_at": "2025-10-08T18:07:43.541Z" } } ] } ``` {% /tab %} {% /tabset %} ### Get inventory counts For each shoe size that Fleet Foot Running Co. wants to report on, call the [BatchRetrieveInventoryCounts](https://developer.squareup.com/reference/square/inventory-api/batch-retrieve-inventory-counts) endpoint. The response includes the current stock count at the specified store location for the inventory states requested. This example asks for the in-stock and in-transit counts at the Olympia store (`90A9W5RRYD2GQ`) for size 9 New Balance FuelCell shoes (`XPBDUOG3VQBRASADVRSOYS67`). {% aside type="tip" %} If you provide the New Balance FuelCell item ID instead of a specific size variation ID, the endpoint returns the counts for all available sizes of that shoe model. {% /aside %} {% tabset %} {% tab id="Request" %} ```` {% /tab %} {% tab id="Response" %} ```json { "counts": [ { "catalog_object_id": "XPBDUOG3VQBRASADVRSOYS67", "catalog_object_type": "ITEM_VARIATION", "state": "IN_STOCK", "location_id": "90A9W5RRYD2GQ", "quantity": "0", "calculated_at": "2025-10-08T18:04:49.762Z" }, { "catalog_object_id": "XPBDUOG3VQBRASADVRSOYS67", "catalog_object_type": "ITEM_VARIATION", "state": "IN_TRANSIT", "location_id": "90A9W5RRYD2GQ", "quantity": "5", "calculated_at": "2025-10-08T22:02:23.411Z" } ] } ``` This response shows that the Olympia store currently has 0 pairs in stock and 5 pairs in transit for size 9 New Balance FuelCell shoes. {% /tab %} {% /tabset %} ## Get the transferred catalog item variations To report on the actual shoe models and sizes being transferred between Fleet Foot Running Co. stores, you need to call [RetrieveCatalogObject](https://developer.squareup.com/reference/square/catalog-api/retrieve-catalog-object) as shown in the following example, which retrieves a catalog object by the transfer order line item `item_variation_id`: {% tabset %} {% tab id="Request" %} ```` {% /tab %} {% tab id="Response" %} The response returns the New Balance FuelCell size 9 shoe variation that's in transit: ```json { "object": { "type": "ITEM_VARIATION", "id": "XPBDUOG3VQBRASADVRSOYS67", "updated_at": "2025-10-08T18:04:52.448Z", "created_at": "2024-10-08T18:58:29.345Z", "version": 1759946692448, "is_deleted": false, "present_at_all_locations": false, "present_at_location_ids": [ "90A9W5RRYD2GQ", "EWVV7AYQC45SS" ], "item_variation_data": { "item_id": "M2GW24SH2CI65FSRAN3RX4YP", "name": "Size 9", "sku": "NBFC-9", "ordinal": 4, "pricing_type": "FIXED_PRICING", "price_money": { "amount": 18000, "currency": "USD" }, "location_overrides": [ { "location_id": "90A9W5RRYD2GQ", "track_inventory": true, "sold_out": true }, { "location_id": "EWVV7AYQC45SS", "track_inventory": true } ], "inventory_alert_type": "LOW_QUANTITY", "inventory_alert_threshold": 4, "sellable": true, "stockable": true, "default_unit_cost": { "amount": 9000, "currency": "USD" }, "channels": [ "CH_YaVkJT09dd6NHfrvsF16agtdnoQ9JrkS4gEd5PlQuYC", "CH_jL8ic7qvKWTHgpJOEKJghCeMP62C4VVP4pXfvJPd9945o" ], "item_variation_vendor_info_ids": [ "5NS65PAR7J47DNRYPEWYBLHS" ], "item_variation_vendor_infos": [ { "type": "ITEM_VARIATION_VENDOR_INFO", "id": "5NS65PAR7J47DNRYPEWYBLHS", "updated_at": "2025-10-07T21:51:48.731Z", "created_at": "2025-10-07T21:51:48.903Z", "version": 1759873908731, "is_deleted": false, "present_at_all_locations": false, "present_at_location_ids": [ "90A9W5RRYD2GQ", "EWVV7AYQC45SS" ], "item_variation_vendor_info_data": { "ordinal": 1, "price_money": { "amount": 9000, "currency": "USD" }, "item_variation_id": "XPBDUOG3VQBRASADVRSOYS67", "vendor_id": "N6ET64R24BFQ3YOT" } } ] } } } ``` Note that the Olympia location shows `"sold_out": true` because all inventory is currently in transit from Tacoma. {% /tab %} {% /tabset %} --- # Customer API Best Practices > Source: https://developer.squareup.com/docs/customers-api/best-practices > Status: PUBLIC > Languages: All > Platforms: All Learn about the best practices for creating an application that manages a seller's customer roster using the Customers API. **Applies to:** [Customers API](customers-api/what-it-does) | [Cards API](cards-api/overview) | [Web Payments SDK](web-payments/overview) | [In-App Payments SDK](in-app-payments-sdk/what-it-does) | [Payments API](payments-refunds) {% subheading %}Learn about the best practices for creating an application that manages a seller's customer roster using the Customers API.{% /subheading %} {% toc hide=true /%} ## Overview To be listed in the Square App Marketplace, applications must meet specific requirements aligned with the best practices outlined in this guide. While optional for apps not intended for the App Marketplace, following these practices will help you build more reliable and valuable solutions for Square sellers. {% aside type="info" %} These best practices complement, but don't replace, the comprehensive documentation available in the [Customers API](https://developer.squareup.com/docs/customers) documentation. {% /aside %} ## Core requirements The following requirements outline the essential implementations for managing customer data, processing payments, and storing card information. They're designed to prevent data inconsistencies, ensure accurate payment attribution, and uphold PCI compliance. Each requirement includes links to the relevant API documentation for further guidance. ### Customer data management #### Unique customer records Duplicate customers in the seller's roster can present challenges to their business and must be addressed manually by the seller or with the help of Square technical support. To avoid creating duplicate customers, you should: - Use unique identifiers (an email or phone number is recommended) when creating customers. - Implement logic to detect and prevent duplicate entries for the same customer. For more information, see [Create customer profiles](customers-api/use-the-api/keep-records#create-a-customer-profile). ### Payment integration Linking a payment to the customer who made it improves the accuracy of sales reporting and ensures that refunds are issued to the correct individual when needed. #### Customer ID integration - Always supply the correct `customer_id` when making payment-related API calls. - This applies to: - The Reader SDK. - The Point of Sale API. - Payment-related APIs. For more information, see [CreatePayment](${SQUARE_TECH_REF}/payments-api/create-payment#request__property-customer_id). ### Card-on-file requirements #### Terms of service requirements - Provide explicit Terms of Service/Privacy Policy stating the storage of credit card information. For more information, see [Cards API](cards-api/overview). #### Customer consent - Implement a checkbox mechanism to acquire customer consent for storing credit card details. - Ensure that consent is clearly documented and stored. For more information, see [Cards API](cards-api/overview). ## Implementation guidelines The following code examples and patterns demonstrate the correct implementation of customer creation and payment processing. Each section includes sample JSON payloads, required fields, and validation steps. Follow these patterns to ensure reliable customer data management and payment attribution. ### Creating customers Before creating a new customer, check to see whether a customer already exists with that email address or phone number. For more information see, [Create customer profiles](customers-api/use-the-api/keep-records#create-customer-profiles). #### Unique key implementation ```json { "customer": { "email_address": "customer@example.com", "phone_number": "+1-555-555-0123", "given_name": "John", "family_name": "Doe" }, "idempotency_key": "unique_idempotency_key" } ``` - Use an email address or phone number as a unique identifier. - Always include idempotency keys in create requests. - Validate contact information before submission. ### Payment integration When processing payments, include the `customer_id` in your `CreatePayment` request. This is required for card-on-file payments and recommended for all other payment types. Without a `customer_id`, Square attempts to match the customer using an email or phone number or creates a new customer record if no match is found. #### Customer ID usage ```json { "source_id": "card_nonce_or_token", "customer_id": "CUSTOMER_ID", "amount_money": { "amount": 1000, "currency": "USD" } } ``` - Include [customer_id](${SQUARE_TECH_REF}/objects/Payment#definition__property-customer_id) in all payment requests when available. - Validate that `customer_id` exists before making payment calls. - Handle cases where `customer_id` might be invalid. ## Best practices by feature This section details specific implementation patterns for key API features. Each feature includes code-level considerations and common pitfalls to avoid. These practices are based on real integration scenarios and help prevent issues like duplicate customer records or incomplete card storage compliance. ### Card on file Implementing card-on-file storage requires specific consent mechanisms and privacy policy components to maintain PCI compliance and protect customer data. #### Consent implementation - Display a clear consent checkbox. - Store consent records. - Include a consent timestamp. - Link to the Terms of Service. #### Privacy policy requirements - Clearly state your card storage policy. - Describe how customer data will be used. - Outline your data retention policies. - Explain customers’ rights and control over their data. ### Customer data management Poor customer data management leads to duplicate records, which breaks customer-based reporting, complicates Point of Sale lookups, and causes redundant customer notifications. #### Duplicate prevention - Check for existing customers before creating new records. - Use reference keys to link customers with external systems. - Implement strategies to merge duplicate customer records. - Perform regular data cleanup to maintain accuracy and consistency. ## Troubleshooting guide This guide addresses common integration issues related to customer data integrity and payment processing. Each issue includes identifiable symptoms, step-by-step resolution instructions, and code examples that illustrate effective error handling and recovery strategies. ### Common issues #### Duplicate customer records - Symptom: Multiple records for same customer. - Resolution: 1. Search for existing customers using email and phone number before creating a new record. 2. Use idempotency keys to prevent accidental duplicates. 3. Implement functionality to merge duplicate records when identified. #### Invalid customer IDs - Symptom: Payment failures due to `customer_id` issues. - Resolution: 1. Validate the `customer_id` before using it in requests. 2. If validation fails, implement a retry mechanism that includes a customer lookup. 3. Gracefully handle cases where the `customer_id` is expired or invalid, with appropriate error messaging and fallback logic. ## Monitoring recommendations Monitor key metrics and error patterns to ensure reliable customer operations and quickly detect integration issues. Each metric offers valuable insights into system health and API performance. Set up alerts for anomalies—particularly those related to customer creation and payment processing—to enable fast response and resolution. ### Key metrics #### Customer creation - Success rate - Duplicate detection rate - Error rates by type #### Payment integration - Customer ID validation success - Card-on-file usage rates - Consent-tracking metrics ### Error tracking #### Priority errors - Customer creation failures - Payment integration issues - Consent-recording failures #### Response time - API latency monitoring - Error resolution time - Customer lookup performance --- # Quickstart: Create and Manage Gift Cards with Customer Integration > Source: https://developer.squareup.com/docs/customers-api/quick-start > Status: PUBLIC > Languages: All > Platforms: All Learn how to create a new customer and process a payment for that customer using the Customers and Payments APIs. **Applies to:** [Customers API](customers-api/what-it-does) | [Gift Cards API](gift-cards/using-gift-cards-api) {% subheading %}Learn how to integrate the Customers API with the Gift Cards API to create or find customers and manage their gift cards.{% /subheading %} {% toc hide=true /%} ## Prerequisites Before you begin, you need the following: * A Square account (sign up for a free [developer account](https://squareup.com/signup) if you don't have one). * An access token for the Square API. For more information, see [Get Started](get-started). The following language-specific requirements are needed to run the code samples: {% tabset %} {% tab id="java" %} * Java 8 or later * Square Java SDK 35.0.0 or later * Gradle or Maven Add this dependency to your `build.gradle` file: ```groovy implementation 'com.squareup:square:35.0.0' ``` Or to your `pom.xml` file: ```xmlCrispy tortilla chips topped with melted cheese, jalapenos, sour cream, and salsa.
", "description_plaintext": "Crispy tortilla chips topped with melted cheese, jalapenos, sour cream, and salsa.", "channels": [ "CH_QsN9pXIZiVwCMWjFr8NXQqPO54qnOXkyQRkiBQlQuYC" ], "is_archived": false, "reporting_category": { "id": "Z6O6YJFE37DLY37G4OULXDHY", "ordinal": 1 }, "is_alcoholic": false } }, { "type": "ITEM", "id": "WMESBNU3X25KAZ7MOSEBKNHC", "updated_at": "2025-06-10T22:44:08.012Z", "created_at": "2025-06-10T22:43:22.259Z", "version": 1749595448012, "is_deleted": false, "present_at_all_locations": false, "present_at_location_ids": [ "E78VDDVFW1EYX" ], "item_data": { "name": "Buffalo Wings", "description": "Spicy chicken wings served with blue cheese dressing and celery sticks.", "is_taxable": true, "modifier_list_info": [ { "modifier_list_id": "CQZ6SECYCB5E2JM2OJU6WO2M", "min_selected_modifiers": -1, "max_selected_modifiers": -1, "enabled": true, "hidden_from_customer": false, "ordinal": 1, "allow_quantities": "NOT_SET", "is_conversational": "NOT_SET", "hidden_from_customer_override": "NOT_SET" } ], "variations": [ { "type": "ITEM_VARIATION", "id": "I573TS32QL4CESCFYG5T3Y7A", "updated_at": "2025-06-10T22:43:22.122Z", "created_at": "2025-06-10T22:43:22.259Z", "version": 1749595402122, "is_deleted": false, "present_at_all_locations": false, "present_at_location_ids": [ "E78VDDVFW1EYX" ], "item_variation_data": { "item_id": "WMESBNU3X25KAZ7MOSEBKNHC", "name": "Regular", "ordinal": 0, "pricing_type": "FIXED_PRICING", "price_money": { "amount": 1000, "currency": "USD" }, "sellable": true, "stockable": true, "channels": [ "CH_QsN9pXIZiVwCMWjFr8NXQqPO54qnOXkyQRkiBQlQuYC" ] } } ], "product_type": "FOOD_AND_BEV", "ecom_uri": "https://flamingredphoto.square.site/product/buffalo-wings/35", "ecom_available": true, "ecom_visibility": "VISIBLE", "categories": [ { "id": "Z6O6YJFE37DLY37G4OULXDHY", "ordinal": 0 }, { "id": "7EN7VWSPUI25PJPS6XEEUV5G", "ordinal": 0 } ], "description_html": "Spicy chicken wings served with blue cheese dressing and celery sticks.
", "description_plaintext": "Spicy chicken wings served with blue cheese dressing and celery sticks.", "channels": [ "CH_QsN9pXIZiVwCMWjFr8NXQqPO54qnOXkyQRkiBQlQuYC" ], "is_archived": false, "reporting_category": { "id": "Z6O6YJFE37DLY37G4OULXDHY", "ordinal": 0 }, "is_alcoholic": false } }, { "type": "ITEM", "id": "GDAYYASFCCF6QUGM4URTS7BN", "updated_at": "2025-06-10T22:44:08.232Z", "created_at": "2025-06-10T22:43:22.259Z", "version": 1749595448232, "is_deleted": false, "present_at_all_locations": false, "present_at_location_ids": [ "E78VDDVFW1EYX" ], "item_data": { "name": "Classic Burger", "description": "Juicy beef patty with lettuce, tomato, onions, and pickles on a sesame seed bun.", "is_taxable": true, "modifier_list_info": [ { "modifier_list_id": "UBG5GMFXG4GABAHNIY4DNHWQ", "min_selected_modifiers": -1, "max_selected_modifiers": -1, "enabled": true, "hidden_from_customer": false, "ordinal": 2, "allow_quantities": "NOT_SET", "is_conversational": "NOT_SET", "hidden_from_customer_override": "NOT_SET" } ], "variations": [ { "type": "ITEM_VARIATION", "id": "B5IDPADMOT7MQ2NK26FKNJQU", "updated_at": "2025-06-10T22:43:22.122Z", "created_at": "2025-06-10T22:43:22.259Z", "version": 1749595402122, "is_deleted": false, "present_at_all_locations": false, "present_at_location_ids": [ "E78VDDVFW1EYX" ], "item_variation_data": { "item_id": "GDAYYASFCCF6QUGM4URTS7BN", "name": "Regular", "ordinal": 0, "pricing_type": "FIXED_PRICING", "price_money": { "amount": 1500, "currency": "USD" }, "sellable": true, "stockable": true, "channels": [ "CH_QsN9pXIZiVwCMWjFr8NXQqPO54qnOXkyQRkiBQlQuYC" ] } } ], "product_type": "FOOD_AND_BEV", "ecom_uri": "https://flamingredphoto.square.site/product/classic-burger/36", "ecom_available": true, "ecom_visibility": "VISIBLE", "categories": [ { "id": "U3G2UZ75UWIVG3JWQL5BCLH4", "ordinal": 0 }, { "id": "JNC2O2TJ534GSLDLSIE35NMH", "ordinal": 0 } ], "description_html": "Juicy beef patty with lettuce, tomato, onions, and pickles on a sesame seed bun.
", "description_plaintext": "Juicy beef patty with lettuce, tomato, onions, and pickles on a sesame seed bun.", "channels": [ "CH_QsN9pXIZiVwCMWjFr8NXQqPO54qnOXkyQRkiBQlQuYC" ], "is_archived": false, "reporting_category": { "id": "U3G2UZ75UWIVG3JWQL5BCLH4", "ordinal": 0 }, "is_alcoholic": false } } ], "related_objects": [ { "type": "CATEGORY", "id": "U3G2UZ75UWIVG3JWQL5BCLH4", "updated_at": "2025-06-10T22:43:22.122Z", "created_at": "2025-06-10T22:43:22.259Z", "version": 1749595402122, "is_deleted": false, "present_at_all_locations": true, "category_data": { "name": "Sandwiches", "category_type": "REGULAR_CATEGORY", "parent_category": { "ordinal": 412317061904 }, "is_top_level": true, "online_visibility": true } }, { "type": "CATEGORY", "id": "Z6O6YJFE37DLY37G4OULXDHY", "updated_at": "2025-06-10T22:43:22.122Z", "created_at": "2025-06-10T22:43:22.259Z", "version": 1749595402122, "is_deleted": false, "present_at_all_locations": true, "category_data": { "name": "Starters", "category_type": "REGULAR_CATEGORY", "parent_category": { "ordinal": 481036538640 }, "is_top_level": true, "online_visibility": true } } ], "latest_time": "2025-06-11T21:39:58.991Z" } ``` {% /tab %} {% /tabset %} {% accordion expanded=false %} {% slot "heading" %} ### Best practices for menu syncing {% /slot %} 1. **Initial load** - Perform the initial sync during off-peak hours. - Process objects in the correct order (categories → items → modifiers). - Validate relationships as you build the menu structure. 2. **Incremental updates** - Store the last sync timestamp. - Include related objects in update queries. - Handle deleted objects appropriately. 3. **Error handling** - Implement retry logic for failed requests. - Log sync errors for troubleshooting. - Maintain a sync status dashboard. 4. **Data validation** - Verify category relationships. - Validate price points. - Check for required attributes. {% /accordion %} ## Understanding menu categories vs. regular categories When working with the Catalog API, it's important to understand the distinction between menu categories and regular categories: * **Menu categories** - Created through the Square Dashboard's menu builder. - Have `category_type = "MENU_CATEGORY"`. - Used specifically for menu organization. - Like regular categories, support parent-child relationships. - Can be synchronized across channels. * **Regular categories** - Created through the Catalog API. - Have `category_type = "REGULAR_CATEGORY"`. - Used for general item organization. - Might appear as duplicates of menu categories. {% aside type="important" %} When retrieving categories through the Catalog API, you might see what appears to be duplicate categories. This happens because the menu builder creates separate menu categories (`MENU_CATEGORY`) from regular categories (`REGULAR_CATEGORY`). To work specifically with menu structures, always filter for categories where `category_type = "2" (`MENU_CATEGORY`). {% /aside %} ## Kitchen display names For Food & Beverage operations, Square supports separate kitchen display names to streamline back-of-house operations. These properties allow restaurants to use different names for kitchen staff versus customer-facing displays, improving order accuracy and kitchen efficiency. **Available properties:** - [CatalogItem.kitchen_name](https://developer.squareup.com/reference/square/objects/CatalogItem#definition__property-kitchen_name) - A kitchen-friendly name for the menu item. - [CatalogItem.buyer_facing_name](https://developer.squareup.com/reference/square/objects/CatalogItem#definition__property-buyer_facing_name) - The customer-facing name displayed to buyers. - [CatalogItemVariation.kitchen_name](https://developer.squareup.com/reference/square/objects/CatalogItemVariation#definition__property-kitchen_name) - Kitchen name for specific item variations (sizes and options). - [CatalogModifier.kitchen_name](https://developer.squareup.com/reference/square/objects/CatalogModifier#definition__property-kitchen_name) - Kitchen name for modifiers (such as add-ons and customizations). **Benefits for restaurant operations:** These properties help restaurants: - Use abbreviated or coded names on kitchen display systems (KDSs). - Maintain appetizing, descriptive names for customer receipts and online ordering. - Reduce confusion during food preparation with standardized kitchen terminology. - Speed up order fulfillment by using familiar kitchen shorthand. - Support multi-language operations (kitchen names in one language, buyer names in another). **Example: Creating items with kitchen names** ```` **Example: Adding kitchen names to modifiers** ```` **Best practices for kitchen names:** 1. **Keep it concise** - Kitchen names should be short and easy to read on kitchen display screens. 2. **Use standard abbreviations** - Develop consistent abbreviations your kitchen staff understands. 3. **Consider screen space** - Kitchen display systems often have limited character width. 4. **Test with staff** - Ensure kitchen names are clear and unambiguous to your team. 5. **Update consistently** - When adding new items, maintain your naming conventions. **Common kitchen name patterns:** | Customer Name | Kitchen Name | Purpose | |--------------|--------------|---------| | Grilled Chicken Caesar Salad | GRL CKN CAESAR | Abbreviated ingredients | | Mediterranean Veggie Wrap | MED VEG WRAP | Shortened descriptors | | Extra Virgin Olive Oil | EVOO | Industry standard abbreviations | | Add Avocado (+$2.00) | +AVO | Modifier shorthand | | No Onions | NO ONION / -ONION | Exclusion indicators | | Gluten-Free Bun | GF BUN | Dietary abbreviations | ## Create a menu ### 1. Retrieving menu categories Use the Catalog API to retrieve existing menu categories. ```` ### 2. Creating location-specific menu categories When creating categories for different locations, ensure that you: * Set the correct `category_type`. * Establish proper parent-child relationships. * Use consistent naming across locations. The following shows an example for a delivery service menu category: {% tabset %} {% tab id="Request" %} ```` {% aside type="info" %} Menu objects are automatically enabled across all locations by default. When creating or updating menu objects using the API: - Set `present_at_all_locations` to `true` or omit it entirely. - Don't set `present_at_location_ids` or `absent_at_location_ids` (leave these fields empty). Location-specific menu configuration can only be managed through the Square Dashboard. This includes: - Enabling or disabling menus for specific locations. - Setting location-specific menu availability. - Managing location-based menu variations. {% /aside %} {% /tab %} {% tab id="Response" %} ```json { "catalog_object": { "type": "CATEGORY", "id": "ILBEI56EXWHBBHC7LH7HP7CZ", "updated_at": "2025-06-10T23:39:45.984Z", "created_at": "2025-06-10T23:39:45.984Z", "version": 1749598785984, "is_deleted": false, "present_at_all_locations": true, "category_data": { "name": "Delivery Menu", "category_type": "MENU_CATEGORY", "parent_category": { "ordinal": 618475492112 }, "is_top_level": true, "online_visibility": true } }, "id_mappings": [ { "client_object_id": "#delivery_menu", "object_id": "ILBEI56EXWHBBHC7LH7HP7CZ" } ] } ``` {% /tab %} {% /tabset %} ### 3. Creating subcategories When creating subcategories, always reference the parent menu category. {% tabset %} {% tab id="Request" %} ```` {% /tab %} {% tab id="Response" %} ```json { "catalog_object": { "type": "CATEGORY", "id": "YUJLJGPTN5OSDE5ZJOTNBK47", "updated_at": "2025-06-10T23:52:08.586Z", "created_at": "2025-06-10T23:52:08.586Z", "version": 1749599528586, "is_deleted": false, "present_at_all_locations": true, "category_data": { "name": "Appetizers", "category_type": "MENU_CATEGORY", "parent_category": { "id": "ILBEI56EXWHBBHC7LH7HP7CZ", "ordinal": -2251731094208512 }, "is_top_level": false, "online_visibility": true, "root_category": "ILBEI56EXWHBBHC7LH7HP7CZ" } }, "id_mappings": [ { "client_object_id": "#delivery_appetizers_2", "object_id": "YUJLJGPTN5OSDE5ZJOTNBK47" } ] } ``` {% /tab %} {% /tabset %} ### 4. Menu structures When working with menu categories, it's important to understand that the structure you see in the Square Dashboard is represented by `MENU_CATEGORY` types in the Catalog API. The following example shows how different menu structures might look: #### Dashboard menu structure ``` Restaurant Menu (MENU_CATEGORY) ├── Beverages │ ├── Non-Alcoholic │ │ ├── Iced Tea (Small/Large) │ │ ├── Soft Drinks (Regular/Large) │ │ └── Coffee (Small/Large) │ ├── Beer │ │ ├── Draft (Domestic/Craft) │ │ └── Bottled (Domestic/Imported) │ └── Wine │ ├── Red (Glass/Bottle) │ └── White (Glass/Bottle) ├── Appetizers │ ├── Mozzarella Sticks │ ├── Wings (6pc/12pc) │ └── Nachos └── Entrees ├── Sandwiches │ ├── Club │ └── BLT └── Salads ├── Caesar └── Garden ``` #### Delivery service menu structure ``` Delivery Menu (MENU_CATEGORY) ├── Beverages │ ├── Iced Tea (Small/Large) │ ├── Soft Drinks (Regular/Large) │ └── Coffee (Small/Large) ├── Appetizers │ ├── Mozzarella Sticks │ ├── Wings (6pc/12pc) │ └── Nachos └── Entrees ├── Sandwiches │ ├── Club │ └── BLT └── Salads ├── Caesar └── Garden ``` ## Common issues and solutions ### Issue: Duplicate categories - **Cause**: Items appearing in both menu and regular categories. - **Solution**: Filter specifically for `MENU_CATEGORY` types. - **Prevention**: Use proper category type filtering in API calls. ### Issue: Missing menu items - **Cause**: Looking at the wrong category type. - **Solution**: Check both menu and regular category associations. - **Prevention**: Implement proper category type checking. ### Issue: Incorrect menu structure - **Cause**: Mixing menu and regular categories. - **Solution**: Maintain proper parent-child relationships. - **Prevention**: Validate category types and relationships. ## Implementation tips 1. **Category filtering** ```` 2. **Relationship maintenance** - Track both menu and regular category IDs. - Update all relevant category associations. - Maintain a proper hierarchy. 3. **Data validation** - Verify category types before updates. - Check parent-child relationships. - Validate item associations. ### Next steps after syncing 1. **Verify data** - Compare item counts. - Validate category structures. - Check modifier associations. 2. **Monitor performance** - Track sync times. - Monitor API usage. - Optimize query patterns. 3. **Setup alerts** - Configure sync failure notifications. - Monitor for pricing discrepancies. - Alert when structure changes occur. --- # Manage Scheduled Shifts > Source: https://developer.squareup.com/docs/labor-api/manage-scheduled-shifts > Status: BETA > Languages: All > Platforms: All Learn how to create and manage future scheduled shifts for team members using scheduling endpoints in the Labor API. **Applies to:** [Labor API](labor-api/what-it-does) | [Team API](team/overview) | [Locations API](locations-api) | [Webhooks](webhooks/overview) {% subheading %}Use the [Labor API](https://developer.squareup.com/reference/square/labor-api) to create and manage future scheduled shifts for team members.{% /subheading %} {% toc hide=true /%} ## Overview Applications can use the Labor API to manage team schedules. The basic process includes creating draft scheduled shifts, publishing the scheduled shifts to make them visible to team members, and optionally updating or deleting scheduled shifts. The following diagram shows the API call flow for managing a scheduled shift and the webhook events triggered by each request:  All updates to a scheduled shift are made to the `draft_shift_details` field. When a scheduled shift is published, Square copies `draft_shift_details` to `published_shift_details`. *If the shift was never published, setting `draft_shift_details.is_deleted` to `true` in an `UpdateScheduledShift` request deletes the shift. Otherwise, the shift is just marked for deletion and you must publish the change to delete it. {% aside type="info" %} The Labor API defines two types of objects; be careful not to confuse them: - A [ScheduledShift](https://developer.squareup.com/reference/square/objects/ScheduledShift) is used to manage team schedules. - A [Timecard](https://developer.squareup.com/reference/square/objects/Timecard) represents a single work shift for a team member. Each `Timecard` captures: - The actual time worked during one shift - Information needed for payroll processing For example, if an employee works five different shifts in a week, they will have five separate Timecard records - one for each shift. **Note:** The older `Shift` object type is deprecated. Use Timecards instead. For information about migration, see [Migration notes](labor-api/what-it-does#migration-notes). {% /aside %} ## Requirements and limitations The following requirements and limitations apply when managing scheduled shifts using the Labor API: * **Square API version** - Square API version 2025-05-21 or later is required to access or manage scheduled shifts. * **Valid access token** - Square API requests require an `Authorization` header with a bearer access token. The examples in this topic use the {% tooltip text="Square Sandbox" %}An isolated server environment used for testing. API calls in the Sandbox use the "connect.squareupsandbox.com" domain and require an access token that's valid for Sandbox.{% /tooltip %}, so you can test using your [Sandbox access token](build-basics/access-tokens#get-a-personal-access-token). {% accordion expanded=false %} {% slot "heading" %} #### More details {% /slot %} Applications using [OAuth](oauth-api/overview) access tokens to authorize requests typically require the following permissions when working with team schedules: - `MERCHANT_PROFILE_READ` to retrieve location information. - `EMPLOYEES_READ` to retrieve team member and job information. - `TIMECARDS_READ` to retrieve scheduled shifts. - `TIMECARDS_WRITE` to create, update, and publish scheduled shifts. To call Square APIs in the production environment, change the base URL to `https://connect.squareup.com` and use your production (personal) access token. {% /accordion %} * **Two-week window for bulk publishing** - The minimum `start_at` and maximum `end_at` timestamps of all shifts in a `BulkPublishScheduledShifts` request must fall within a two-week period. {% anchor id="time-zone-offset" /%} * **Time zone + offset for start and end times** - The `start_at` and `end_at` timestamps are provided in RFC 3339 format and represent the local time for the shift location specified in `location_id`. Precision up to the minute is respected; seconds are truncated. {% accordion expanded=false %} {% slot "heading" %} #### More details {% /slot %} For example, 9:00 AM local PST time can be specified as `2025-04-13T09:00:00-08:00` or `2025-04-13T17:00:00Z`. Square stores the location's time zone (for example, "America/Los_Angeles") in the read-only `timezone` field of the scheduled shift. Square uses the time zone to calculate the correct local time for shifts and display times appropriately to team members. When providing Daylight Saving Time (DST) timestamps, be aware that: - Times during the DST start transition (typically 2:00-2:59 AM) don't exist. - Times during the DST end transition (typically 1:00-1:59 AM) occur twice. {% /accordion %} For scheduling capabilities, see general [Requirements and limitations](labor-api/scheduling#requirements-and-limitations). {% anchor id="create-scheduled-shifts" /%} ## Create scheduled shifts To create a scheduled shift, call [CreateScheduledShift](https://developer.squareup.com/reference/square/labor-api/create-scheduled-shift) and provide `draft_shift_details`. You can then review and update the shift and publish it when you're ready to make it visible to all team members. When a scheduled shift is published, Square keeps `draft_shift_details` as is and copies it to the `published_shift_details` field. Be ready to provide `draft_shift_details` with the following required fields: - `location_id` - The ID of the location where the shift occurs. Call [ListLocations](https://developer.squareup.com/reference/square/locations-api/list-locations) to get location IDs. - `job_id` - The ID of the job for the shift. Call [ListJobs](https://developer.squareup.com/reference/square/team-api/list-jobs) to get job IDs. - `start_at` and `end_at` - Start and end times for the shift in the [time zone + offset](#time-zone-offset) of the location. You can also provide the following optional `draft_shift_details` fields: - `team_member_id` - The ID of the [team member to assign to the shift](#manage-team-member-assignments). - `notes` - Notes for the shift. {% line-break /%} {% tabset %} {% tab id="Request" %} The following example request includes all draft shift details ```` {% /tab %} {% tab id="Response" %} The response returns the new scheduled shift with draft shift details. ```json { "scheduled_shift": { "id": "kxR8FWJE8DT3x7", "draft_shift_details": { "location_id": "KACM8A41GR60X", "job_id": "jxR8EWJE8DT2x7", "team_member_id": "-3oZQKPKVk6gUXU_V5Qa", "start_at": "2025-04-15T09:00:00-07:00", "end_at": "2025-04-15T17:00:00-07:00", "notes": "Opening shift", "is_deleted": false, "timezone": "America/Los_Angeles" }, "version": 1, "created_at": "2025-04-02T14:58:49-07:00", "updated_at": "2025-04-02T14:58:49-07:00" } } ``` {% /tab %} {% /tabset %} ### Webhooks Creating a scheduled shift triggers the following [webhook event](webhooks/overview): - [labor.scheduled_shift.created](https://developer.squareup.com/reference/square/labor-api/webhooks/labor.scheduled_shift.created) ### Next steps - [Update the scheduled shift](#update-scheduled-shifts) as needed. Updates might include [managing the team member assignment](#manage-team-member-assignments) or [deleting the shift](#delete-scheduled-shifts). - [Publish the scheduled shift](#publish-scheduled-shifts) to make it public. ## Update scheduled shifts To make changes to a scheduled shift, call [UpdateScheduledShift](https://developer.squareup.com/reference/square/labor-api/update-scheduled-shift) and provide `draft_shift_details` with any new, changed, or cleared fields. When you're ready to make the changes visible to all team members, publish the shift. Be ready to provide the following information: - `id` - The shift ID as a path parameter. Get the shift ID from the [CreateScheduledShift](https://developer.squareup.com/reference/square/labor-api/create-scheduled-shift) response or call [SearchScheduledShifts](https://developer.squareup.com/reference/square/labor-api/search-scheduled-shifts). - `draft_shift_details` - Any new, changed, or cleared fields. You can make the following changes: - Change the value of required fields. - `location_id` - The ID of the location where the shift occurs. Call [ListLocations](https://developer.squareup.com/reference/square/locations-api/list-locations) to get location IDs. - `job_id` - The ID of the job for the shift. Call [ListJobs](https://developer.squareup.com/reference/square/team-api/list-jobs) to get job IDs. - `start_at` and `end_at` - Start and end times for the shift in [time zone + offset format](#time-zone-offset). - Add, change, or clear optional fields. To clear these fields, set the value to `null`. - `team_member_id` - The ID of the [team member to assign to the shift](#manage-team-member-assignments). - `notes` - Notes for the shift. - Use the `is_deleted` field to [delete the shift](#delete-scheduled-shifts). - `version` (optional) - The current version of the shift, used to enable [optimistic concurrency control](build-basics/common-api-patterns/optimistic-concurrency) for the request. Get the current version when you get the shift ID. {% aside type="info" %} `UpdateScheduledShift` supports [sparse updates](build-basics/clearing-fields#sparse-updates), so the request only needs to include your changes. Any updates to the `published_shift_details` field are ignored. {% /aside %} {% tabset %} {% tab id="Request" %} The following example request changes the `team_member_id`, `start_at`, and `end_at` fields and clears the `notes` field: ```` {% /tab %} {% tab id="Response" %} The response returns the scheduled shift with updated draft shift details and incremented `version`. In this example, the presence of the `published_shift_details` field indicates that the shift was previously published. Note that `draft_shift_details` differs from `published_shift_details` because the change isn't published yet. ```json { "scheduled_shift": { "id": "kxR8FWJE8DT3x7", "draft_shift_details": { "location_id": "KACM8A41GR60X", "job_id": "jxR8EWJE8DT2x7", "team_member_id": "-3oZQKPKVk6gUXU_V5Qa", "start_at": "2025-04-15T11:00:00-07:00", "end_at": "2025-04-15T18:00:00-07:00", "is_deleted": false, "timezone": "America/Los_Angeles" }, "published_shift_details": { "location_id": "KACM8A41GR60X", "job_id": "jxR8EWJE8DT2x7", "team_member_id": "1AVJj0DjkzbmbJw5r4KK", "start_at": "2025-04-15T09:00:00-07:00", "end_at": "2025-04-15T17:00:00-07:00", "notes": "Opening shift", "is_deleted": false, "timezone": "America/Los_Angeles" }, "version": 2, "created_at": "2025-04-02T14:58:49-07:00", "updated_at": "2025-04-02T15:00:00-07:00" } } ``` {% /tab %} {% /tabset %} ### Webhooks Updating a scheduled shift triggers the following [webhook events](webhooks/overview): - [labor.scheduled_shift.updated](https://developer.squareup.com/reference/square/labor-api/webhooks/labor.scheduled_shift.updated) - [labor.scheduled_shift.deleted](https://developer.squareup.com/reference/square/labor-api/webhooks/labor.scheduled_shift.deleted) - Only if `draft_shift_details.is_deleted` is set to `true` when [deleting a draft-only shift](#delete-scheduled-shifts). ### Next steps - Update the scheduled shift with more changes as needed. This might include [managing the team member assignment](#manage-team-member-assignments) or [deleting the shift](#delete-scheduled-shifts). - [Publish the scheduled shift](#publish-scheduled-shifts) to make your changes public. ## Publish scheduled shifts After creating or updating a scheduled shift, you must call [PublishScheduledShift](https://developer.squareup.com/reference/square/labor-api/publish-scheduled-shift) or [BulkPublishScheduledShifts](https://developer.squareup.com/reference/square/labor-api/bulk-publish-scheduled-shifts) to make the new shift or latest draft details visible to all team members. When a scheduled shift is published, Square keeps `draft_shift_details` as is and copies it to the `published_shift_details` field. Be ready to provide the following information: - `id` - The shift ID. Get the shift ID from the [CreateScheduledShift](https://developer.squareup.com/reference/square/labor-api/create-scheduled-shift) response or call [SearchScheduledShifts](https://developer.squareup.com/reference/square/labor-api/search-scheduled-shifts). - `idempotency_key` - A unique string used to ensure the [idempotency](build-basics/common-api-patterns/idempotency) of the operation. Required for `PublishScheduledShift`; not used for `BulkPublishScheduledShifts`. - `scheduled_shift_notification_audience` - Indicates whether Square should send an email notification to team members. Valid values: * `ALL` - Send a notification to all active team members. * `AFFECTED` (default) - Send a notification to the team member assigned to the shift. When changing the team member assignment, both team members are notified. * `NONE` - Don't send notifications. Note that republishing an unchanged shift doesn't resend the notification if set to `AFFECTED` or `NONE`. - `version` (optional) - The current version of the shift, used to enable [optimistic concurrency control](build-basics/common-api-patterns/optimistic-concurrency) for the request. Get the current version when you get the shift ID. {% line-break /%} {% accordion expanded=true %} {% slot "heading" %} ### Publish a single scheduled shift {% /slot %} To publish a single scheduled shift, call [PublishScheduledShift](https://developer.squareup.com/reference/square/labor-api/publish-scheduled-shift) and provide the ID of the scheduled shift you want to publish (as a path parameter) and `idempotency_key`. Optionally provide `version` and `scheduled_shift_notification_audience`. {% tabset %} {% tab id="Request" %} ```` {% /tab %} {% tab id="Response" %} The response returns the published scheduled shift. ```json { "scheduled_shift": { "id": "kxR8FWJE8DT3x7", "draft_shift_details": { "location_id": "KACM8A41GR60X", "job_id": "jxR8EWJE8DT2x7", "team_member_id": "-3oZQKPKVk6gUXU_V5Qa", "start_at": "2025-04-15T09:00:00-07:00", "end_at": "2025-04-15T17:00:00-07:00", "notes": "Opening shift", "is_deleted": false, "timezone": "America/Los_Angeles" }, "published_shift_details": { "location_id": "KACM8A41GR60X", "job_id": "jxR8EWJE8DT2x7", "team_member_id": "-3oZQKPKVk6gUXU_V5Qa", "start_at": "2025-04-15T09:00:00-07:00", "end_at": "2025-04-15T17:00:00-07:00", "notes": "Opening shift", "is_deleted": false, "timezone": "America/Los_Angeles" }, "version": 2, "created_at": "2025-04-02T14:58:49-07:00", "updated_at": "2025-04-02T15:00:00-07:00" } } ``` {% /tab %} {% /tabset %} {% /accordion %} {% accordion expanded=true %} {% slot "heading" %} ### Publish multiple scheduled shifts {% /slot %} To publish 1 – 100 scheduled shifts, provide `scheduled_shifts` as a map of key-value pairs that represent individual publish requests. The minimum `start_at` and maximum `end_at` timestamps of all shifts in a `BulkPublishScheduledShifts` request must fall within a two-week period. - Each key is the ID of a scheduled shift you want to publish. - Each value is a `BulkPublishScheduledShiftsData` object that contains the optional `version` field or is empty. The optional `scheduled_shift_notification_audience` setting applies to the bulk operation. {% tabset %} {% tab id="Request" %} ```` {% /tab %} {% tab id="Response" %} The response returns a map of responses for the published scheduled shifts. Responses might not be returned in the same order as the individual publish requests sent to `BulkPublishScheduledShifts`. ```json { "responses": { "kxR8FWJE8DT3x7": { "scheduled_shift": { "id": "kxR8FWJE8DT3x7", "draft_shift_details": { "location_id": "KACM8A41GR60X", "job_id": "jxR8EWJE8DT2x7", "team_member_id": "-3oZQKPKVk6gUXU_V5Qa", "start_at": "2025-04-15T09:00:00-07:00", "end_at": "2025-04-15T17:00:00-07:00", "notes": "Opening shift", "is_deleted": false, "timezone": "America/Los_Angeles" }, "published_shift_details": { "location_id": "KACM8A41GR60X", "job_id": "jxR8EWJE8DT2x7", "team_member_id": "-3oZQKPKVk6gUXU_V5Qa", "start_at": "2025-04-15T09:00:00-07:00", "end_at": "2025-04-15T17:00:00-07:00", "notes": "Opening shift", "is_deleted": false, "timezone": "America/Los_Angeles" }, "version": 2, "created_at": "2025-04-02T14:58:49-07:00", "updated_at": "2025-04-02T15:00:00-07:00" } }, "kxR8FWJE8DT4x8": { "scheduled_shift": { "id": "kxR8FWJE8DT4x8", "draft_shift_details": { "location_id": "KACM8A41GR60X", "job_id": "jxR8EWJE8DT2x7", "team_member_id": "-4pZQKPKVk6gUXU_V5Qb", "start_at": "2025-04-15T17:00:00-07:00", "end_at": "2025-04-16T01:00:00-07:00", "notes": "Closing shift", "is_deleted": false, "timezone": "America/Los_Angeles" }, "published_shift_details": { "location_id": "KACM8A41GR60X", "job_id": "jxR8EWJE8DT2x7", "team_member_id": "-4pZQKPKVk6gUXU_V5Qb", "start_at": "2025-04-15T17:00:00-07:00", "end_at": "2025-04-16T01:00:00-07:00", "notes": "Closing shift", "is_deleted": false, "timezone": "America/Los_Angeles" }, "version": 2, "created_at": "2025-04-02T14:58:49-07:00", "updated_at": "2025-04-02T15:00:00-07:00" } }, "kxR8FWJE8DT5x9": { "scheduled_shift": { "id": "kxR8FWJE8DT5x9", "draft_shift_details": { "location_id": "KACM8A41GR60X", "job_id": "jxR8EWJE8DT2x7", "team_member_id": "-5qZQKPKVk6gUXU_V5Qc", "start_at": "2025-04-16T09:00:00-07:00", "end_at": "2025-04-16T17:00:00-07:00", "notes": "Opening shift", "is_deleted": false, "timezone": "America/Los_Angeles" }, "published_shift_details": { "location_id": "KACM8A41GR60X", "job_id": "jxR8EWJE8DT2x7", "team_member_id": "-5qZQKPKVk6gUXU_V5Qc", "start_at": "2025-04-16T09:00:00-07:00", "end_at": "2025-04-16T17:00:00-07:00", "notes": "Opening shift", "is_deleted": false, "timezone": "America/Los_Angeles" }, "version": 1, "created_at": "2025-04-02T14:58:49-07:00", "updated_at": "2025-04-02T15:00:00-07:00" } } } } ``` {% /tab %} {% /tabset %} {% /accordion %} ### Webhooks Publishing a scheduled shift triggers the following [webhook events](webhooks/overview): - [labor.scheduled_shift.updated](https://developer.squareup.com/reference/square/labor-api/webhooks/labor.scheduled_shift.updated) - [labor.scheduled_shift.published](https://developer.squareup.com/reference/square/labor-api/webhooks/labor.scheduled_shift.published) - [labor.scheduled_shift.deleted](https://developer.squareup.com/reference/square/labor-api/webhooks/labor.scheduled_shift.deleted) - Only if publishing a shift that has `draft_shift_details.is_deleted` set to `true` when [deleting the published shift](#delete-scheduled-shifts). Each individual publish request in a bulk operation triggers these events. ### Next steps - [Update the scheduled shift](#update-scheduled-shifts) with more changes as needed. This might include [managing the team member assignment](#manage-team-member-assignments) or [deleting the shift](#delete-scheduled-shifts). {% anchor id="manage-team-member-assignments" /%} ## Managing team member assignments In a scheduled shift, the optional `team_member_id` field references the ID of the [team member](https://developer.squareup.com/reference/square/objects/TeamMember) assigned to the shift. >`"team_member_id": "K6JiNAakLUf5lUYmfjn"` You can assign a team member when you first [create the shift](#create-scheduled-shifts) or add, change, or clear the assignment when you [update the shift](#update-scheduled-shifts). To clear the team member assignment, set the value to `null`. >`"team_member_id": null` You must [publish the shift](#publish-scheduled-shifts) to make the latest draft details public. When published, Square notifies team members according to the `scheduled_shift_notification_audience` setting. ### Team members and job assignments Sellers can optionally record the jobs that team members are qualified to do. These jobs are stored in the `wage_setting.job_assignments` field, as shown in the following example: ```json { "team_member": { "id": "1yJlHapkseYnNPETIU1B", ... "wage_setting": { "team_member_id": "-3oZQKPKVk6gUXU_V5Qa", "job_assignments": [ { "job_title": "Server", "job_id": "FjS8x95cqHiMenw4f1NAUH4P", "pay_type": "HOURLY", "hourly_rate": { "amount": 1700, "currency": "USD" } }, { "job_title": "Cashier", "job_id": "VDNpRv8da51NU8qZFC5zDWpF", "pay_type": "HOURLY", "hourly_rate": { "amount": 2000, "currency": "USD" } } ], ... } } } ``` ### Find available team members To find team members that can work a particular job: 1. Call [ListJobs](https://developer.squareup.com/reference/square/team-api/list-jobs) to get the ID of the job to schedule (if needed). 2. Call [SearchTeamMembers](https://developer.squareup.com/reference/square/team-api/search-team-members) and filter by location ID and `ACTIVE` status. 3. Iterate through the results to build a job pool of team members who can do the job: 1. If `wage_setting` is present, iterate through `job_assignments`. 2. Compare the `job_id` field to the ID of the job to schedule. 3. Copy the ID of team members who can do the job. 4. Call [SearchScheduledShifts](https://developer.squareup.com/reference/square/labor-api/search-scheduled-shifts) to find the team members who are already working during the scheduled shift so that you can remove them from the job pool. ```` This query finds all published and assigned shifts that overlap with the specified time period at the specified location. The overlap is detected by finding shifts that start before the period ends (`start.end_at`) and end after the period begins (`end.start_at`). To find team members working on a specific calendar day, you can alternatively use the [workday filter](labor-api/retrieve-scheduled-shifts#workday-start-at), which is designed for date-based queries. {% anchor id="delete-scheduled-shifts" /%} ## Deleting scheduled shifts The `draft_shift_details.is_deleted` field is used to delete scheduled shifts. The deletion process differs based on whether the shift was previously published. ### Draft-only shifts For shifts that were never published, you need to [update the shift](#update-scheduled-shifts). 1. Call `UpdateScheduledShift` and set `draft_shift_details.is_deleted` to `true`. This operation performs a hard delete — the shift is permanently deleted and is no longer accessible through API endpoints. This operation triggers these [webhook events](webhooks/overview): - [labor.scheduled_shift.updated](https://developer.squareup.com/reference/square/labor-api/webhooks/labor.scheduled_shift.updated) - [labor.scheduled_shift.deleted](https://developer.squareup.com/reference/square/labor-api/webhooks/labor.scheduled_shift.deleted) ### Published shifts For shifts that were previously published, you need to [update the shift](#update-scheduled-shifts) and then [publish the shift](#publish-scheduled-shifts). 1. Call `UpdateScheduledShift` and set `draft_shift_details.is_deleted` to `true`. This operation marks the shift for deletion and triggers this [webhook event](webhooks/overview): - [labor.scheduled_shift.updated](https://developer.squareup.com/reference/square/labor-api/webhooks/labor.scheduled_shift.updated) In this state, the shift remains accessible and `draft_shift_details.is_deleted` can be set back to `false` to cancel the deletion. 2. Call `PublishScheduledShift` to make the change public. This operation performs a hard delete — the shift is permanently deleted and is no longer accessible through API endpoints. This operation triggers these webhook events: - [labor.scheduled_shift.updated](https://developer.squareup.com/reference/square/labor-api/webhooks/labor.scheduled_shift.updated) - [labor.scheduled_shift.deleted](https://developer.squareup.com/reference/square/labor-api/webhooks/labor.scheduled_shift.deleted) - [labor.scheduled_shift.published](https://developer.squareup.com/reference/square/labor-api/webhooks/labor.scheduled_shift.published) Square notifies team members according to the `scheduled_shift_notification_audience` setting. Note that the `ScheduledShift` object in the event data has different `is_deleted` settings: - For `labor.scheduled_shift.updated`: - `draft_shift_details.is_deleted` is `true` - `published_shift_details.is_deleted` is `false` - For `labor.scheduled_shift.published`: - `draft_shift_details.is_deleted` is `true` - `published_shift_details.is_deleted` is `true` ## See also * [Scheduling with the Labor API](labor-api/scheduling) * [Labor API](labor-api/what-it-does) * [Team API](team/overview) --- # Scheduling with the Labor API > Source: https://developer.squareup.com/docs/labor-api/scheduling > Status: BETA > Languages: All > Platforms: All Use the Square Labor API to create and manage team schedules. **Applies to:** [Labor API](labor-api/what-it-does) | [Team API](team/overview) | [Locations API](locations-api) | [Webhooks](webhooks/overview) | [GraphQL](devtools/graphql) {% subheading %}Learn how to manage team schedules using the Labor API.{% /subheading %} {% toc hide=true /%} ## Overview The Labor API provides scheduling capabilities that let third-party scheduling applications integrate with Square. You can manage team member schedules, synchronize schedule data between external systems and Square, and access data for labor forecasting, labor cost reporting, and optimizing staffing levels. The API supports: - Creating draft and published schedules. - Managing shift assignments and changes. - Searching scheduled hours by status, team member, and other parameters. - Notifying team members about schedule updates. Whether you're creating a standalone scheduling application or integrating with the Square Dashboard and Square Team application, you can use the Labor API to manage scheduled shifts. {% aside type="info" %} The Labor API defines two types of objects; be careful not to confuse them: - A [ScheduledShift](https://developer.squareup.com/reference/square/objects/ScheduledShift) is used to manage team schedules. - A [Timecard](https://developer.squareup.com/reference/square/objects/Timecard) represents a single work shift for a team member. Each `Timecard` captures: - The actual time worked during one shift - Information needed for payroll processing For example, if an employee works five different shifts in a week, they will have five separate Timecard records - one for each shift. **Note:** The older `Shift` object type is deprecated. Use Timecards instead. For information about migration, see [Migration notes](labor-api/what-it-does#migration-notes). {% /aside %} ## Requirements and limitations The following requirements and limitations apply when managing scheduled shifts using the Labor API. - Square API version 2025-05-21 or later is required to access or manage scheduled shifts. - The ability to schedule repeated shifts using templates isn't supported. - Scheduled shifts created by the API are visible in the **Staff** section of the Square Dashboard under **Scheduling** and in the Square Team application. Note the following: - Sellers must use [team permissions](https://squareup.com/help/article/5822-employee-permissions) in the **Shifts** category to allow other team members to access draft shifts. Published shifts are accessible by all team members. - Some scheduling features require that the seller has an active [Shifts Plus](https://squareup.com/staff/shifts/pricing) subscription. For example, shifts that are scheduled more than 10 days in advance using the Labor API are visible in the Square Dashboard to sellers without a subscription but these shifts cannot be updated. {% aside info="info" %} The Labor API provides unrestricted access to schedule management, so these requirements don't apply to third-party scheduling applications. {% /aside %} - The color scheme for a scheduled shift cannot be changed. This property isn't exposed on the `ScheduledShift` object. See additional [requirements and limitations](labor-api/manage-scheduled-shifts#requirements-and-limitations) for managing scheduled shifts. ## How scheduling works Team schedules are made up of individual scheduled shifts spanning a given time period. To create a scheduled shift, first provide draft shift details such as the job ID, location ID, team member assignment, and start and end times. You can update draft shift details as needed and then publish the shift to make your changes public. The following diagram shows the API call flow for managing a scheduled shift and the [webhook events](labor-api/webhooks) triggered by each request:  The simplest flow is from create to publish, but you can iteratively update and publish or republish. For more information about these flows, see [Manage Scheduled Shifts](labor-api/manage-scheduled-shifts). *If the shift was never published, setting `draft_shift_details.is_deleted` to `true` in an `UpdateScheduledShift` request deletes the shift. Otherwise, the shift is just marked for deletion and you must publish the change to delete it. {% aside type="info" %} The diagram doesn't include calls used to obtain the `location_id`, `team_member_id`, and `job_id` from other Square APIs or calls to `RetrieveScheduledShift` and `SearchScheduledShifts` for retrieving information about a scheduled shift. {% /aside %} The following key concepts apply to scheduling with the Labor API: {% accordion expanded=false %} {% slot "heading" %} ### Scheduled shifts contain draft and published shift details {% /slot %} You work with two primary objects for shift management: - [ScheduledShift](https://developer.squareup.com/reference/square/objects/ScheduledShift) - A specific time slot in a work schedule. This object is used to manage the draft and published versions of a shift: - `draft_shift_details` - Stores the latest draft shift details. - `published_shift_details` - Stores the current published shift details. This field is initially added when the scheduled shift is first published and then updated with every subsequent published version. After the initial publish, you can determine whether there are unpublished changes by comparing `draft_shift_details` to `published_shift_details`. - [ScheduledShiftDetails](https://developer.squareup.com/reference/square/objects/ScheduledShiftDetails) - The data type for the `draft_shift_details` and `published_shift_details` fields of a `ScheduledShift`. The following fields are required: `location_id`, `job_id`, `start_at`, and `end_at`. Only the latest draft shift details and current published shift details are accessible for a scheduled shift. {% /accordion %} {% accordion expanded=false %} {% slot "heading" %} ### Draft shift details are writable, published shift details are read only {% /slot %} Draft shift details are writable using the `CreateScheduledShift` and `UpdateScheduledShift` endpoints. To change the details for a published shift: 1. Call `UpdateScheduledShift` and provide the updated `draft_shift_details`. 2. Call `PublishScheduledShift` or `BulkPublishScheduledShifts` to make the changes public. When a scheduled shift is published, Square copies `draft_shift_details` to `published_shift_details`. {% /accordion %} {% accordion expanded=false %} {% slot "heading" %} ### Team member notifications are optionally sent on publish {% /slot %} The `PublishScheduledShift` and `BulkPublishScheduledShifts` endpoints provide a `scheduled_shift_notification_audience` parameter that lets you control whether Square notifies team members about new, changed, or deleted shifts. Notifications can be sent to affected team members only (default) or all active team members, or you can choose not to send notifications. Notifications are sent to the email address on record for [team members](https://developer.squareup.com/reference/square/objects/TeamMember). {% /accordion %} ## Scheduling endpoints The Labor API provides a set of endpoints used for managing team schedules. Applications using [OAuth](oauth-api/overview) to authorize API requests require the `TIMECARDS_WRITE` or `TIMECARDS_READ` permission to call scheduling endpoints. ### Managing scheduled shifts Use the following endpoints to [create and manage](labor-api/manage-scheduled-shifts) scheduled shifts. {% table %} * Endpoint * Permission {% width="145px" %} * Description ----- * [CreateScheduledShift](https://developer.squareup.com/reference/square/labor-api/create-scheduled-shift) * `TIMECARDS_WRITE` * Creates a scheduled shift by providing draft shift details such as job ID, team member assignment, and start and end times. ----- * [UpdateScheduledShift](https://developer.squareup.com/reference/square/labor-api/update-scheduled-shift) * `TIMECARDS_WRITE` * Updates the draft shift details for a scheduled shift. ----- * [PublishScheduledShift](https://developer.squareup.com/reference/square/labor-api/publish-scheduled-shift) * `TIMECARDS_WRITE` * Publishes a scheduled shift to make the current draft details visible to all team members. ----- * [BulkPublishScheduledShifts](https://developer.squareup.com/reference/square/labor-api/bulk-publish-scheduled-shifts) * `TIMECARDS_WRITE` * Publishes 1 – 100 scheduled shifts to make the current draft details visible to all team members. {% /table %} ### Retrieving scheduled shifts Use the following endpoints to [retrieve and search](labor-api/retrieve-scheduled-shifts) scheduled shifts. {% table %} * Endpoint * Permission {% width="135px" %} * Description ----- * [RetrieveScheduledShift](https://developer.squareup.com/reference/square/labor-api/retrieve-scheduled-shift) * `TIMECARDS_READ` * Retrieves a scheduled shift by ID. ----- * [SearchScheduledShifts](https://developer.squareup.com/reference/square/labor-api/search-scheduled-shifts) * `TIMECARDS_READ` * Retrieves a paginated list of scheduled shifts, with optional filter and sort settings. {% /table %} {% aside type="info" %} You can use the `scheduledShifts` entry point in [Square GraphQL](devtools/graphql) queries to access scheduling data through read-only operations. GraphQL queries can improve performance and reduce development time by letting you request exactly the data you need in fewer, more compact data transfers. {% /aside %} ## See also - [Manage Scheduled Shifts](labor-api/manage-scheduled-shifts) - [Retrieve Scheduled Shifts](labor-api/retrieve-scheduled-shifts) --- # Retrieve Scheduled Shifts > Source: https://developer.squareup.com/docs/labor-api/retrieve-scheduled-shifts > Status: BETA > Languages: All > Platforms: All Use the Square Labor API to retrieve scheduled shifts and search for shifts by location, team member, date range, and assignment status. **Applies to:** [Labor API](labor-api/what-it-does) | [Team API](team/overview) | [Locations API](locations-api) {% subheading %}Use the [Labor API](https://developer.squareup.com/reference/square/labor-api) to search and retrieve scheduled shifts with flexible filtering options.{% /subheading %} {% toc hide=true /%} ## Overview Applications can use the Labor API to search and retrieve scheduled shifts. You can search shifts using various filters, including date ranges, locations, team members, assignment status, and published status. {% aside type="info" %} The Labor API defines two types of objects; be careful not to confuse them: - A [ScheduledShift](https://developer.squareup.com/reference/square/objects/ScheduledShift) is used to manage team schedules. - A [Timecard](https://developer.squareup.com/reference/square/objects/Timecard) represents a single work shift for a team member. Each `Timecard` captures: - The actual time worked during one shift - Information needed for payroll processing For example, if an employee works five different shifts in a week, they will have five separate Timecard records - one for each shift. **Note:** The older `Shift` object type is deprecated. Use Timecards instead. For information about migration, see [Migration notes](labor-api/what-it-does#migration-notes). {% /aside %} ## Requirements and limitations The following requirements and limitations apply when retrieving scheduled shifts using the Labor API. * **Square API version** - Square API version 2025-05-21 or later is required to access or manage scheduled shifts. * **Valid access token** - Square API requests require an `Authorization` header with a bearer access token. The examples in this topic use the {% tooltip text="Square Sandbox" %}An isolated server environment used for testing. API calls in the Sandbox use the "connect.squareupsandbox.com" domain and require an access token that's valid for Sandbox.{% /tooltip %}, so you can test using your [Sandbox access token](build-basics/access-tokens#get-a-personal-access-token). {% accordion expanded=false %} {% slot "heading" %} #### More details {% /slot %} Applications using [OAuth](oauth-api/overview) access tokens to authorize requests typically require the following permissions when working with team schedules: - `MERCHANT_PROFILE_READ` to retrieve location information. - `EMPLOYEES_READ` to retrieve team member and job information. - `TIMECARDS_READ` to retrieve scheduled shifts. - `TIMECARDS_WRITE` to create, update, and publish scheduled shifts. To call Square APIs in the production environment, change the base URL to `https://connect.squareup.com` and use your production (personal) access token. {% /accordion %} ## Retrieve a specific shift To retrieve a specific scheduled shift, call [RetrieveScheduledShift](https://developer.squareup.com/reference/square/labor-api/retrieve-scheduled-shift) and provide the shift ID. {% tabset %} {% tab id="Request" %} ```` {% /tab %} {% tab id="Response" %} The response returns the requested scheduled shift. ```json { "scheduled_shift": { "id": "kxR8FWJE8DT3x7", "draft_shift_details": { "location_id": "KACM8A41GR60X", "job_id": "jxR8EWJE8DT2x7", "team_member_id": "-3oZQKPKVk6gUXU_V5Qa", "start_at": "2025-04-15T09:00:00-07:00", "end_at": "2025-04-15T17:00:00-07:00", "notes": "Opening shift", "is_deleted": false, "timezone": "America/Los_Angeles" }, "published_shift_details": { "location_id": "KACM8A41GR60X", "job_id": "jxR8EWJE8DT2x7", "team_member_id": "-3oZQKPKVk6gUXU_V5Qa", "start_at": "2025-04-15T09:00:00-07:00", "end_at": "2025-04-15T17:00:00-07:00", "notes": "Opening shift", "is_deleted": false, "timezone": "America/Los_Angeles" }, "version": 2, "created_at": "2025-04-02T14:58:49-07:00", "updated_at": "2025-04-02T15:00:00-07:00" } } ``` {% /tab %} {% /tabset %} ## Search scheduled shifts To search for scheduled shifts, call [SearchScheduledShifts](https://developer.squareup.com/reference/square/labor-api/search-scheduled-shifts) with optional filter and sort settings. By default, results are sorted by `start_at` in ascending order. To list all scheduled shifts, call `SearchScheduledShifts` without any filters: {% tabset %} {% tab id="Request" %} ```` {% /tab %} {% tab id="Response" %} ```json { "scheduled_shifts": [ { "id": "kxR8FWJE8DT3x7", "draft_shift_details": { "location_id": "KACM8A41GR60X", "job_id": "jxR8EWJE8DT2x7", "team_member_id": "-3oZQKPKVk6gUXU_V5Qa", "start_at": "2025-04-15T09:00:00-07:00", "end_at": "2025-04-15T17:00:00-07:00", "notes": "Opening shift", "is_deleted": false, "timezone": "America/Los_Angeles" }, "published_shift_details": { "location_id": "KACM8A41GR60X", "job_id": "jxR8EWJE8DT2x7", "team_member_id": "-3oZQKPKVk6gUXU_V5Qa", "start_at": "2025-04-15T09:00:00-07:00", "end_at": "2025-04-15T17:00:00-07:00", "notes": "Opening shift", "is_deleted": false, "timezone": "America/Los_Angeles" }, "version": 2, "created_at": "2025-04-02T14:58:49-07:00", "updated_at": "2025-04-02T15:00:00-07:00" }, { "id": "kxR8FWJE8DT4x8", "draft_shift_details": { "location_id": "KACM8A41GR60X", "job_id": "jxR8EWJE8DT2x7", "team_member_id": "-4pZQKPKVk6gUXU_V5Qb", "start_at": "2025-04-15T17:00:00-07:00", "end_at": "2025-04-16T01:00:00-07:00", "notes": "Closing shift", "is_deleted": false, "timezone": "America/Los_Angeles" }, "published_shift_details": { "location_id": "KACM8A41GR60X", "job_id": "jxR8EWJE8DT2x7", "team_member_id": "-4pZQKPKVk6gUXU_V5Qb", "start_at": "2025-04-15T17:00:00-07:00", "end_at": "2025-04-16T01:00:00-07:00", "notes": "Closing shift", "is_deleted": false, "timezone": "America/Los_Angeles" }, "version": 1, "created_at": "2025-04-02T14:58:49-07:00", "updated_at": "2025-04-02T14:58:49-07:00" } ] } ``` {% /tab %} {% /tabset %} Use the optional `limit` field to control the {% tooltip text="page size" %}The maximum number of results to return in a single paged response.{% /tooltip %} (default 50, maximum 50) and the `cursor` field to page through the results. For more information, see [Pagination](build-basics/common-api-patterns/pagination). ### Filter and sort options The following filter options can be used in a search query. Multiple filters in a query are combined as an `AND` operation, which means shifts must match all specified filters to be included in the results. For information about how some filters interact, see [Filter combinations](#filter-combinations). {% table %} * Field {% width="220px" %} * Description --- * `assignment_status` * Returns shifts based on whether the `team_member_id` field is populated in the draft or published shift details. Valid values are `ASSIGNED` or `UNASSIGNED`. When omitted, it returns shifts with both statuses. --- * `location_ids` * Returns shifts for the specified locations. When omitted, it returns shifts for all locations. If needed, call [ListLocations](https://developer.squareup.com/reference/square/locations-api/list-locations) to get location IDs. --- * `scheduled_shift_statuses` * Returns shifts based on the draft or published status of the shift. A published shift has `published_shift_details`. Draft shifts either don't have that field or it differs from the draft details. Valid values are `DRAFT` or `PUBLISHED`. When omitted, it returns shifts with both statuses. --- * `start` * Returns shifts whose `start_at` time is within the specified time range (inclusive). --- * `end` * Returns shifts whose `end_at` time is within the specified time range (inclusive). --- * `team_member_ids` * Returns shifts assigned to the specified team members. When omitted, it returns all assigned and unassigned shifts. If needed, call [SearchTeamMembers](https://developer.squareup.com/reference/square/team-api/search-team-members) to get team member IDs. --- * `workday` * Returns shifts based on a workday date range. For more information, see [Workday filter](#workday-filter). {% /table %} Use the following fields to sort the query. By default, results are sorted by `START_AT` and `ASC`. {% table %} * Field {% width="120px" %} * Description --- * `field` * The field to sort by. Valid values are `START_AT` (default), `END_AT`, `CREATED_AT`, or `UPDATED_AT`. --- * `order` * The sort order. Valid values are `DESC` (default) or `ASC`. {% /table %} #### Workday filter The `workday` filter provides a way to search scheduled shifts using `YYYY-MM-DD` calendar dates. You define whether to match shifts based on their start time, their end time, or either their start time or end time. ```json "query": { "filter": { "workday": { "match_shifts_by": "START_AT", "date_range": { "start_date": "2024-10-14", "end_date": "2024-10-27" } } } } ``` This filter has three components: {% table %} * Component * Description --- * `date_range` * Specifies the `start_date` and `end_date` of the date range to match against. --- * `match_shifts_by` * Controls how shifts are matched against the date range. Valid values: - `START_AT` - Match shifts that start within the date range. - `END_AT` - Match shifts that end within the date range. - `INTERSECTION` - Match shifts that start or end within the date range. --- * `default_timezone` * The time zone to use for date matching, in IANA format (for example, "America/Los_Angeles"). Required if the shift location doesn't define a time zone. {% /table %} {% anchor id="filter-combinations" /%} #### Filter combinations When combined in a query, certain filters might interact in ways that aren't immediately apparent. Understanding the interactions between the following combinations can help you create more effective queries: * **Team member and assignment status**: - Using `team_member_ids` alone returns shifts for the specified team members plus all unassigned shifts. - Add `assignment_status: "ASSIGNED"` to only get shifts assigned to the specified team members. * **Shift status and assignment status**: - The `scheduled_shift_statuses` filter affects which shift details are checked: - `DRAFT` - Evaluates `draft_shift_details` (includes unpublished shifts and shifts with modified drafts). - `PUBLISHED` - Evaluates `published_shift_details`. - `DRAFT` and `PUBLISHED` (default) - Evaluates both sets of details. - The assignment status is evaluated against the specified shift details. - Shifts marked for deletion (`is_deleted: true`) are ignored with the `DRAFT` status filter. * **Date range filters**: - The `start` and `end` filters use exact timestamps. - The `workday` filter uses calendar dates and can match shifts based on different criteria. - When both are specified, shifts must match both conditions. ### Example: Published shifts at a specified location and date range The following example finds all published shifts that start during a specific time window at the specified location: {% tabset %} {% tab id="Request" %} ```` {% /tab %} {% tab id="Response" %} ```json { "scheduled_shifts": [ { "id": "K0YH4CV5462JB", "draft_shift_details": { "team_member_id": "TMDHAOU2N4I8RWi2", "location_id": "PAA1RJZZKXBFG", "job_id": "gad1UVkdSodJ1F4PCzoA8y6d", "start_at": "2025-01-25T03:00:00-05:00", "end_at": "2025-01-25T09:00:00-05:00", "notes": "Don't forget to prep the vegetables and pull the dough", "is_deleted": false, "timezone": "America/New_York" }, "published_shift_details": { "team_member_id": "TMDHAOU2N4I8RWi2", "location_id": "PAA1RJZZKXBFG", "job_id": "gad1UVkdSodJ1F4PCzoA8y6d", "start_at": "2025-01-25T03:00:00-05:00", "end_at": "2025-01-25T09:00:00-05:00", "notes": "Don't forget to prep the vegetables and pull the dough", "is_deleted": false, "timezone": "America/New_York" }, "version": 4, "created_at": "2024-12-31T03:11:24Z", "updated_at": "2024-12-31T06:52:01Z" } ] } ``` {% /tab %} {% /tabset %} {% anchor id="workday-start-at" /%} ### Example: Workday matching START_AT The following example gets all published and assigned shifts on the specified date at the specified location, matching shifts based on their start time: {% tabset %} {% tab id="Request" %} ```` {% /tab %} {% tab id="Response" %} ```json { "scheduled_shifts": [ { "id": "K0YH4CV5462JB", "draft_shift_details": { "team_member_id": "TMDHAOU2N4I8RWi2", "location_id": "PAA1RJZZKXBFG", "job_id": "gad1UVkdSodJ1F4PCzoA8y6d", "start_at": "2025-01-25T03:00:00-05:00", "end_at": "2025-01-25T09:00:00-05:00", "notes": "Don't forget to prep the vegetables and pull the dough", "is_deleted": false, "timezone": "America/New_York" }, "published_shift_details": { "team_member_id": "TMDHAOU2N4I8RWi2", "location_id": "PAA1RJZZKXBFG", "job_id": "gad1UVkdSodJ1F4PCzoA8y6d", "start_at": "2025-01-25T03:00:00-05:00", "end_at": "2025-01-25T09:00:00-05:00", "notes": "Don't forget to prep the vegetables and pull the dough", "is_deleted": false, "timezone": "America/New_York" }, "version": 4, "created_at": "2024-12-31T03:11:24Z", "updated_at": "2024-12-31T06:52:01Z" } ] } ``` {% /tab %} {% /tabset %} ## See also * [Scheduling with the Labor API](labor-api/scheduling) * [Manage Scheduled Shifts](labor-api/manage-scheduled-shifts) * [Labor API](labor-api/what-it-does) * [Team API](team/overview) --- # Square Model Context Protocol Server > Source: https://developer.squareup.com/docs/mcp > Status: BETA > Languages: All > Platforms: All Learn about the Square MCP server, which implements the MCP standard and provides a bridge between AI assistants and the Square REST API platform. {% subheading %}The Square Model Context Protocol (MCP) server implements the [Model Context Protocol](https://modelcontextprotocol.io) standard, providing a bridge between AI assistants and the Square REST API platform.{% /subheading %} ## Overview {% aside type="info" %} The Square MCP server is currently in Beta. To provide feedback or report bugs, open [GitHub issues](https://github.com/square/square-mcp-server/issues). {% /aside %} To view the MCP server source or contribute to the project, see [GitHub - square/square-mcp-server](https://github.com/square/square-mcp-server/). You can configure your AI agent to use the Square MCP server hosted by Block as a remote server or you can set up and run the server locally for your AI agent's use. For information about running the Square MCP server locally, see [Local Square MCP server quickstart](#local-server-quickstart). ## Square MCP server The Square remote MCP server is hosted at: ``` https://mcp.squareup.com/sse ``` ### MCP server tools The Square MCP server connects your AI tools directly to the full Square API platform, giving you programmatic access to everything the APIs offer—customers, orders, items, and more. Use it to power natural language queries, generate code, and accelerate development with AI assistance. Square recommends using the remote MCP server, which supports OAuth login with more control and granular permissions. This lets you securely sign in with your Square account and authorize only the scopes your application needs, with no manual token management required. For testing, you can run a local instance configured to access a seller's Sandbox environment. Remote instances only access production resources. {% aside type="important" %} Square recommends that you become familiar with the server and test your prompts against a Square account Sandbox environment before using it in production. {% /aside %} ## Integration with AI assistants The Square MCP server works with many different AI assistants including: * [Claude.ai](https://claude.ai) * [Claude Desktop](https://claude.ai/download) * [Goose](https://block.github.io/goose/) * [Cursor](https://www.cursor.com/) * [Windsurf](https://codeium.com/windsurf) ## Integrate with Claude.ai Add the Square MCP server to Claude. ### For Claude Enterprise & Teams (owners and primary owners) 1. Navigate to **Settings > Integrations** (for Teams) or **Settings > Data management** (for Enterprise). 1. In the **Integrations** section, choose **Add more**. 1. Add the Square MCP server URL: `https://mcp.squareup.com/sse`. 1. To finish configuring your integration, choose **Add**. ### For Claude Max 1. Navigate to **Settings > Profile**. 1. In the **Integrations** section, choose **Add more**. 1. Add the Square MCP server URL: `https://mcp.squareup.com/sse`. 1. To finish configuring your integration, choose **Add**. ### Enable the Square integration 1. Open a chat with Claude. 2. In the chat interface, access the **Search and tools** menu. 3. Choose **Connect** to authenticate with your Square account. 4. After connecting, use the same menu to enable specific Square tools. ## Integrate with Claude Desktop ### Remote MCP Add an entry to `claude_desktop_config.json` as shown in the following example: ```json { "mcpServers": { "mcp_square_api": { "command": "npx", "args": ["mcp-remote", "https://mcp.squareup.com/sse"] } } } ``` This approach allows you to authenticate directly with your Square account credentials without needing to manage access tokens. ### Local MCP Add an entry to `claude_desktop_config.json` as shown in the following example: ```json { "mcpServers": { "mcp_square_api": { "command": "npx", "args": ["square-mcp-server", "start"], "env": { "ACCESS_TOKEN": "YOUR_SQUARE_ACCESS_TOKEN", "SANDBOX": "true" } } } } ``` ## Integrate with Goose [Install Goose](https://block.github.io/goose/docs/getting-started/installation) and then configure the Square MCP server as an extension for the Goose Desktop and Goose CLI. The Goose Desktop and CLI share the same configuration file so you only need to configure the MCP server once. ### Remote MCP server To install the Square remote MCP server in Goose, click the [Open Goose](goose://extension?cmd=npx&arg=mcp-remote&arg=https%3A%2F%2Fmcp.squareup.com%2Fsse&id=square_mcp_production_remote&name=Square%20MCP%20Remote&description=Square%20Production%20MCP%20Remote) link on any computer with Goose installed, choose **Open Goose**, and then choose **OK** to confirm the extension installation. You can also copy and paste the URL into your browser's address bar. ### Local MCP server To install the Square remote MCP server in Goose, run the following command: ```bash npx square-mcp-server install # Auto-install local MCP for Goose npx square-mcp-server get-goose-url # Output a manual config URL ``` To learn more about using the Square MCP server with Goose, see [Square MCP Extension](https://block.github.io/goose/docs/tutorials/square-mcp). ## Integrate with Cursor To connect [Cursor](https://www.cursor.com/) with the Square MCP server, following the instructions for a remote or local MCP server. ### Remote MCP server Choose `Type`: "Command" and in the `Command` field, enter the following (recommended for OAuth authentication): ``` npx mcp-remote https://mcp.squareup.com/sse ``` This allows you to log in with your Square account directly instead of managing access tokens. ### Local MCP server Choose `Type`: "Command" and in the `Command` field, enter the following: ``` npx square-mcp-server start ``` ## Integrate with Windsurf Connect the Square MCP server to [Windsurf](https://codeium.com/windsurf) by editing the [`mcp_config.json` file](https://docs.codeium.com/windsurf/mcp): ### Remote MCP server ```json { "mcpServers": { "square_api": { "command": "npx", "args": ["mcp-remote", "https://mcp.squareup.com/sse"] } } } ``` Using the remote MCP server enables OAuth authentication, allowing you to connect with your Square account credentials without managing access tokens. {% aside type="info" %} Square maintains an allowlist of MCP clients in order to protect against malicious client registration attempts. If you want your client added to the allowlist, please request an addition in the [Square developer forum](https://developer.squareup.com/forums/c/mcp/14). {% /aside %} ### Local MCP server ```json { "mcpServers": { "square_api": { "command": "npx", "args": ["square-mcp-server", "start"], "env": { "ACCESS_TOKEN": "YOUR_SQUARE_ACCESS_TOKEN", "SANDBOX": "true" } } } } ``` --- {% anchor id="local-server-quickstart" /%} ## Local Square MCP server quickstart To run a local instance, you need to have [Node.js](https://nodejs.org/en/download) installed. You can then set up and run the Square MCP server with a single command: ```bash npx square-mcp-server start ``` To configure environment variables inline: ```bash ACCESS_TOKEN=YOUR_SQUARE_ACCESS_TOKEN SANDBOX=true npx square-mcp-server start ``` To configure environment variables for local development: ```bash npx /path/to/project/square-mcp-server ``` {% aside type="info" %} You need your Square access token. For more information, see [Access Tokens and Other Square Credentials](https://developer.squareup.com/docs/build-basics/access-tokens). {% /aside %} ### Local configuration Set the following environment variables as needed: | Variable | Purpose | Example | |----------------------|--------------------------------------|--------------------------------| | `ACCESS_TOKEN` | Your Square API access token | `ACCESS_TOKEN=sq0atp-...` | | `SANDBOX` | Use the Square sandbox environment | `SANDBOX=true` | | `PRODUCTION` | Use the Square production environment | `PRODUCTION=true` | | `DISALLOW_WRITES` | Restrict to read-only operations | `DISALLOW_WRITES=true` | | `SQUARE_VERSION` | Specify the Square API version | `SQUARE_VERSION=2025-04-16` | --- # Python SDK Migration Guide > Source: https://developer.squareup.com/docs/sdks/python/migration > Status: PUBLIC > Languages: Python > Platforms: All Upgrade from the legacy version of the Square Python SDK to version 42.0.0 or later. Version `42.0.0.20250416` of the Python SDK represents a full rewrite of the SDK with a number of breaking changes, outlined in the following sections. When upgrading to this version or later versions, take note of the changes in the SDK, including client construction and parameter names. If necessary, you can use the legacy version of the SDK along with the latest version. SDK versions `41.0.0.20250319` and earlier continue to function as expected, but you should plan to update your integration as soon as possible to ensure continued support. ## Client construction The new version of the Python SDK introduces breaking changes in the client class. ```python // LEGACY import os from square_legacy.client import Client as LegacySquare client = LegacySquare( environment="sandbox", access_token=os.environ.get("SQUARE_TOKEN") ) // NEW import os from square import Square from square.environment import SquareEnvironment client = Square( environment=SquareEnvironment.SANDBOX, token=os.environ.get("SQUARE_TOKEN") ) ``` {% table %} * Legacy {% width="165px" %} * New {% width="180px" %} * Additional information --- * `environment` * `environment` * An enumeration for specifying the environment (`SANDBOX` or `PRODUCTION`). --- * `access_token` * `token` * The access token used for authentication. --- * `custom_url` * `base_url` * The base URL for API requests. --- * `square_version` * `version` * The Square API version to use. --- * `additional_headers` * `additional_headers` * Additional headers can be set at individual methods. --- * `http_call_back` * Removed * This option has been removed. --- * `user_agent_detail` * Removed * This option has been removed. {% /table %} ## Use the legacy and new SDK side by side While the new SDK has many improvements, it takes time to upgrade when there are breaking changes. To make the migration easier, the old SDK is published as `squareup_legacy` so that the two SDKs can be used side by side in the same project. The following example shows how to use the new and legacy SDKs inside a single file: ```python from square_legacy.client import Client as LegacySquare from square import Square def main(): client = Square(token=os.environ.get("SQUARE_TOKEN")) legacy_client = LegacySquare(access_token=os.environ.get("SQUARE_TOKEN")) # List all payments with the new client. new_response = client.payments.list() # List all payments with the legacy client. legacy_response = legacy_client.payments.list_payments() ``` You should migrate to the new SDK using the following steps: 1. Upgrade the PyPi package to `^42.0.0`. 2. Run `pip install squareup_legacy`. 3. Search and replace all requires and imports from `square` to `square_legacy`. 4. Gradually introduce methods from the new SDK by importing them from the Square import. ## TypedDict requests The previous version of the SDK relied on simple Python dictionaries to represent request body properties. For example, consider the following method call required to create a customer: ```python // LEGACY response = client.customers.create_customer( { "idempotency_key": str(uuid.uuid4()), "given_name": 'given-vs1', "family_name": 'family-vs1', "address": { "address_line_1": '1955 Broadway', "address_line_2": 'Oakland, CA 94612' } ) ``` In the new SDK, requests are now defined with [`TypedDict`](https://typing.python.org/en/latest/spec/typeddict.html#typeddict) types. The UX is similar, but you're now presented with `**kwargs` for each parameter to better guide you through the expected set of parameters. ```python // NEW response = client.customers.create( idempotency_key=str(uuid.uuid4()), given_name="given-vs1", family_name="family-vs1", address={ "address_line_1": "1955 Broadway", "address_line_2": "Oakland, CA 94612" } ) ``` ## Pydantic responses The new SDK uses [Pydantic](https://docs.pydantic.dev), a popular Python validation library, to define response models. With this, response types now have a better autocompletion experience and properties can be accessed with dot-notation rather than free-form dictionary access. ```python // LEGACY response = client.locations.list_locations() location = response.body['locations'][0] name = location['name'] // NEW response = client.locations.list() location_name = response.locations[0] name = location.name ``` ## Exceptions The legacy SDK represents successful calls and errors with the same `ApiResponse` type. Users could distinguish between successes and errors as shown: ```python // LEGACY response = client.locations.list_locations() if response.is_success(): for location in response.body['locations']: print(f"{location['id']}: ", end="") print(f"{location['name']}, ", end="") print(f"{location['address']['address_line_1']}, ", end="") print(f"{location['address']['locality']}") elif response.is_error(): for error in response.errors: print(error['category']) print(error['code']) print(error['detail']) ``` The new SDK represents errors as `Exceptions`, all of which inherit from the `ApiError` class. The `ApiError` class includes the Square `Error` type. ```python from square.core.api_error import ApiError try: response = client.locations.list() for location in response.items: print(f"{location.id}: ", end="") print(f"{location.name}, ", end="") print(f"{location.address.address_line_1}, ", end="") print(f"{location.address.locality}") except ApiError as e: for error in e.errors: print(error.category) print(error.code) print(error.detail) ``` ## Auto-pagination Square's paginated endpoints now support auto-pagination with an iterator. Callers don't need to manually fetch the next page. You can now iterate over the entire list and the client automatically fetches the next page behind the scenes. The following example shows the same code, with the legacy and new SDKs: ```python // LEGACY result = client.customers.list_customers( limit=10, sort_field='DEFAULT', sort_order='DESC' ) if result.is_success(): while (result.body != {}): for cust in result.body['customers']: print(f"customer: ID: {cust['id']}, ", end="") print(f"Version: {cust['version']}, ", end="") print(f"Given name: {cust['given_name']}, ", end="") print(f"Family name: {cust['family_name']}") if (result.cursor): result = client.customers.list_customers( limit=limit, sort_field='DEFAULT', sort_order='DESC', cursor=result.cursor ) else: break elif result.is_error(): for error in result.errors: print(error['category']) print(error['code']) print(error['detail']) // NEW try: response = client.customers.list( limit=10, sort_field='DEFAULT', sort_order='DESC' ) for customer in response: print(f"customer: ID: {customer.id}, ", end="") print(f"Version: {cutomer.version}, ", end="") print(f"Given name: {customer.given_name}, ", end="") print(f"Family name: {customer.family_name}") except ApiError as e: for error in e.errors: print(error.category) print(error.code) print(error.detail) ``` --- # Develop for Japan > Source: https://developer.squareup.com/docs/international-development/japan > Status: PUBLIC > Languages: All > Platforms: All Learn about regional differences and requirements related to building integrations for Japanese sellers. ## Overview You can use Square APIs and SDKs to build applications that support and process in-person or online payments for sellers in Japan. Before you begin development, be sure to familiarize yourself with which [payment methods](payment-card-support-by-country) are available in Japan and the [pricing models](payments-pricing) for payments made with Square APIs and Square hardware. ## In-Person Payment Solutions ### Integration Options for Your POS System If you have an existing POS system and want to connect to Square in-person payment solutions in Japan, you have two integration options: * The [Terminal API](terminal-api/overview) is compatible with Square Terminal devices. It connects directly to Square servers, making it available for almost any system, including Windows-based systems, web applications, or other platforms, because it connects directly to Square servers. * The [Point-of-Sale API](pos-api/what-it-does) works with Square Reader devices and operates via mobile devices only. It uses the Square mobile app for checkout, utilizing deep link functionality to switch between your app and Square. #### Key considerations when choosing an API: * If your goal is to process itemized orders, the Terminal API is your only option. * The Point-of-Sale API supports only custom amount-based transactions. * If your external POS system manages itemized orders, syncing itemized sales with Square can allow you to: * Print itemized receipts. * Keep itemized transactions synchronized between systems. When implementing in-person payment applications, you should process payments in-store with the [Square Terminal](https://squareup.com/jp/ja/hardware/terminal) or [Square Reader](https://squareup.com/jp/ja/hardware/reader) devices. Staff must be present in the store or close enough to attend at any time when payment processing is required. ### Itemized vs. Non-Itemized Approaches When integrating with Square in-person payment APIs, you'll need to choose between two integration approaches based on your business needs: {% tabset %} {% tab id="Itemized" %} This approach is best for merchants who want to log itemized transactions in Square and want the Square Terminal or Square's compatible printers to print itemized receipts. **How it works**: * Use the [Orders API](orders-api/what-it-does) to create orders with line items. * Pass the Square order ID to the [Terminal API](terminal-api/overview) to process payments and print itemized receipts. * Use the [Catalog API](catalog-api/what-it-does) to sync products between your system and Square. * Optionally, use [Terminal Actions](terminal-api/advanced-features) to programmatically print receipts as a separate operation. **Limitations**: * Formal receipts are not currently supported - only normal receipts can be printed. This approach is ideal when you want Square Terminal to handle receipt printing, want to leverage Square's reporting and analytics capabilities, or need product-level sales tracking for your business. {% /tab %} {% tab id="Non-Itemized (Custom Amount)" %} This approach is best suited for merchants with a custom POS system and existing printer integration for order management. **How it works**: You send only the total payment amount to Square. Square processes the payment but does not store details of the individual items. Only the custom payment amount is recorded. **Limitations**: * Square cannot track or print itemized receipts for these transactions. * If you need detailed transaction records, you must print receipts using your own receipt printing system. {% /tab %} {% /tabset %} ### Supported Payment Methods for In-Person Payments Japanese merchants can accept several payment methods for in-person transactions, including credit cards, digital wallet payments (Apple Pay and Google Pay), gift cards, e-money, prepaid IC cards (such as SUICA and PASMO), and QR code payments (such as PayPay, Alipay, Rakuten Pay, and au PAY). {% aside type="important" %} To support e-money or PayPay payments, Japanese sellers must submit an application form to activate those payment methods. For more information, see: - [Apply for Electronic Money](https://squareup.com/help/jp/ja/article/6711-jp-only-e-money-application) - [Apply for QR Code](https://squareup.com/help/jp/ja/article/8370-apply-for-qr-code-payments) {% /aside %} ### API Comparison for In-Person Payments {% table %} * Feature * Terminal API * Point-of-Sale API * Mobile Payments SDK --- * Device Type * Square Terminal * Square Reader * Square Reader --- * Connection Method * Connection to Square server * Link to Square Point of Sale app * Direct SDK integration --- * Itemized Orders Support * ✅ Supported (with Orders API) * ❌ Not supported * - --- * Custom Amount Support * ✅ Supported * ✅ Supported * - --- * Accepted Payment Methods * credit card, e-money, QR, gift card * credit card, e-money, QR, gift card * - --- * SDK/API Status in Japan * ✅ Available * ✅ Available * ❌ Not yet available --- * Documentation * [Terminal API Overview](terminal-api/overview) * [Point of Sale API Overview](pos-api/what-it-does) * [Mobile Payments SDK Overview](mobile-payments-sdk) {% /table %} ### Testing In-Person Payment Integrations Square provides a Sandbox environment where you can test your in-person payment integrations before going live. However, there are important differences between Sandbox and production testing for physical devices. #### Sandbox Testing (Without Physical Devices): You can test Terminal API integrations in the Sandbox without connecting to a physical Square Terminal. Square provides special test device IDs that simulate different checkout scenarios, including successful payments, canceled payments, and timeout scenarios. This allows you to develop and test your integration logic without needing physical hardware. The Sandbox environment supports testing various payment methods specific to Japan, including: * **E-money (FELICA) payments** - Test e-money/FeLiCa transactions using dedicated test device IDs * **QR code payments (PayPay)** - Test PayPay QR code payments (note: the Sandbox location must be based in Japan) **Example Test Device IDs:** {% table %} * Payment Method * Test Device ID * Notes --- * Credit Card (Successful) * `9fa747a2-25ff-48ee-b078-04381f7c828f` * Checkout successfully completed with credit card. --- * E-money (FELICA) * `19a01fbd-3dcd-4d9f-a499-a641684af745` * eMoney/FeLiCa payment for the full amount is approved. --- * QR Code (PayPay) * `cae0ee02-f83b-11ec-b939-0242ac120002` * PayPay QR code payment for the full amount is approved. Sandbox location must be based in Japan {% line-break /%} __Note:__ In the sandbox environment, PayPay is the only QR code brand name that appears. {% /table %} For a complete list of test device IDs and their behaviors, see [Terminal API Sandbox Testing](devtools/sandbox/testing#terminal-api-checkouts). #### Production Testing (With Physical Devices): Testing with an actual Square Terminal device is only supported in the production environment. The Sandbox environment does not support connections to physical Square Terminal devices. When testing with a physical device in production, you can use the minimum payment amount of 1 yen to minimize testing costs. Use the following workflow for testing: 1. Conduct initial development and testing in Sandbox using test device IDs. 2. Perform final testing in production using a Square Terminal and small payment amounts (minimum 1 yen). For more information, see [Test Square APIs in the Sandbox](devtools/sandbox/testing). ## Online Payment Solutions ### Payments Made on a Buyer's Screen For online payments in Japan, merchants can accept credit cards, debit cards, gift cards, and digital wallet payments (Apple Pay and Google Pay) when customers purchase through a web browser or mobile app. You can build custom checkout forms using either the [Web Payments SDK](web-payments/overview) or [In-App Payments SDK](in-app-payments-sdk/what-it-does), and you can [localize these forms](in-app-payments/localize-ios) from English to Japanese to provide a better customer experience. * Use the **Web Payments SDK** for web applications or browser-based checkouts (desktop and mobile). * Use the **In-App Payments SDK** for native mobile applications on iOS and Android. ### 3D Secure (3DS) Requirement for Online Payments {% aside type="important" %} This is a critical requirement for all merchants operating in Japan. [3D Secure (3DS) authentication](sca-overview) is **mandatory** for all online card payments in Japan. All merchants and integrations must comply with this regulation. {% /aside %} **What does this mean for your integration?** * Depending on the card issuer, 3DS authentication may be required during card transactions. * Use the [Web Payments SDK](web-payments/take-card-payment) or [In-App SDK](sca-overview-iap) to implement the 3DS flow. * Merchants cannot disable or opt out of 3DS in Japan. ## Sales Reporting ### FELICA Payments In a [Payment](https://developer.squareup.com/reference/square/objects/Payment) object, gift cards, credit cards, debit cards, and e-money (FELICA) payments are all recorded with a `source_type` of `CARD`. Detailed information is available through the `card_details` field. [Terminal checkout objects](https://developer.squareup.com/reference/square/objects/TerminalCheckout) also use the `CheckoutOptionsPaymentType` enum to denote whether the payment was made with a card, e-money, or QR. To retrieve payment information, you can use the [GetTerminalCheckout]((https://developer.squareup.com/reference/square/square/terminal-api/get-terminal-checkout)), [RetrieveOrder](https://developer.squareup.com/reference/square/square/orders-api/retrieve-order), or [GetPayment](https://developer.squareup.com/reference/square/square/payments-api/get-payment) endpoints depending on your needs. The `card_details` object includes a `card_brand` enum that helps you distinguish between different payment methods. * Credit cards show the international brand name (such as `VISA` or `Mastercard`). * E-money payments show `FELICA`. The `electronic_money_details.felica_details` field shows details specific to FeliCa payments, including `felica_brand` (either `FELICA_QP`, `FELICA_TRANSPORTATION`, `FELICA_ID`, or `UNKNOWN`). {% accordion expanded=false%} {% slot "heading" %} ### Example Payment object {% /slot %} ```json { "payment": { "id": "{PAYMENT_ID}", "created_at": "2023-10-13T21:14:29.577Z", "updated_at": "2023-10-13T21:14:30.504Z", "amount_money": { "amount": 5000, "currency": "JPY" }, "source_type": "CARD", "card_details": { "status": "CAPTURED", "card": { "card_brand": "FELICA" } } } } ``` {% /accordion %} {% accordion expanded=false%} {% slot "heading" %} ### Example Order object {% /slot %} ```json { "order": { "id": "{ORDER_ID}", "created_at": "2023-10-13T21:15:54.577Z", "updated_at": "2023-10-13T21:15:55.504Z", "tenders": [ { "id": "{PAYMENT_ID}", "location_id": "{LOCATION_ID}", "transaction_id": "{TRANSACTION_ID}", "created_at": "2023-10-13T21:15:54.577Z", "amount_money": { "amount": 5000, "currency": "JPY" }, "type": "CARD", "card_details": { "status": "CAPTURED", "card": { "card_brand": "FELICA", "last_4": "{LAST_FOUR}" }, "entry_method": "CONTACTLESS" } } ] } } ``` {% /accordion %} #### Point of Sale API workflow for E-Money payments 1. Get a transaction ID from a completed checkout. 2. Retrieve the corresponding Order object using the Orders API. 3. Use the `tenders.id` value to get a Payment object with [GetPayment](https://developer.squareup.com/reference/square/square/payments-api/get-payment). The Payment object will return `CardPaymentDetails.Card.CardBrand` with the value `FELICA`. **Terminal checkouts** use `payment_ids` for payments, so you can use `GetPayment` directly to get payment information without needing to go through the Order object first. For non-itemized sales, the `item_type` for orders created with e-money payments, are `CUSTOM`, because there's a custom amount for the payment. Itemized sales use the `ITEM` item type instead. For more information, see [Take E-Money Payments in Japan](pos-api/cookbook/electronic-payments). ### QR Code Payments The [wallet_details](https://developer.squareup.com/reference/square/objects/Payment#definition__property-wallet_details) property in a `Payments` object supports several QR code brands, including: * `PAYPAY` * `ALIPAY` * `RAKUTEN_PAY` * `AU_PAY` * `D_BARAI` * `MERPAY` * `WECHAT_PAY`. If a different brand is used that doesn't match these values, it is recorded as `UNKNOWN`. **Example Order object with a QR code payment:** ```json { "order": { "id": "{ORDER_ID}", "created_at": "2023-10-13T21:15:54.577Z", "updated_at": "2023-10-13T21:15:55.504Z", "tenders": [ { "id": "{PAYMENT_ID}", "location_id": "{LOCATION_ID}", "transaction_id": "{ORDER_ID}", "created_at": "2023-10-13T21:15:55.504Z", "note": "a custom note description", "amount_money": { "amount": 100, "currency": "JPY" }, "processing_fee_money": { "amount": 3, "currency": "JPY" }, "type": "WALLET" } ] } } ``` Note that the `tenders` object array in an order object doesn't have a value defined for `wallet_details`. For implementation guides on accepting QR code payments, see * [Accept PayPay Payments in Japan for the Point of Sale API](pos-api/qr-code-payments-paypay) * [PayPay QR Code Payments for the Terminal API](terminal-api/international-payment-methods/paypay-qr-code-payments) * [Take E-Money Payments in Japan](pos-api/cookbook/electronic-payments). {% aside type="info" %} **Data retention** The Square Developer Console shows data for 1 year, but [API logs](devtools/api-logs) are retained for only 28 days. You can view API logs in the Developer Console or API Explorer. Use the Square API Explorer and API Logs to capture historical data about payments and purchases. {% /aside %} ## Error Handling for Failed Requests When a request fails, Square API endpoints return a response with an `errors` field containing one or more `Errors`. ### Key considerations for Japan * **Error messages are always returned in English**. You need to retrieve the error object as a JSON object and translate the error details for Japanese users. Your application should take appropriate action based on the `error.category`, `error.code`, and `error.field` (which indicates where the error occurred). * **Specific refund limitations for transportation cards**. If a buyer uses a PASMO or SUICA transportation card for a payment, you encounter a `REFUND_DECLINED` error. The seller cannot issue a refund for those payment cards. **Example refund decline error response:** ```json { "errors": [ { "code": "REFUND_DECLINED", "detail": "Payment could not be refunded", "category": "REFUND_ERROR" } ] } ``` ### Errors with Canceling an E-Money Payment Using [CancelTerminalCheckout](https://developer.squareup.com/reference/square/terminal-api/cancel-terminal-checkout) to cancel an e-money payment after getting an error on the Square Terminal is not supported. If an error occurs on Square Terminal during an e-money payment, the checkout request has the `IN_PROGRESS` or `PENDING` state. In this situation, the buyer must manually cancel the checkout. To do so: * Close the error message window * Tap the navigation button back to the checkout screen * Cancel the checkout manually. --- # Add a Content Security Policy > Source: https://developer.squareup.com/docs/web-payments/content-security-policy > Status: PUBLIC > Languages: All > Platforms: All Learn how to add a content security policy to your application that takes payments with Web Payments SDK. {% subheading %} Learn how to implement a Content Security Policy with vendor scripts in your Web Payments SDK integration. {% /subheading %} ## Overview A [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP) (CSP) is a critical security feature that helps protect your Web Payments SDK integration from various attacks, specifically cross-site scripting (XSS) attacks. To comply with the latest PCI DDS security requirements, your application must provide a CSP vendor script for a supported payment method, so that the Web Payments SDK can securely process payments through the payment method, and enable the CSP to protect your application from external scripting attacks. ## What CSP provides CSP provides several key security benefits: - Controls which resources (scripts, styles, images) can load - Specifies allowed origins for content - Prevents malicious script injection - Manages secure communication with payment endpoints ## Requirements and limitations - Web Payments SDK installed and integrate with an application - Access to modify your application's headers - An HTTPS-enabled environment for development ## Set up CSP If your application deploys a Content Security Policy (CSP) with the Web Payments SDK, you must enable the following CSP directives to add an additional security layer. 1. Modify the headers. In `index.html`, add the CSP meta tag or header to your application: For Sandbox Environment: ```html ``` For Production: ```html ``` 2. Add the vendor scripts. Include the necessary payment scripts in your HTML: {% tabset %} {% tab id="Square" %} ```html ``` {% /tab %} {% tab id="Cash App Pay" %} ```html ``` {% /tab %} {% tab id="Afterpay" %} ```html ``` {% /tab %} {% tab id="ACH" %} ```html ``` {% /tab %} {% tab id="Google Pay" %} ```html ``` {% /tab %} {% /tabset %} 3. Implement error handling in the application. Add error handling for the following script loading failures in the application: ```javascript // Monitor script loading window.addEventListener('error', function(e) { if (e.target.src && e.target.src.includes('square.js')) { console.error('Square script failed to load:', e); handleScriptLoadError(); } }); function handleScriptLoadError() { // Implement your fallback logic const paymentForm = document.getElementById('payment-form'); if (paymentForm) { paymentForm.innerHTML = 'Payment temporarily unavailable. Please try again later.'; } // Notify your error monitoring service notifyErrorService('Square script load failure'); } ``` 4. Verify the CSP implementation. 1. Check the console with a web browser: - Look for CSP violation messages. - Verify script loading success. 2. Test a payment flow: ```javascript async function testPaymentFlow() { try { const payments = Square.payments(appId, locationId); const card = await payments.card(); await card.attach('#card-container'); console.log('Payment flow initialized successfully'); } catch (e) { console.error('Payment flow test failed:', e); } } ``` 3. Validate in different environments: - Test in sandbox environment first. - Verify in staging (if applicable). - Confirm in production. ## Common issues and solutions Script loading failures: - Verify CSP directives match script sources. - Check for network connectivity. - Confirm that the script URLs are correct. CSP violations: - Review browser console for specific violation messages. - Update CSP to include missing sources. - Verify all resource domains are included. Integration conflicts: - Check for conflicts with other security headers. - Verify compatibility with other third-party scripts. - Test with all payment methods. ## Best practices Security: - Use specific source URLs rather than wildcards - Minimize use of 'unsafe-inline' and 'unsafe-eval' - Regularly review and update CSP rules Performance: - Load scripts asynchronously when possible - Implement proper error handling - Monitor script loading performance Maintenance: - Document all CSP modifications - Keep track of vendor script versions - Regular testing of all payment flows ## Additional resources - [Mozilla - Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) - [CSP Evaluator tool](https://csp-evaluator.withgoogle.com/) --- # Set Up the Web Client Application > Source: https://developer.squareup.com/docs/web-payments/quickstart/set-up-web-client-app > Status: PUBLIC > Languages: All > Platforms: All Learn how to set up a web client application with the Web Payments SDK quickstart sample project. **Applies to:** [Web Payments SDK](web-payments/overview) | [Payments API](payments-refunds) {% subheading %}Learn how to set up a web client app with the [Web Payments SDK quickstart](https://github.com/square/web-payments-quickstart) sample project.{% /subheading %} {% toc hide=true /%} ## Overview If you don't already have a web client set up that you're working in, the [Web Payments SDK quickstart](https://github.com/square/web-payments-quickstart) includes a template front-end and backend. ### Set up the application 1. Clone the [Web Payments SDK quickstart](https://github.com/square/web-payments-quickstart) GitHub repo. 2. Run `npm install` and then `npm run dev`. 3. Navigate to `http://localhost:3000` to verify that the server is running. The server displays the following web page:  {% aside type="success" %} If your web page renders as shown in the previous image, you're ready to write the code that adds the [Card](https://developer.squareup.com/reference/sdks/web/payments/card-payments) payment method to the page. {% /aside %} The following files are key to the quickstart: * The `public/index.html` file lists specific payment method sub-examples. If you continue this quickstart, you replace this list with the default credit card payment acceptance logic. * The top-level `server.js` file contains the server call to the [CreatePayment](https://developer.squareup.com/reference/square/payments-api/create-payment) endpoint for charging the payment source. The quickstart application must include server.js to make the server call; otherwise, the application cannot create a payment. {% aside type="important" %} * The value in `server.js` [line 45](https://github.com/square/web-payments-quickstart/blob/main/server.js#L45) sets the currency for the quickstart to US dollars. If your default test account is based in another country, be sure to update `server.js` with the currency code for your country before you run the quickstart. * If you're using a non-US account, you must change the currency to match the country in which you're accepting the payment. {% /aside %} ## Next step * [Add the Web Payments SDK to the Web Client](web-payments/quickstart/add-sdk-to-web-client) --- # Charge and Store Cards for Online Payments > Source: https://developer.squareup.com/docs/web-payments/charge-and-store-cards > Status: PUBLIC > Languages: All > Platforms: All Learn how to use the Web Payments SDK to charge a card and store card details for future online payments with a card on file. **Applies to:** [Web Payments SDK](web-payments/overview) | [Payments API](payments-refunds) | [Cards API](cards-api/overview) | [Customers API](customers-api/what-it-does) {% subheading %} Learn how to use the Web Payments SDK to charge a card and store card details for future online payments with a card on file.{% /subheading %} ## Overview After configuring your application with the Web Payments SDK to accept card-not-present payments, you can use the Web Payments SDK to store card details with Square as a card on file and charge the card on file for future online payments. ## How the card-on-file workflows work During payment tokenization, Square checks the tokenize request to determine whether [buyer verification](sca-overview) is needed based on the buyer's information. After receiving the payment token and verifying the buyer, the application performs one of the following card-on-file workflows: * **Store card details** - The application includes the payment token in a Cards API call to store card details as a card on file with [CreateCard](https://developer.squareup.com/reference/square/cards-api/create-card). * **Charge a card on file** - The application includes the payment token in a Payments API call to process a payment with [CreatePayment](https://developer.squareup.com/reference/square/payments-api/create-payment). * **Charge a card and store card details** - The application charges the card and stores the card details as a card on file with a single request call. The application includes the payment token in a Payments API call to process a payment and then creates a card on file and stores the card details as a `Card` object with a new customer profile. When you call `CreateCard`, the `source_id` is the `payment_id` from the Payments API payment response object. This workflow is ideal for scenarios where buyers want to save their card details while making a payment. To explore example implementations of the card-on-file workflows, see [Web Payments SDK Quickstart](web-payments/quickstart) or set up the [quickstart repository](https://github.com/square/web-payments-quickstart) and choose from the available examples. The following are additional code examples of each card-on-file workflow setup: - [Store card details](https://github.com/square/web-payments-quickstart/blob/273eb48c41ce189683e12d8628c7e81c8509854f/public/examples/card-store.html) - [Charge a card on file](https://github.com/square/web-payments-quickstart/blob/273eb48c41ce189683e12d8628c7e81c8509854f/public/examples/card-charge-card-on-file.html) - [Charge a card and store its details](https://github.com/square/web-payments-quickstart/blob/273eb48c41ce189683e12d8628c7e81c8509854f/public/examples/card-charge-and-store.html) ## Set up a card-on-file workflow ### Requirements and limitations Update your application to support the Web Payments SDK card payment flow by following the instructions in [Take a Card Payment](web-payments/take-card-payment) or [Add the Web Payments SDK to the Web Client](web-payments/quickstart/add-sdk-to-web-client). Choose a workflow when you set up the application. Follow the example code by setting up the [quickstart repository](https://github.com/square/web-payments-quickstart). The "store card details" workflow involves a `storeCard` method to store card details, while the "charge a card on file" workflow modifies a `CreatePayment` method to take the payment token and the customer ID. If your application needs to charge a card for a payment and store the card details from that same payment, use the "charge a card and store its details" workflow where the application combines both tasks in a single request call. {% accordion expanded=false %} {% slot "heading" %} ### Store card details {% /slot %} 1. Add a helper `storeCard` method, as shown in the following example, that passes the `token` and `customerId` objects as arguments to your server. The server can then make the call to the Square [CreateCard](https://developer.squareup.com/reference/square/cards-api/create-card) endpoint. ```javascript async function storeCard(token, customerId) { const body = JSON.stringify({ locationId, sourceId: token, customerId, idempotencyKey: window.crypto.randomUUID(), }); const paymentResponse = await fetch('/card', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body, }); if (paymentResponse.ok) { return paymentResponse.json(); } const errorBody = await paymentResponse.text(); throw new Error(errorBody); } ``` 2. Modify the `tokenize` method with the following updates to `verificationDetails`. Set `intent` to `STORE`, `customerInitiated` to `true`, and `sellerKeyedIn` to `false`. ```javascript async function tokenize(paymentMethod) { const verificationDetails = { billingContact: { givenName: 'John', familyName: 'Doe', email: 'john.doe@square.example', phone: '3214563987', addressLines: ['123 Main Street', 'Apartment 1'], city: 'London', state: 'LND', countryCode: 'GB', }, intent: 'STORE', customerInitiated: true, sellerKeyedIn: false, }; const tokenResult = await paymentMethod.tokenize(verificationDetails); if (tokenResult.status === 'OK') { return tokenResult.token; } else { throw new Error( `Tokenization errors: ${JSON.stringify(tokenResult.errors)}`, ); } } ``` 3. Replace the `handlePaymentMethodSubmission` method with a new method called `handleStoreCardSubmission`, pass the `event`, `card`, and `customerId` arguments to the method, and modify the following `try` statement declaration from the code example: ```javascript async function handleStoreCardSubmission(event, card, customerId) { event.preventDefault(); try { // disable the submit button as we await tokenization and make a payment request. cardButton.disabled = true; const token = await tokenize(card); const storeCardResults = await storeCard( token, customerId, ); displayResults('SUCCESS'); console.debug('Store Card Success', storeCardResults); } catch (e) { cardButton.disabled = false; displayResults('FAILURE'); console.error('Store Card Failure', e); } } ``` 4. In the `cardButton.addEventListener` event listener, declare a new `customerId` object and call the `handleStoreCardSubmission` method as indicated in the following code example: ```javascript const cardButton = document.getElementById('card-button'); cardButton.addEventListener('click', async function (event) { const textInput = document.getElementById('customer-input'); if (!textInput.reportValidity()) { return; } const customerId = textInput.value; handleStoreCardSubmission(event, card, customerId); }); ``` {% /accordion %} {% accordion expanded=false %} {% slot "heading" %} ### Charge a card on file {% /slot %} 1. In your application, initialize a [Card](https://developer.squareup.com/reference/sdks/web/payments/objects/Card) object. 2. Call [Card.tokenize()](https://developer.squareup.com/reference/sdks/web/payments/objects/Card#Card.tokenize) with the `verificationDetails` and `cardId` of the stored card. The `Card.tokenize()` method passes the following properties in a `verificationDetails` object: * `amount` - The amount of the card payment to be charged. * `billingContact` - The buyer's contact information for billing. * `intent` - The transactional intent of the payment. * `sellerKeyedIn` - Indicates that the seller keyed in payment details on behalf of the customer. This is used to flag a payment as Mail Order / Telephone Order (MOTO). * `customerInitiated` - Indicates whether the customer initiated the payment. * `currencyCode` - The three-letter ISO 4217 currency code. {% aside type="important" %} Provide as much buyer information as possible for `billingContact` so that you get more accurate decline rate performance from 3DS authentication. {% /aside %} 3. Modify the [CreatePayment](https://developer.squareup.com/reference/square/payments-api/create-payment) endpoint by renaming the `createPaymentWithCardOnFile` function and passing in the `token` and `customerId`. The following code example demonstrates the charge card-on-file setup. ```javascript async function createPaymentWithCardOnFile( token, customerId, ) { const body = JSON.stringify({ locationId, sourceId: token, customerId, idempotencyKey: window.crypto.randomUUID(), }); const paymentResponse = await fetch('/payment', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body, }); if (paymentResponse.ok) { return paymentResponse.json(); } const errorBody = await paymentResponse.text(); throw new Error(errorBody); } // status is either SUCCESS or FAILURE; function displayPaymentResults(status) { const statusContainer = document.getElementById( 'payment-status-container', ); if (status === 'SUCCESS') { statusContainer.classList.remove('is-failure'); statusContainer.classList.add('is-success'); } else { statusContainer.classList.remove('is-success'); statusContainer.classList.add('is-failure'); } statusContainer.style.visibility = 'visible'; } async function tokenize(paymentMethod, sourceId) { const verificationDetails = { amount: '1.00', billingContact: { givenName: 'John', familyName: 'Doe', email: 'john.doe@square.example', phone: '3214563987', addressLines: ['123 Main Street', 'Apartment 1'], city: 'London', state: 'LND', countryCode: 'GB', }, currencyCode: 'GBP', intent: 'CHARGE', customerInitiated: true, sellerKeyedIn: false, }; const tokenResult = await paymentMethod.tokenize(verificationDetails, sourceId); if (tokenResult.status === 'OK') { return tokenResult.token; } else { let errorMessage = `Tokenization failed with status: ${tokenResult.status}`; if (tokenResult.errors) { errorMessage += ` and errors: ${JSON.stringify( tokenResult.errors, )}`; } throw new Error(errorMessage); } } document.addEventListener('DOMContentLoaded', async function () { if (!window.Square) { throw new Error('Square.js failed to load properly'); } let payments; try { payments = window.Square.payments(appId, locationId); } catch { const statusContainer = document.getElementById( 'payment-status-container', ); statusContainer.className = 'missing-credentials'; statusContainer.style.visibility = 'visible'; return; } let card; try { card = await payments.card(); } catch (e) { console.error('Initializing Card failed', e); return; } async function handleChargeCardOnFileSubmission( event, card, cardId, customerId, ) { event.preventDefault(); try { // disable the submit button as we await tokenization and make a payment request. cardButton.disabled = true; const token = await tokenize(card, cardId); const paymentResults = await createPaymentWithCardOnFile( token, customerId ); displayPaymentResults('SUCCESS'); console.debug('Payment Success', paymentResults); } catch (e) { cardButton.disabled = false; displayPaymentResults('FAILURE'); console.error(e.message); } } const cardButton = document.getElementById('card-button'); cardButton.addEventListener('click', async function (event) { const customerTextInput = document.getElementById('customer-input'); const cardTextInput = document.getElementById('card-input'); if ( !customerTextInput.reportValidity() || !cardTextInput.reportValidity() ) { return; } const cardId = cardTextInput.value; const customerId = customerTextInput.value; handleChargeCardOnFileSubmission(event, card, cardId, customerId); }); }); ``` {% /accordion %} {% accordion expanded=false %} {% slot "heading" %} ### Charge a card and store its details {% /slot %} 1. After the `CreatePayment` method in your application, create a new method called `storeCard`, pass the `paymentId` and `customerId` objects as arguments, and add the requisite properties and the `storeCardResponse` as shown in the following example: ```javascript async function storeCard(paymentId, customerId) { const body = JSON.stringify({ locationId, sourceId: paymentId, customerId, idempotencyKey: window.crypto.randomUUID(), }); const storeCardResponse = await fetch('/card', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body, }); if (storeCardResponse.ok) { return storeCardResponse.json(); } const errorBody = await storeCardResponse.text(); throw new Error(errorBody); } ``` 2. In the `tokenize` method, update the `verificationDetails` object by setting `intent` to `CHARGE_AND_STORE`. ```javascript async function tokenize(paymentMethod) { const verificationDetails = { amount: '1.00', billingContact: { givenName: 'John', familyName: 'Doe', email: 'john.doe@square.example', phone: '3214563987', addressLines: ['123 Main Street', 'Apartment 1'], city: 'London', state: 'LND', countryCode: 'GB', }, currencyCode: 'GBP', intent: 'CHARGE_AND_STORE', customerInitiated: true, sellerKeyedIn: false, }; ``` 3. In the `document.addEventListener` event listener method, change the `handlePaymentMethodSubmission` method to `handleChargeAndStoreCardSubmission`, pass the `event`, `card`, and `customerId` objects as arguments, and modify the rest of the code as shown in the following example: ```javascript async function handleChargeAndStoreCardSubmission(event, card, customerId) { event.preventDefault(); try { // disable the submit button as we await tokenization and make a payment request. cardButton.disabled = true; const token = await tokenize(card); const paymentResults = await createPayment(token); console.debug('Payment Success', paymentResults); const storeCardResults = await storeCard( paymentResults.payment.id, customerId, ); console.debug('Store Card Success', storeCardResults); displayPaymentResults('SUCCESS'); } catch (e) { cardButton.disabled = false; displayPaymentResults('FAILURE'); console.error(e.message); } } const cardButton = document.getElementById('card-button'); cardButton.addEventListener('click', async function (event) { const textInput = document.getElementById('customer-input'); if (!textInput.reportValidity()) { return; } const customerId = textInput.value; await handleChargeAndStoreCardSubmission(event, card, customerId); }); ``` {% /accordion %} ## Recommended next step If your application doesn't yet have a Content Security Policy configured, see [Add a Content Security Policy](web-payments/content-security-policy). --- # Charge and Store Cards for Online Payments > Source: https://developer.squareup.com/docs/web-payments/sca-charge-and-store-card-on-file > Status: PUBLIC > Languages: All > Platforms: All Learn how to use the Web Payments SDK to charge a card and store card details for future online payments with a card on file. **Applies to:** [Web Payments SDK](web-payments/overview) | [Payments API](payments-refunds) | [Cards API](cards-api/overview) | [Customers API](customers-api/what-it-does) {% subheading %} Learn how to use the Web Payments SDK to charge a card and store card details for future online payments with a card on file.{% /subheading %} ## Overview After configuring your application with the Web Payments SDK to accept card-not-present payments, you can use the Web Payments SDK to store card details with Square as a card on file and charge the card on file for future online payments. ## How the card-on-file workflows work During payment tokenization, Square checks the tokenize request to determine whether [buyer verification](sca-overview) is needed based on the buyer's information. After receiving the payment token and verifying the buyer, the application performs one of the following card-on-file workflows: * **Store card details** - The application includes the payment token in a Cards API call to store card details as a card on file with [CreateCard](https://developer.squareup.com/reference/square/cards-api/create-card). * **Charge a card on file** - The application includes the payment token in a Payments API call to process a payment with [CreatePayment](https://developer.squareup.com/reference/square/payments-api/create-payment). * **Charge a card and store card details** - The application charges the card and stores the card details as a card on file with a single request call. The application includes the payment token in a Payments API call to process a payment and then creates a card on file and stores the card details as a `Card` object with a new customer profile. When you call `CreateCard`, the `source_id` is the `payment_id` from the Payments API payment response object. This workflow is ideal for scenarios where buyers want to save their card details while making a payment. To explore example implementations of the card-on-file workflows, see [Web Payments SDK Quickstart](web-payments/quickstart) or set up the [quickstart repository](https://github.com/square/web-payments-quickstart) and choose from the available examples. The following are additional code examples of each card-on-file workflow setup: - [Store card details](https://github.com/square/web-payments-quickstart/blob/273eb48c41ce189683e12d8628c7e81c8509854f/public/examples/card-store.html) - [Charge a card on file](https://github.com/square/web-payments-quickstart/blob/273eb48c41ce189683e12d8628c7e81c8509854f/public/examples/card-charge-card-on-file.html) - [Charge a card and store its details](https://github.com/square/web-payments-quickstart/blob/273eb48c41ce189683e12d8628c7e81c8509854f/public/examples/card-charge-and-store.html) ## Set up a card-on-file workflow ### Requirements and limitations Update your application to support the Web Payments SDK card payment flow by following the instructions in [Take a Card Payment](web-payments/take-card-payment) or [Add the Web Payments SDK to the Web Client](web-payments/quickstart/add-sdk-to-web-client). Choose a workflow when you set up the application. Follow the example code by setting up the [quickstart repository](https://github.com/square/web-payments-quickstart). The "store card details" workflow involves a `storeCard` method to store card details, while the "charge a card on file" workflow modifies a `CreatePayment` method to take the payment token and the customer ID. If your application needs to charge a card for a payment and store the card details from that same payment, use the "charge a card and store its details" workflow where the application combines both tasks in a single request call. {% accordion expanded=false %} {% slot "heading" %} ### Store card details {% /slot %} 1. Add a helper `storeCard` method, as shown in the following example, that passes the `token` and `customerId` objects as arguments to your server. The server can then make the call to the Square [CreateCard](https://developer.squareup.com/reference/square/cards-api/create-card) endpoint. ```javascript async function storeCard(token, customerId) { const body = JSON.stringify({ locationId, sourceId: token, customerId, idempotencyKey: window.crypto.randomUUID(), }); const paymentResponse = await fetch('/card', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body, }); if (paymentResponse.ok) { return paymentResponse.json(); } const errorBody = await paymentResponse.text(); throw new Error(errorBody); } ``` 2. Modify the `tokenize` method with the following updates to `verificationDetails`. Set `intent` to `STORE`, `customerInitiated` to `true`, and `sellerKeyedIn` to `false`. ```javascript async function tokenize(paymentMethod) { const verificationDetails = { billingContact: { givenName: 'John', familyName: 'Doe', email: 'john.doe@square.example', phone: '3214563987', addressLines: ['123 Main Street', 'Apartment 1'], city: 'London', state: 'LND', countryCode: 'GB', }, intent: 'STORE', customerInitiated: true, sellerKeyedIn: false, }; const tokenResult = await paymentMethod.tokenize(verificationDetails); if (tokenResult.status === 'OK') { return tokenResult.token; } else { throw new Error( `Tokenization errors: ${JSON.stringify(tokenResult.errors)}`, ); } } ``` 3. Replace the `handlePaymentMethodSubmission` method with a new method called `handleStoreCardSubmission`, pass the `event`, `card`, and `customerId` arguments to the method, and modify the following `try` statement declaration from the code example: ```javascript async function handleStoreCardSubmission(event, card, customerId) { event.preventDefault(); try { // disable the submit button as we await tokenization and make a payment request. cardButton.disabled = true; const token = await tokenize(card); const storeCardResults = await storeCard( token, customerId, ); displayResults('SUCCESS'); console.debug('Store Card Success', storeCardResults); } catch (e) { cardButton.disabled = false; displayResults('FAILURE'); console.error('Store Card Failure', e); } } ``` 4. In the `cardButton.addEventListener` event listener, declare a new `customerId` object and call the `handleStoreCardSubmission` method as indicated in the following code example: ```javascript const cardButton = document.getElementById('card-button'); cardButton.addEventListener('click', async function (event) { const textInput = document.getElementById('customer-input'); if (!textInput.reportValidity()) { return; } const customerId = textInput.value; handleStoreCardSubmission(event, card, customerId); }); ``` {% /accordion %} {% accordion expanded=false %} {% slot "heading" %} ### Charge a card on file {% /slot %} 1. In your application, initialize a [Card](https://developer.squareup.com/reference/sdks/web/payments/objects/Card) object. 2. Call [Card.tokenize()](https://developer.squareup.com/reference/sdks/web/payments/objects/Card#Card.tokenize) with the `verificationDetails` and `cardId` of the stored card. The `Card.tokenize()` method passes the following properties in a `verificationDetails` object: * `amount` - The amount of the card payment to be charged. * `billingContact` - The buyer's contact information for billing. * `intent` - The transactional intent of the payment. * `sellerKeyedIn` - Indicates that the seller keyed in payment details on behalf of the customer. This is used to flag a payment as Mail Order / Telephone Order (MOTO). * `customerInitiated` - Indicates whether the customer initiated the payment. * `currencyCode` - The three-letter ISO 4217 currency code. {% aside type="important" %} Provide as much buyer information as possible for `billingContact` so that you get more accurate decline rate performance from 3DS authentication. {% /aside %} 3. Modify the [CreatePayment](https://developer.squareup.com/reference/square/payments-api/create-payment) endpoint by renaming the `createPaymentWithCardOnFile` function and passing in the `token` and `customerId`. The following code example demonstrates the charge card-on-file setup. ```javascript async function createPaymentWithCardOnFile( token, customerId, ) { const body = JSON.stringify({ locationId, sourceId: token, customerId, idempotencyKey: window.crypto.randomUUID(), }); const paymentResponse = await fetch('/payment', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body, }); if (paymentResponse.ok) { return paymentResponse.json(); } const errorBody = await paymentResponse.text(); throw new Error(errorBody); } // status is either SUCCESS or FAILURE; function displayPaymentResults(status) { const statusContainer = document.getElementById( 'payment-status-container', ); if (status === 'SUCCESS') { statusContainer.classList.remove('is-failure'); statusContainer.classList.add('is-success'); } else { statusContainer.classList.remove('is-success'); statusContainer.classList.add('is-failure'); } statusContainer.style.visibility = 'visible'; } async function tokenize(paymentMethod, sourceId) { const verificationDetails = { amount: '1.00', billingContact: { givenName: 'John', familyName: 'Doe', email: 'john.doe@square.example', phone: '3214563987', addressLines: ['123 Main Street', 'Apartment 1'], city: 'London', state: 'LND', countryCode: 'GB', }, currencyCode: 'GBP', intent: 'CHARGE', customerInitiated: true, sellerKeyedIn: false, }; const tokenResult = await paymentMethod.tokenize(verificationDetails, sourceId); if (tokenResult.status === 'OK') { return tokenResult.token; } else { let errorMessage = `Tokenization failed with status: ${tokenResult.status}`; if (tokenResult.errors) { errorMessage += ` and errors: ${JSON.stringify( tokenResult.errors, )}`; } throw new Error(errorMessage); } } document.addEventListener('DOMContentLoaded', async function () { if (!window.Square) { throw new Error('Square.js failed to load properly'); } let payments; try { payments = window.Square.payments(appId, locationId); } catch { const statusContainer = document.getElementById( 'payment-status-container', ); statusContainer.className = 'missing-credentials'; statusContainer.style.visibility = 'visible'; return; } let card; try { card = await payments.card(); } catch (e) { console.error('Initializing Card failed', e); return; } async function handleChargeCardOnFileSubmission( event, card, cardId, customerId, ) { event.preventDefault(); try { // disable the submit button as we await tokenization and make a payment request. cardButton.disabled = true; const token = await tokenize(card, cardId); const paymentResults = await createPaymentWithCardOnFile( token, customerId ); displayPaymentResults('SUCCESS'); console.debug('Payment Success', paymentResults); } catch (e) { cardButton.disabled = false; displayPaymentResults('FAILURE'); console.error(e.message); } } const cardButton = document.getElementById('card-button'); cardButton.addEventListener('click', async function (event) { const customerTextInput = document.getElementById('customer-input'); const cardTextInput = document.getElementById('card-input'); if ( !customerTextInput.reportValidity() || !cardTextInput.reportValidity() ) { return; } const cardId = cardTextInput.value; const customerId = customerTextInput.value; handleChargeCardOnFileSubmission(event, card, cardId, customerId); }); }); ``` {% /accordion %} {% accordion expanded=false %} {% slot "heading" %} ### Charge a card and store its details {% /slot %} 1. After the `CreatePayment` method in your application, create a new method called `storeCard`, pass the `paymentId` and `customerId` objects as arguments, and add the requisite properties and the `storeCardResponse` as shown in the following example: ```javascript async function storeCard(paymentId, customerId) { const body = JSON.stringify({ locationId, sourceId: paymentId, customerId, idempotencyKey: window.crypto.randomUUID(), }); const storeCardResponse = await fetch('/card', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body, }); if (storeCardResponse.ok) { return storeCardResponse.json(); } const errorBody = await storeCardResponse.text(); throw new Error(errorBody); } ``` 2. In the `tokenize` method, update the `verificationDetails` object by setting `intent` to `CHARGE_AND_STORE`. ```javascript async function tokenize(paymentMethod) { const verificationDetails = { amount: '1.00', billingContact: { givenName: 'John', familyName: 'Doe', email: 'john.doe@square.example', phone: '3214563987', addressLines: ['123 Main Street', 'Apartment 1'], city: 'London', state: 'LND', countryCode: 'GB', }, currencyCode: 'GBP', intent: 'CHARGE_AND_STORE', customerInitiated: true, sellerKeyedIn: false, }; ``` 3. In the `document.addEventListener` event listener method, change the `handlePaymentMethodSubmission` method to `handleChargeAndStoreCardSubmission`, pass the `event`, `card`, and `customerId` objects as arguments, and modify the rest of the code as shown in the following example: ```javascript async function handleChargeAndStoreCardSubmission(event, card, customerId) { event.preventDefault(); try { // disable the submit button as we await tokenization and make a payment request. cardButton.disabled = true; const token = await tokenize(card); const paymentResults = await createPayment(token); console.debug('Payment Success', paymentResults); const storeCardResults = await storeCard( paymentResults.payment.id, customerId, ); console.debug('Store Card Success', storeCardResults); displayPaymentResults('SUCCESS'); } catch (e) { cardButton.disabled = false; displayPaymentResults('FAILURE'); console.error(e.message); } } const cardButton = document.getElementById('card-button'); cardButton.addEventListener('click', async function (event) { const textInput = document.getElementById('customer-input'); if (!textInput.reportValidity()) { return; } const customerId = textInput.value; await handleChargeAndStoreCardSubmission(event, card, customerId); }); ``` {% /accordion %} ## Recommended next step If your application doesn't yet have a Content Security Policy configured, see [Add a Content Security Policy](web-payments/content-security-policy). --- # Manage Card-on-File Declines > Source: https://developer.squareup.com/docs/cards-api/manage-card-on-file-declines > Status: PUBLIC > Languages: All > Platforms: All Learn about Square features and guidance that can help reduce and manage card-on-file declines. **Applies to:** [Cards API](cards-api/overview) | [Webhooks](webhooks/overview) {% subheading %}Use Square features and guidance to help manage or reduce declines for [card-on-file payments](cards-api/overview#card-on-file-payments) and increase customer retention.{% /subheading %} {% toc hide=true /%} {% anchor id="card-automatically-updated-event-notifications" /%} ## card.automatically_updated event notifications To proactively manage potential declines due to expired card data or closed accounts, subscribe to receive [card.automatically_updated](https://developer.squareup.com/reference/square/cards-api/webhooks/card.automatically_updated) event notifications when Square automatically updates cards on file. You can subscribe [manually](webhooks/step2subscribe) in the Developer Console or [programmatically](webhooks/webhook-subscriptions-api) using the Webhook Subscriptions API. When you receive a `card.automatically_updated` notification, inspect the `card` object in the notification payload and take appropriate action: * **Expiration updates** - When allowed by the issuing bank, Square automatically updates the expiration date and last 4 digits of expiring cards on file. By listening for these updates, you can help ensure that card information remains current if the expiration date or card number changes. To use this feature: 1. In the notification payload, check the `exp_month`, `exp_year`, and `last_4` fields. 2. If you store this card information, update your records. {% line-break /%} * **Issuer alerts about closed accounts (Beta, select Mastercard cards only)** - For some Mastercard cards on file, the issuing bank alerts Square when the underlying account is closed. Square then marks the card as `ISSUER_ALERT_CARD_CLOSED`. Issuer alerts aren't guaranteed to be accurate, but they're a strong signal that future charges to the card are likely to fail. In testing, 80% of cards with issuer alerts were declined at the time of payment. By proactively contacting customers to set up a new payment method on file, you can reduce the likelihood that recurring subscriptions become delinquent. {% aside type="info" %} To access issuer alerts, Square API version 2024-12-18 or later is required for your webhook subscription or when calling the Cards API. {% /aside %} To use this feature: 1. In the notification payload, check whether the `issuer_alert` field is present. If present and set to `ISSUER_ALERT_CARD_CLOSED`, check the `issuer_alert_at` field to determine whether this is a new alert. 2. If the account is newly marked as closed, request a new payment method from the customer. {% accordion expanded=false %} {% slot "heading" %} #### More details {% /slot %} If notified by the issuing bank that the underlying account for the card on file is closed, Square sets the `issuer_alert` field to `ISSUER_ALERT_CARD_CLOSED`, updates the `issuer_alert_at` timestamp, and invokes a `card.automatically_updated` event. The `issuer_alert` and `issuer_alert_at` fields are present only when the card has an active issuer alert. If notified by the issuing bank that the account is reopened, Square clears both fields and invokes a `card.automatically_updated` event. The following example payload contains the `issuer_alert` and `issuer_alert_at` fields, which indicates that there's an active issuer alert: ```json { "merchant_id": "6SSW7HV8K2ST5", "type": "card.automatically_updated", "event_id": "d214f854-adb1-4f56-b078-4b8697a3187a", "created_at": "2025-02-15T04:38:13Z", "data": { "type": "card", "id": "ccof:uIbfJXhXETSP197M3GB", "object": { "card": { "id": "ccof:uIbfJXhXETSP197M3GB", "billing_address": { "address_line_1": "500 Electric Ave", "address_line_2": "Suite 600", "locality": "New York", "administrative_district_level_1": "NY", "postal_code": "10003", "country": "US" }, "fingerprint": "ex-p-cs80EK9Flz7LsCMv-szbptQ_ssAGrhemzSTsPFgt9nzyE6t7okiLIQc-qw_quqKX4Q", "bin": "411111", "card_brand": "VISA", "card_type": "CREDIT", "cardholder_name": "Amelia Earhart", "customer_id": "VDKXEEKPJN48QDG3BGGFAK05P8", "enabled": true, "exp_month": 11, "exp_year": 2027, "last_4": "1111", "issuer_alert": "ISSUER_ALERT_CARD_CLOSED", "issuer_alert_at": "2025-02-16T04:38:13Z", "merchant_id": "6SSW7HV8K2ST5", "prepaid_type": "NOT_PREPAID", "reference_id": "user-id-2", "version": 2 } } } } ``` {% /accordion %} Square also supports `card.created`, `card.updated`, `card.disabled`, and `card.forgotten` [webhook events](webhooks/v2webhook-events-tech-ref#cards-api) for the Cards API. {% aside type="tip" %} The [Events API](events-api/overview) can be used with webhook subscriptions for recovery and reconciliation of missed webhook events. {% /aside %} ## SCA verification [Strong Customer Authentication](sca-overview) (SCA) provides an additional layer of fraud protection in regions where SCA is required or for sellers that subscribe to Square Risk Manager to enforce 3-D Secure (3DS). SCA applies both when saving cards on file and making card-on-file payments. To help reduce the likelihood of declines for card-on-file payments, provide as much buyer information as possible to the Web Payments SDK or In-App Payments SDK. For more information, see [Charge and Store Cards for Online Payments](web-payments/charge-and-store-cards) for Web Payments SDK integrations or [Verify a Buyer](in-app-payments-sdk/verify-buyer) for In-App Payments SDK integrations. {% anchor id="health-account-cards-beta" /%} {% anchor id="health-account-cards" /%} ## Health account cards (Beta) Square uses the Bank Identification Number (BIN) to determine whether a card on file is for a Health Savings Account (HSA) or a Flexible Spending Account (FSA), which can be used for authorized medical expenses. For these cards, Square sets the `Card.hsa_fsa` field to `true`. You can use the `hsa_fsa` field to identify HSA and FSA cards, which tend to have higher decline rates as the year progresses because they might run out of funds. Sellers accepting card-on-file payments should ask customers to provide a secondary payment method on file when saving an HSA or FSA card on file. {% aside type="info" %} Square API version 2024-12-18 or later is required to access the `hsa_fsa` field. {% /aside %} ## Prepaid cards A prepaid card is a type of payment card that can hold a certain amount of value. For these cards, Square sets the `Card.prepaid_type` field to `PREPAID`. You can use the `prepaid_type` field to identify prepaid cards, which tend to have higher decline rates because they run out of funds. Sellers accepting card-on-file payments should ask customers to also provide a secondary payment method on file when saving a prepaid card on file. ## Additional best practices ### Logging and audits Keep detailed logs of transaction attempts and declined payments to identify and address recurring issues. Conduct regular audits of transaction flows to ensure compliance with regulations and identify any potential issues. ### Retry strategy Consider the following factors when designing your retry strategy: - Check the decline reason to determine the appropriate action: - `INSUFFICIENT_FUNDS` - Retrying after a few days might be effective. - `GENERIC_DECLINE` error - Don't retry. Instead, inform the buyer that the specific reason for the decline isn't available and advise them to contact their issuer for further details and assistance. Sometimes the specific reason for a card decline is unavailable because of limitations in the information provided by the issuing bank. - Wait at least 24 hours before retrying. Immediate retries can trigger fraud alerts or cause additional declines. - Limit retries to 2 to 3 attempts over several days to avoid triggering fraud alerts. Excessive retries can be flagged as suspicious activity. ### Customer communication When possible, inform buyers when a decline occurs and suggest they contact their issuing bank for further details and assistance, especially when the exact reason for the decline isn't available. If a charge continues to fail, request a new payment method from the customer. ### Test decline scenarios Test in production and Sandbox environments to ensure your application gracefully handles card-on-file declines. Square provides [card-on-file IDs](devtools/sandbox/payments#card-on-file-id-values) you can use for testing some common card-on-file decline scenarios in the {% tooltip text="Square Sandbox" %}An isolated server environment used for testing. API calls in the Sandbox use the "connect.squareupsandbox.com" domain and require an access token that's valid for Sandbox.{% /tooltip %}. ## See also * [Cards API](cards-api) * [Manage Customer Cards on File](cards-api/manage-cards) * [Create a Card on File from a Payment ID](cards-api/walkthrough/card-from-payment-id) * [Create a Card on File and a Payment](cards-api/walkthrough-seller-card) * [Create a Shared Card on File and Make a Payment](cards-api/walkthrough-shared-card) --- # .NET SDK Migration Guide > Source: https://developer.squareup.com/docs/sdks/dotnet/migration > Status: PUBLIC > Languages: All > Platforms: All Upgrade from the legacy version of the Square .NET SDK to version 42.0.0 or later. Version `41.0.0` of the .NET SDK represents a full rewrite of the SDK with a number of breaking changes, outlined in the following sections. When upgrading to this version or later versions, take note of the changes in the SDK, including client construction and parameter names. If necessary, you can use the legacy version of the SDK along with the latest version. SDK versions `40.1.0` and earlier continue to function as expected, but you should plan to update your integration as soon as possible to ensure continued support. ## Client construction To create a new client, you construct it using `new` and passing in parameters, as opposed to the legacy client that uses a Java-esque builder pattern. ```csharp using Square; // new var client = new SquareClient( token, // optional if you configure SQUARE_TOKEN environment variable new ClientOptions { BaseUrl = SquareEnvironment.Production // default, so you can omit ClientOptions parameter } ); // legacy var legacyClient = new Square.Legacy.SquareClient.Builder() .BearerAuthCredentials(new BearerAuthModel.Builder(token).Build()) .Environment(Square.Legacy.Environment.Production) .Build(); ``` {% table %} * Legacy * New {% width="200px" %} * Additional information --- * `AccessToken` * `Token` * The access token used for authentication. --- * `BearerAuthCredentials` * Removed * Set using the `Token` or `SQUARE_TOKEN` environment variable. --- * `CustomUrl` * `ClientOptions.BaseUrl` * Sets the base URL for API requests and defaults to `https://connect.squareup.com`. --- * `SquareVersion` * `ClientOptions.Version` * The Square API version to use. --- * `AdditionalHeaders` * `ClientOptions.AdditionalHeaders` * Available through `ClientOptions.AdditionalHeaders`. --- * `UserAgentDetail` * Removed * Set the user agent header on `ClientOptions.DefaultHeaders`. --- * `Timeout` * `Timeout` * Accepts a `TimeSpan` and the default is 30 seconds. --- * `HttpCallback` * Removed * Configure the HTTP client and options on the `ClientOptions`. --- * `HttpClientConfig` * Removed * Configure the HTTP client and options on the `ClientOptions`. {% /table %} ## Use the legacy and new SDK side by side While the new SDK has many improvements, it takes time to upgrade when there are breaking changes. To make the migration easier, the old SDK is available in the `Square.Legacy` NuGet package. The following example shows how to use the new and legacy SDKs inside a single file: ```csharp using Square; using Square.Legacy.Authentication; using Square.Locations; public class Program { public static async Task Main(string[] args) { var token = Environment.GetEnvironmentVariable("SQUARE_TOKEN") ?? throw new Exception("Missing SQUARE_TOKEN environment variable"); // LEGACY var legacyClient = new Square.Legacy.SquareClient.Builder() .BearerAuthCredentials(new BearerAuthModel.Builder(token).Build()) .Environment(Square.Legacy.Environment.Sandbox) .Build(); // NEW var client = new SquareClient( token, new ClientOptions { BaseUrl = SquareEnvironment.Sandbox } ); // use new client var location = await GetLocationAsync(client); // use legacy client await CreateOrderAsync(legacyClient, location); } private static async TaskPlump homemade muffins
", "description_plaintext": "Plump homemade muffins", "is_archived": false } }, { "type": "ITEM_VARIATION", "id": "UD3CZNICTRKB35IWY36ARVHT", "updated_at": "2024-10-02T21:36:36.932Z", "created_at": "2024-04-09T20:59:24.983Z", "version": 1727904996932, "is_deleted": true, "present_at_all_locations": true, "item_variation_data": { "item_id": "BD4FDFQQOMVOIIVW67F3AUYN", "name": "Regular", "ordinal": 0, "pricing_type": "VARIABLE_PRICING", "sellable": true, "stockable": true } }, { "type": "ITEM_VARIATION", "id": "6U4CAIYCXVZO6NZYTBGKPXYN", "updated_at": "2024-10-02T21:36:36.932Z", "created_at": "2024-10-02T21:36:37.081Z", "version": 1727904996932, "is_deleted": false, "present_at_all_locations": true, "item_variation_data": { "item_id": "BD4FDFQQOMVOIIVW67F3AUYN", "name": "Regular", "ordinal": 0, "pricing_type": "VARIABLE_PRICING", "sellable": true, "stockable": true } } ], "latest_time": "2024-10-02T21:36:36.932Z" } ``` {% /tab %} {% /tabset %} ## Sync on regular interval polling Polling replaces the non-deterministic webhook event trigger with a regular cadence that you establish in your application. For example, you can set a chron job to fire an event every 15 minutes to get all catalog updates made since the last time a chron event fired. ### Trigger sync from your chron job Complete following steps to start syncing a catalog from Square to an external system by using a timer that you add to your application backend: 1. Make a full copy of the master catalog in the target platform. 3. Cache an arbitrary start date. Usually this is the date the target catalog is created. 4. Start your chron job. 5. When a chron event fires: 1. Call `SearchCatalogObjects` with the cached date, 1. Replace the cached date with the timestamp of the chron event. 1. Parse the response to `SearchCatalogObjects`, which has all Square catalog changes since the initial copy. The following example shows the use of a chron event timestamp to get all catalog updates after the catalog version was incremented: {% tabset %} {% tab id="Request" %} To request all `Catalog` objects that have changed after the previous chron event (`2024-10-02T21:31:36.932Z`) , make a `SearchCatalogOjbects` request like this request. ```` {% /tab %} {% tab id="Response" %} The response shows that the only catalog update was a `CatalogItem` that was updated because its `CatalogItemVariation` with ID `UD3CZNICTRKB35IWY36ARVHT` was deleted after `2024-10-02T21:31:36.932Z`. ```json { "objects": [ { "type": "ITEM", "id": "BD4FDFQQOMVOIIVW67F3AUYN", "updated_at": "2024-10-02T21:36:36.932Z", "created_at": "2024-04-09T20:59:24.983Z", "version": 1727904996932, "is_deleted": false, "present_at_all_locations": true, "item_data": { "name": "Muffin", "description": "Plump homemade muffins", "abbreviation": "MUFF", "is_taxable": true, "variations": [ { "type": "ITEM_VARIATION", "id": "6U4CAIYCXVZO6NZYTBGKPXYN", "updated_at": "2024-10-02T21:36:36.932Z", "created_at": "2024-10-02T21:36:37.081Z", "version": 1727904996932, "is_deleted": false, "present_at_all_locations": true, "item_variation_data": { "item_id": "BD4FDFQQOMVOIIVW67F3AUYN", "name": "Regular", "ordinal": 0, "pricing_type": "VARIABLE_PRICING", "sellable": true, "stockable": true } } ], "product_type": "REGULAR", "description_html": "Plump homemade muffins
", "description_plaintext": "Plump homemade muffins", "is_archived": false } }, { "type": "ITEM_VARIATION", "id": "UD3CZNICTRKB35IWY36ARVHT", "updated_at": "2024-10-02T21:36:36.932Z", "created_at": "2024-04-09T20:59:24.983Z", "version": 1727904996932, "is_deleted": true, "present_at_all_locations": true, "item_variation_data": { "item_id": "BD4FDFQQOMVOIIVW67F3AUYN", "name": "Regular", "ordinal": 0, "pricing_type": "VARIABLE_PRICING", "sellable": true, "stockable": true } }, { "type": "ITEM_VARIATION", "id": "6U4CAIYCXVZO6NZYTBGKPXYN", "updated_at": "2024-10-02T21:36:36.932Z", "created_at": "2024-10-02T21:36:37.081Z", "version": 1727904996932, "is_deleted": false, "present_at_all_locations": true, "item_variation_data": { "item_id": "BD4FDFQQOMVOIIVW67F3AUYN", "name": "Regular", "ordinal": 0, "pricing_type": "VARIABLE_PRICING", "sellable": true, "stockable": true } } ], "latest_time": "2024-10-02T21:36:36.932Z" } ``` {% /tab %} {% /tabset %} ## Sync from an external platform to Square Syncing from an external platform to Square applies where the external platform hosts the master catalog. Using whatever mechanism is appropriate for the external platform that your application supports, you parse any external updates and push them into the catalog in the seller's Square account by using [UpsertCatalogObject](https://developer.squareup.com/reference/square/catalog-api/upsert-catalog-object), [DeleteCatalogObject](https://developer.squareup.com/reference/square/catalog-api/delete-catalog-object), [BatchUpsertCatalogObjects](https://developer.squareup.com/reference/square/catalog-api/batch-upsert-catalog-objects), or [BatchDeleteCatalogObjects](https://developer.squareup.com/reference/square/catalog-api/batch-delete-catalog-objects) API calls. It's a good idea to group like operations so you can call the Catalog API batch endpoints and reduce the number of API calls you make. For example, you can pull all the delete operations into a single [BatchDeleteCatalogObjects](https://developer.squareup.com/reference/square/catalog-api/batch-delete-catalog-objects) API call. For each `Catalog` object that exists in both platforms, apply the external update by first retrieving the Square `Catalog` object by the `CatalogObject` ID that you stored in the external platform. For information about retrieving a `Catalog` object by ID, see [Retrieve a catalog object with a specified ID and catalog version](catalog-api/retrieve-catalog-objects#retrieve-a-catalog-object-with-a-specified-id-and-catalog-version). To learn about updating existing Square `Catalog` objects with changes to the same object in an external platform, see [Update Catalog Objects](catalog-api/update-catalog-objects). If an external catalog item is deleted, call [DeleteCatalogObject](https://developer.squareup.com/reference/square/catalog-api/delete-catalog-object). ### Verify updates You can verify updates synchronously by catching the response to upsert or delete requests. If you get a `200` response, that means the catalog change succeeded. You can parse the call response to verify if necessary. If you want to run verification in a webhook listener process, you can use the webhook events generated by the Catalog API operations. You need to provide a timestamp representing your first Catalog API call to that verify process. To process verifications triggered by webhook events, first [subscribe](webhooks/step2subscribe) to [catalog.version.updated](https://developer.squareup.com/reference/square/catalog-api/webhooks/catalog.version.updated) using the Developer Console or [subscribe](webhooks/webhook-subscriptions-api) programmatically with the Subscriptions API. The webhook listener you use needs to be running before you make your first Catalog API call to push updates. ## Bi-directional syncing Bi-directional catalog synchronization can be difficult to implement because of the large number of endpoints involved in both systems. `Catalog` object updates in either system result in risky operations such as: * **CRUD operations** - Risks arise from concurrency, merging, duplication, and deletion issues if both systems have CRUD operations around the same time. * **Price changes** - An accounting integrity risk can arise from a user on either system inadvertently updating the other system. For example, a Square user updates the POS price, which then updates the online price in the other system with that new price. A seller might intend for item prices to be different across sales channels. If your application must address a bi-directional use case, you need to account for these side-effects, which are beyond the scope of this topic. However, you can use the preceding sections to understand the mechanisms you can use to build either direction of your use case. --- # Process an Unlinked Refund > Source: https://developer.squareup.com/docs/refunds-api/unlinked-refunds > Status: PUBLIC > Languages: All > Platforms: All Learn how to process unlinked refunds with Refunds API. **Applies to:** [Refunds API](refunds-api/overview) | [Web Payments SDK](web-payments/overview) {% subheading %} Learn how to process unlinked refunds with the Refunds API.{% /subheading %} {% toc hide=true /%} ## Overview An unlinked refund isn't linked to a Square payment and has no associated payment ID. Use an unlinked refund when there's no Square payment to link to (such as when the original payment was processed by another payment processor). ## Requirements and limitations The ability to process an unlinked refund has the following limitations: * Unlinked refunds aren't supported in Japan. * Refunds cannot be processed by a Square account that hasn't been enabled for payment processing. * The unlinked refund feature is only available for sellers who've been authorized to use it. A seller using your application needs to contact their Square account manager for authorization. When authorized, all of the seller's locations can take unlinked refunds. The following example is the `400` response that your application receives if used by a seller who isn't authorized to make unlinked refunds: ```json { "errors": [ { "code": "BAD_REQUEST", "detail": "Unlinked refund processing is not enabled for this merchant.", "category": "INVALID_REQUEST_ERROR" } ] } ``` ### Refund a non-Square payment Non-Square payments exist when a seller is transitioning their payment processing from another service to Square. During the transition period, the seller might want to issue a refund through Square for a payment taken before they started with Square. The refund request is unlinked because there's no Square `Payment` object to represent the original payment. This unlinked refund is processed the same way as a standard refund. The seller's Square account is the source of the funds for the refund. If there are insufficient funds in the seller's Square account, the seller's linked bank account is the refund's source. ## Create an unlinked refund Use the `RefundPayment` endpoint to process an unlinked refund to a card. In the request, don't provide a payment ID. Instead, provide values for the following unlinked refund types before processing the refund. ### Create an unlinked refund to a card Add the following values to the `RefundPayment` request for the card refund: * `destination_id` - Identifies the refund destination by setting the value to a payment card (or Square gift card) token or card-on-file ID. Specify one of the following: * A payment token. * A Square gift card or card-on-file ID. * `location_id` - Where the refund occurred. * `customer_id` - Required for card-on-file destinations. * `unlinked` - Must be `true`. A payment token is generated by your application using the [Web Payments SDK](web-payments/overview). When testing, use a [Sandbox test](devtools/sandbox/payments#payment-tokens-for-testing-payments-api) token such as `cnon:card-nonce-ok`. The following `RefundPayment` request specifies a test payment card on file that Square provides for [Sandbox testing](devtools/sandbox/payments#payment-tokens-for-testing-payments-api). You can test the same request with any of the card test values that Square provides. ```` After receiving the request, Square processes the refund and returns money to the buyer. In response, Square returns a `Refund` object, as shown in the following example: ```json { "refund": { "id": "9YBgvKvpjwsfM4lT1FjpXBdI...fAGf0A9ueKAqWCHxmQotH8TWjor9ZYfI", "status": "PENDING", "amount_money": { "amount": 1000, "currency": "USD" }, "order_id": "6MnVDc5q4xpivpp6LED7FWYNGdZZY", "created_at": "2022-09-24T21:08:06.847Z", "updated_at": "2022-09-24T21:08:06.856Z", "location_id": "S8GWD5R9QB376", "reason": "Buyer returned goods", "unlinked": true, "destination_type": "CARD", "destination_details": { "card_details": { "card": { "card_brand": "MASTERCARD", "last_4": "6952", "exp_month": 12, "exp_year": 2028, "fingerprint": "sq-1-HVUCWbGnS2LZ...E8Gkh_EtOTe0A", "card_type": "CREDIT", "bin": "512081" }, "entry_method": "ON_FILE" } } } } ``` The `order_id` in this example is the unlinked return order ID. Square creates a refund order while processing the refund request and returns the new order ID in this response. The `destination_details` field contains the details of the refund destination. Card refunds have a `card_details` field, which contains the refund destination's payment card details that you can use to confirm with the buyer that the funds were refunded to the intended card. ### Create a cash unlinked refund Add the following values to the `RefundPayment` request for the cash refund: * `destination_id` - Must be `CASH`. * `location_id` - Where the refund occurred. * `unlinked` - Must be `true`. * `cash_details` - Must include the amount of cash the seller provided as `seller_supplied_money`. The following is an example `RefundPayment` request: ```` After receiving the request, Square makes a record of the refund and returns a `Refund` object, as shown in the following example: ```json { "refund": { "id": "9YBgvKvpjwsfM4lT1FjpXBdI...fAGf0A9ueKAqWCHxmQotH8TWjor9ZYfI", "status": "PENDING", "amount_money": { "amount": 1000, "currency": "USD" }, "order_id": "6MnVDc5q4xpivpp6LED7FWYNGdZZY", "created_at": "2024-10-17T21:08:06.847Z", "updated_at": "2024-10-17T21:08:06.856Z", "location_id": "{LOCATION_ID}", "reason": "Buyer returned goods", "unlinked": true, "destination_type": "CASH", "destination_details": { "cash_details": { "seller_supplied_money": { "amount": 1000, "currency": "USD" }, "change_back_money": { "amount": 0, "currency": "USD" } } } } } ``` ### Create an unlinked refund to an external source Add the following values to the `RefundPayment` request for the external refund: * `destination_id` - Must be `EXTERNAL`. * `location_id` - Where the refund occurred. * `unlinked` - Must be `true`. * `external_details` - Must include: * source - A description of the external refund source. * type - See [accepted options](https://developer.squareup.com/reference/square/refunds-api/refund-payment#request__property-external_details). The following is an example `RefundPayment` request: ```` After receiving the request, Square makes a record of the refund and returns a `Refund` object, as shown in the following example: ```json { "refund": { "id": "9YBgvKvpjwsfM4lT1FjpXBdI...fAGf0A9ueKAqWCHxmQotH8TWjor9ZYfI", "status": "PENDING", "amount_money": { "amount": 1000, "currency": "USD" }, "order_id": "6MnVDc5q4xpivpp6LED7FWYNGdZZY", "created_at": "2024-10-17T21:08:06.847Z", "updated_at": "2024-10-17T21:08:06.856Z", "location_id": "{LOCATION_ID}", "reason": "Buyer returned goods", "unlinked": true, "destination_type": "EXTERNAL", "destination_details": { "external_details": { "type": “OTHER”, "source": "Food Delivery Service" } } } } ``` --- # Order Service Charges > Source: https://developer.squareup.com/docs/orders-api/service-charges > Status: PUBLIC > Languages: All > Platforms: All Learn how to add service charges to an order using the Orders API. **Applies to:** [Orders API](orders-api/what-it-does) {% subheading %}Learn how to apply service charges to an order using the Orders API.{% /subheading %} {% toc hide=true /%} ## Overview Building on the example order created in [Order Price Adjustments](orders-api/price-adjustments), learn how the calculation phases you set on an order affect service charges applied to an order. Square calculates and applies any discounts on the order before service charges are applied. After discounted prices are applied to the order, service charges are applied. Service charges are scoped as follows: * **Based on the total amount of the order** - If the service charge will be taxed, it's applied before taxes are calculated. * **Apportioned based on the individual items in the order** - The apportioned charge is based on a percentage or a fixed amount. ## Calculation phases The time that the service charge calculation happens is a calculation phase. The Orders API supports any of four calculation phases for a service charge: * **Subtotal phase** - The service charge is calculated before taxes. * **Total phase** - The service charge is calculated after taxes. * **Apportioned amount phase** - The service charge is calculated after any percentage-based apportioned service charges and before taxes. * **Apportioned percentage phase** - The service charge is calculated before any amount-based apportioned service charges and taxes. ### Phase limitations Each calculation phase is intended to work with only certain types of service charges. When a given phase is used in the wrong type of charge, your application receives a `400 BAD_REQUEST` response. **Subtotal phase service charge** - Cannot be used on an order in the following condition: * The service charge is applied at the order line-item level. **Total phase service charge** - Cannot be used on an order in the following conditions: * The service charge is taxable. * The service charge is applied at the order line-item level. **Apportioned amount phase service charge** - Cannot be used on an order in the following conditions: * The [treatment_type](https://developer.squareup.com/reference/square/objects/OrderServiceCharge#definition__property-treatment_type) of the service charge is `LINE_ITEM_TREATMENT`. * The service charge has a percentage amount instead of an amount. **Apportioned percentage phase service charge** - Cannot be used on an order in the following conditions: * The [treatment_type](https://developer.squareup.com/reference/square/objects/OrderServiceCharge#definition__property-treatment_type) of the service charge is `LINE_ITEM_TREATMENT`. * The service charge has a dollar amount instead of a percentage. {% aside type="important" %} Apportioned service charges aren't accounted for in the `line_item.gross_sales_money` attribute. If your sales report includes service charges in line-item totals, report on the `total_money` attribute instead of `gross_sales_money`. {% /aside %} ## Apportioned amount service charge An apportioned amount service charge adds a portion of the total service charge to line items in the order. If the `service_charges[].scope` is set to `ORDER`, a portion of the service charge is applied to every line. If the scope is set to `LINE_ITEM`, only those line items where the service charge is applied get a portion of the service charge. Suppose the total service charge on an order is $10 and the order subtotal is $116 for the three line items. $10 needs to be apportioned across all line items using the following calculation: * `Dog Biscuits - Chicken Flavor`: ((2 @ $15 per box = $30) / $116) * 10 = $2.59 * `Handmade Sweater - Blue`: (($50) / $116) * 10 = $4.31 * `Chewy Rawhide - Beef Flavor`: ((3 @ $12 per box = $30) / $116) * 10 = $3.10 {% line-break /%} {% tabset %} {% tab id="Request" %} The following example request specifies the $10 apportioned amount service charge and the three line items: ```` {% /tab %} {% tab id="Response" %} The following response shows that the three apportioned line-item service charges total the $10 total service charge: ```json { "order": { "location_id": "VJN4XSBFTVPK9", "line_items": [ { "uid": "uFptr9GfXbBTuKMJRQJdGB", "quantity": "2", "name": "Dog Biscuits - Chicken Flavor", "base_price_money": { "amount": 1500, "currency": "USD" }, "gross_sales_money": { "amount": 3000, "currency": "USD" }, "total_tax_money": { "amount": 0, "currency": "USD" }, "total_discount_money": { "amount": 0, "currency": "USD" }, "total_money": { "amount": 3259, "currency": "USD" }, "variation_total_price_money": { "amount": 3000, "currency": "USD" }, "item_type": "ITEM", "applied_service_charges": [ { "uid": "PlbvFDyMySGH4FXicgOEyD", "service_charge_uid": "SVS_CHARGE1", "applied_money": { "amount": 259, "currency": "USD" } } ], "total_service_charge_money": { "amount": 259, "currency": "USD" } }, { "uid": "PCi6xxwOBo8Y1fzGEOBl8", "quantity": "1", "name": "Handmade Sweater - Blue", "base_price_money": { "amount": 5000, "currency": "USD" }, "gross_sales_money": { "amount": 5000, "currency": "USD" }, "total_tax_money": { "amount": 0, "currency": "USD" }, "total_discount_money": { "amount": 0, "currency": "USD" }, "total_money": { "amount": 5431, "currency": "USD" }, "variation_total_price_money": { "amount": 5000, "currency": "USD" }, "item_type": "ITEM", "applied_service_charges": [ { "uid": "tB2mgXVVhtIaINB9gFutuB", "service_charge_uid": "SVS_CHARGE1", "applied_money": { "amount": 431, "currency": "USD" } } ], "total_service_charge_money": { "amount": 431, "currency": "USD" } }, { "uid": "ozixhXglLJ9k6O4o5Pkfo", "quantity": "3", "name": "Chewy Rawhide - Beef Flavor", "base_price_money": { "amount": 1200, "currency": "USD" }, "gross_sales_money": { "amount": 3600, "currency": "USD" }, "total_tax_money": { "amount": 0, "currency": "USD" }, "total_discount_money": { "amount": 0, "currency": "USD" }, "total_money": { "amount": 3910, "currency": "USD" }, "variation_total_price_money": { "amount": 3600, "currency": "USD" }, "item_type": "ITEM", "applied_service_charges": [ { "uid": "z24JMoRuw1y8vmRFHK1UE", "service_charge_uid": "SVS_CHARGE1", "applied_money": { "amount": 310, "currency": "USD" } } ], "total_service_charge_money": { "amount": 310, "currency": "USD" } } ], "created_at": "2024-06-18T20:47:37.048Z", "updated_at": "2024-06-18T20:47:37.048Z", "state": "OPEN", "version": 1, "total_tax_money": { "amount": 0, "currency": "USD" }, "total_discount_money": { "amount": 0, "currency": "USD" }, "total_tip_money": { "amount": 0, "currency": "USD" }, "total_money": { "amount": 12600, "currency": "USD" }, "service_charges": [ { "uid": "SVS_CHARGE1", "name": "service charge", "amount_money": { "amount": 1000, "currency": "USD" }, "applied_money": { "amount": 1000, "currency": "USD" }, "calculation_phase": "APPORTIONED_AMOUNT_PHASE", "taxable": true, "total_money": { "amount": 1000, "currency": "USD" }, "total_tax_money": { "amount": 0, "currency": "USD" }, "type": "CUSTOM", "treatment_type": "APPORTIONED_TREATMENT", "scope": "ORDER" } ], "total_service_charge_money": { "amount": 1000, "currency": "USD" }, "net_amounts": { "total_money": { "amount": 12600, "currency": "USD" }, "tax_money": { "amount": 0, "currency": "USD" }, "discount_money": { "amount": 0, "currency": "USD" }, "tip_money": { "amount": 0, "currency": "USD" }, "service_charge_money": { "amount": 1000, "currency": "USD" } }, "customer_id": "0V2VTQ2XQZJRNA8WXVSF81EWH0", "net_amount_due_money": { "amount": 12600, "currency": "USD" } } } ``` {% /tab %} {% /tabset %} ## Apportioned percentage service charge Suppose the total service charge on the order is 10% of the order subtotal (before tax). The order subtotal is $116, which means that the service charge is $11.60. That amount needs to be apportioned across all line items using the following calculation: * `Dog Biscuits - Chicken Flavor`: ((2 @ $15 per box = $30) / $116) * 11.60 = $3.00 * `Handmade Sweater - Blue`: (($50) / $116) * 11.60 = $5.00 * `Chewy Rawhide - Beef Flavor`: ((3 @ $12 per box = $30) / $116) * 11.60 = $3.60 The three apportioned line-item service charges ($3.00 + $5.00 + $3.60) equal the $11.60 total service charge. ## Apportioned service charge taxes Apportioned service charges cannot have their own tax rate or amount. Instead, `applied_service_charges` inherit the tax of the line item it's applied to. If you add an applied tax to an apportioned service charge, it's ignored. ### Ignored service charge taxes In the following example, an apportioned service charge is declared with a tax applied directly to the service charge: {% tabset %} {% tab id="Request" %} ```json { "service_charges": [ { "calculation_phase": "APPORTIONED_AMOUNT_PHASE", "name": "Fixed amount service charge", "treatment_type": "APPORTIONED_TREATMENT", "uid": "APPORTIONED_AMOUNT_SERVICE_CHARGE", "scope": "LINE_ITEM", "amount_money": { "amount": 1000, "currency": "USD" }, "taxable": true, "applied_taxes": [ { "tax_uid": "SERVICE_CHARGE_TAX", "uid": "2" } ] } ], "taxes": [ { "name": "Service charge tax", "percentage": "8", "scope": "LINE_ITEM", "type": "ADDITIVE", "uid": "SERVICE_CHARGE_TAX" } ] } ``` {% /tab %} {% tab id="Response" %} The following response shows that the service charge is applied to the line item but not the service charge tax: ```json { "line_items": [ { "uid": "H5j9clSbv0cAQoUWGzv7QD", "quantity": "2", "name": "Dog Biscuits - Chicken Flavor", "base_price_money": { "amount": 1500, "currency": "USD" }, "gross_sales_money": { "amount": 3000, "currency": "USD" }, "total_tax_money": { "amount": 0, "currency": "USD" }, "total_discount_money": { "amount": 0, "currency": "USD" }, "total_money": { "amount": 5000, "currency": "USD" }, "variation_total_price_money": { "amount": 3000, "currency": "USD" }, "item_type": "ITEM", "applied_service_charges": [ { "uid": "1", "service_charge_uid": "APPORTIONED_AMOUNT_SERVICE_CHARGE", "applied_money": { "amount": 2000, "currency": "USD" } } ], "total_service_charge_money": { "amount": 2000, "currency": "USD" } }, } ``` {% /tab %} {% /tabset %} A non-apportioned line-item service charge must have its own tax information configured with its own rate, which can be different from the line-item tax rate. If a seller wants to ensure that a service charge is taxed at the same rate as its associated line item, an apportioned service charge should be used. ## Percentage service charges When you specify a percentage service charge to an order, Square calculates the service charge as a percentage of the whole order. If you want to apply service charges to individual line items, you need to use apportioned service charges. Raphael's Puppy-Care Emporium has established a fund to help encourage pet adoption and is adding a service charge for this. The service charge is applied after discounts, but before taxes (subtotal phase). {% aside type="important" %} When processing tips for external vendors (such as third-party delivery couriers), do not use the Payment object's tip fields. Adding tips through the Payment object would incorrectly include these amounts in the seller's internal team members' tip pool. Instead, use the [Order.service_charge](https://developer.squareup.com/reference/square/objects/order#definition__property-service_charges) property on the Order object to record tips for external vendors who help fulfill orders. This ensures: - External vendor tips are properly segregated from internal team member tips - Tips are correctly attributed to the external vendor - Tip amounts don't impact the seller's internal tip pool calculations {% /aside %} {% tabset %} {% tab id="Request" %} The following `CalculateOrder` request defines this service charge (`PET-ADOPT-1.5-PCT`) and applies it to the order: ```` {% /tab %} {% tab id="Response" %} In the following response, the 1.5% `service_charge_money` ($1.74) is added to the $116.00 total, so the `net_amount_due_money` is $117.74: ```json { "order": { "line_items": [ { "quantity": "2", "name": "Dog Biscuits - Chicken Flavor", "base_price_money": { "amount": 1500, "currency": "USD" }, "gross_sales_money": { "amount": 3000, "currency": "USD" }, "total_money": { "amount": 3000, "currency": "USD" } }, { "quantity": "1", "name": "Handmade Sweater - Blue", "base_price_money": { "amount": 5000, "currency": "USD" }, "gross_sales_money": { "amount": 5000, "currency": "USD" }, "total_money": { "amount": 5000, "currency": "USD" } }, { "quantity": "3", "name": "Chewy Rawhide - Beef Flavor", "base_price_money": { "amount": 1200, "currency": "USD" }, "gross_sales_money": { "amount": 3600, "currency": "USD" }, "total_money": { "amount": 3600, "currency": "USD" } } ], "total_money": { "amount": 11774, "currency": "USD" }, "service_charges": [ { "name": "PET-ADOPT-1.5-PCT", "percentage": "1.5", "applied_money": { "amount": 174, "currency": "USD" }, "calculation_phase": "SUBTOTAL_PHASE", "total_money": { "amount": 174, "currency": "USD" }, } ], "total_service_charge_money": { "amount": 174, "currency": "USD" }, "net_amounts": { "total_money": { "amount": 11774, "currency": "USD" }, "service_charge_money": { "amount": 174, "currency": "USD" } }, "net_amount_due_money": { "amount": 11774, "currency": "USD" } } } ``` {% /tab %} {% /tabset %} ## Service charge taxes You can apply a tax to an order-level service charge in the same way you can apply a tax to a line item. In the following example, an order-level service charge of $10 is added to an order. The service charge applies a special service charge line-item tax of 8%. The order-scoped service charge is treated as a single line item, which is why the tax needs to be a `LINE_ITEM` tax. {% tabset %} {% tab id="Request" %} ```` {% aside type="info" %} Setting `service_charges.taxable` has no bearing on whether a tax is charged on the service charge. As long as you set `service_charges.applied_taxes`, a tax will be applied. {% /aside %} {% /tab %} {% tab id="Response" %} The response shows that a service charge tax of $.80 is added to the order and applied to the service charge. In the order total amounts, `total_service_charge_money` doesn't reflect the tax. However, you can see the tax in `net_amounts.tax_money`. ```json { "order": { "location_id": "VJN4XSBFTVPK9", "line_items": [ { "uid": "hc8R2Ky8qG4X7s7z95JYBC", "quantity": "2", "name": "Dog Biscuits - Chicken Flavor", "base_price_money": { "amount": 1500, "currency": "USD" }, "gross_sales_money": { "amount": 3000, "currency": "USD" }, "total_tax_money": { "amount": 0, "currency": "USD" }, "total_discount_money": { "amount": 0, "currency": "USD" }, "total_money": { "amount": 3000, "currency": "USD" }, "variation_total_price_money": { "amount": 3000, "currency": "USD" }, "item_type": "ITEM", "total_service_charge_money": { "amount": 0, "currency": "USD" } }, { "uid": "KuoP2Nl0eZHiXqwn52OMwC", "quantity": "1", "name": "Handmade Sweater - Blue", "base_price_money": { "amount": 5000, "currency": "USD" }, "gross_sales_money": { "amount": 5000, "currency": "USD" }, "total_tax_money": { "amount": 0, "currency": "USD" }, "total_discount_money": { "amount": 0, "currency": "USD" }, "total_money": { "amount": 5000, "currency": "USD" }, "variation_total_price_money": { "amount": 5000, "currency": "USD" }, "item_type": "ITEM", "total_service_charge_money": { "amount": 0, "currency": "USD" } }, { "uid": "gyqXxxDcDbSlSmEoiNFyzC", "quantity": "3", "name": "Chewy Rawhide - Beef Flavor", "base_price_money": { "amount": 1200, "currency": "USD" }, "gross_sales_money": { "amount": 3600, "currency": "USD" }, "total_tax_money": { "amount": 0, "currency": "USD" }, "total_discount_money": { "amount": 0, "currency": "USD" }, "total_money": { "amount": 3600, "currency": "USD" }, "variation_total_price_money": { "amount": 3600, "currency": "USD" }, "item_type": "ITEM", "total_service_charge_money": { "amount": 0, "currency": "USD" } } ], "taxes": [ { "uid": "SERVICE_CHARGE_TAX", "name": "Service charge tax", "percentage": "8", "type": "ADDITIVE", "applied_money": { "amount": 80, "currency": "USD" }, "scope": "LINE_ITEM" } ], "created_at": "2024-06-28T20:47:28.825Z", "updated_at": "2024-06-28T20:47:28.825Z", "state": "OPEN", "version": 1, "total_tax_money": { "amount": 80, "currency": "USD" }, "total_discount_money": { "amount": 0, "currency": "USD" }, "total_tip_money": { "amount": 0, "currency": "USD" }, "total_money": { "amount": 12680, "currency": "USD" }, "service_charges": [ { "uid": "APPORTIONED_AMOUNT_SERVICE_CHARGE", "name": "twelve percent service charge", "amount_money": { "amount": 1000, "currency": "USD" }, "applied_money": { "amount": 1000, "currency": "USD" }, "calculation_phase": "SUBTOTAL_PHASE", "taxable": true, "total_money": { "amount": 1080, "currency": "USD" }, "total_tax_money": { "amount": 80, "currency": "USD" }, "applied_taxes": [ { "uid": "2", "tax_uid": "SERVICE_CHARGE_TAX", "applied_money": { "amount": 80, "currency": "USD" } } ], "type": "CUSTOM", "treatment_type": "LINE_ITEM_TREATMENT", "scope": "ORDER" } ], "total_service_charge_money": { "amount": 1000, "currency": "USD" }, "net_amounts": { "total_money": { "amount": 12680, "currency": "USD" }, "tax_money": { "amount": 80, "currency": "USD" }, "discount_money": { "amount": 0, "currency": "USD" }, "tip_money": { "amount": 0, "currency": "USD" }, "service_charge_money": { "amount": 1000, "currency": "USD" } }, "customer_id": "0V2VTQ2XQZJRNA8WXVSF81EWH0", "net_amount_due_money": { "amount": 12680, "currency": "USD" } } } ``` {% /tab %} {% /tabset %} ## 3rd-Party delivery tip with a service charge Use an order-scoped, non-taxable service charge to represent a tip intended for a 3rd-party courier such as DoorDash rather than the seller’s staff. This keeps the amount out of payment‐level gratuity reporting and avoids affecting item-level taxes. ### Why a service charge? `payment.tip_money` is designed for staff gratuities associated with the seller’s team that should be paid out through payroll. An `order.service_charge` is considered the best place to pass non-line-item order adjustments, including those that are pass-through to the merchants like courier tips and delivery platform fees. ### Recommended settings * `scope`- `ORDER` (applies to the whole order). * `amount_money` - Pass in an explicit tip amount, don't calculate the service charge using a percentage. * `calculation_phase` - `TOTAL_PHASE` or `SUBTOTAL_PHASE`. While this field is required in API requests, it only affects percentage-based calculations. When providing an explicit amount, the phase value is accepted but not used in calculations. Avoid using apportioned phases (like `APPORTIONED_PERCENTAGE_PHASE`), as these follow different rules for distributing amounts across line items. * `taxable` - `false` * `name` - A clear label like "Courier Tip" for downstream reporting. ### Example This example shows a $1.50 tip for the courier who delivered the order: ```json "service_charges": [ { "scope": "ORDER", "calculation_phase": "TOTAL_PHASE", "taxable": false, "amount_money": { "amount": 150, "currency": "USD" }, "name": "Courier Tip" } ] ``` ## See also * [Order Price Adjustments](orders-api/price-adjustments) * [Order Taxes](orders-api/taxes) * [Order Discounts](orders-api/discounts) --- # Order Taxes > Source: https://developer.squareup.com/docs/orders-api/taxes > Status: PUBLIC > Languages: All > Platforms: All Learn how to apply taxes to an order using the Orders API. **Applies to:** [Orders API](orders-api/what-it-does) {% subheading %}Learn how to apply taxes to an order using the Orders API.{% /subheading %} {% toc hide=true /%} ## Overview Building on the example order created in [Order Price Adjustments](orders-api/price-adjustments), learn how to apply taxes to the example order. The final calculations for the order involve taxes. There are two types of taxes and they're processed by the Orders API in this order: 1. **Item-level taxes** - Calculated and applied to one or more individual items. 2. **Order-level taxes** - Calculated for the entire order and then distributed proportionally to each item as apportioned taxes. ## Example In the following example, the state of California has approved the following taxes, which must now be collected: * A 5% fair trade tax, applicable to any handmade goods. * An 8.5% sales tax on every order. {% line-break /%} {% tabset %} {% tab id="Request" %} The following `CalculateOrder` request defines these two taxes (`FAIR-TRADE-5-PCT` and `STATE-SALES-8.5-PCT`) and applies each one appropriately: ```` {% /tab %} {% tab id="Response" %} In the response, the fair trade tax is applied only to the handmade sweater. The state sales tax is calculated for the order total and distributed proportionally across all the items. ```json { "order": { "line_items": [ { "quantity": "2", "name": "Dog Biscuits - Chicken Flavor", "base_price_money": { "amount": 1500, "currency": "USD" }, "gross_sales_money": { "amount": 3000, "currency": "USD" }, "total_tax_money": { "amount": 255, "currency": "USD" }, "total_money": { "amount": 3255, "currency": "USD" }, "applied_taxes": [ { "tax_uid": "STATE-SALES-8.5-PCT", "applied_money": { "amount": 255, "currency": "USD" } } ], }, { "quantity": "1", "name": "Handmade Sweater - Blue", "base_price_money": { "amount": 5000, "currency": "USD" }, "gross_sales_money": { "amount": 5000, "currency": "USD" }, "total_tax_money": { "amount": 675, "currency": "USD" }, "total_money": { "amount": 5675, "currency": "USD" }, "applied_taxes": [ { "tax_uid": "FAIR-TRADE-5-PCT", "applied_money": { "amount": 250, "currency": "USD" } }, { "tax_uid": "STATE-SALES-8.5-PCT", "applied_money": { "amount": 425, "currency": "USD" } } ] }, { "quantity": "3", "name": "Chewy Rawhide - Beef Flavor", "base_price_money": { "amount": 1200, "currency": "USD" }, "gross_sales_money": { "amount": 3600, "currency": "USD" }, "total_tax_money": { "amount": 306, "currency": "USD" }, "total_money": { "amount": 3906, "currency": "USD" }, "applied_taxes": [ { "tax_uid": "STATE-SALES-8.5-PCT", "applied_money": { "amount": 306, "currency": "USD" } } ] } ], "taxes": [ { "uid": "STATE-SALES-8.5-PCT", "name": "State sales tax - 8.5%", "percentage": "8.5", "type": "ADDITIVE", "applied_money": { "amount": 986, "currency": "USD" }, "scope": "ORDER" }, { "uid": "FAIR-TRADE-5-PCT", "name": "Fair Trade Tax - 5%", "percentage": "5", "type": "ADDITIVE", "applied_money": { "amount": 250, "currency": "USD" }, "scope": "LINE_ITEM" } ], "total_tax_money": { "amount": 1236, "currency": "USD" }, "total_money": { "amount": 12836, "currency": "USD" }, "net_amounts": { "total_money": { "amount": 12836, "currency": "USD" }, "tax_money": { "amount": 1236, "currency": "USD" } }, "net_amount_due_money": { "amount": 12836, "currency": "USD" } } } ``` {% /tab %} {% /tabset %} If your application supports service charges, see [Order Service Charges](orders-api/service-charges) to learn how to apply service charges to this example order. To learn how discounts are applied to this example order, see [Order Discounts](orders-api/discounts). ## See also * [Order Price Adjustments](orders-api/price-adjustments) * [Order Service Charges](orders-api/service-charges) * [Order Discounts](orders-api/discounts) --- # Order Price Adjustments > Source: https://developer.squareup.com/docs/orders-api/price-adjustments > Status: PUBLIC > Languages: All > Platforms: All Learn how to adjust prices on an order with taxes, discounts, and service charges by using the Orders API. **Applies to:** [Orders API](orders-api/what-it-does) | [Catalog API](catalog-api/what-it-does) {% subheading %}Learn how to adjust prices on an order with taxes, discounts, and service charges using the Orders API.{% /subheading %} {% toc hide=true /%} ## Overview You can set parameters in a [CreateOrder](https://developer.squareup.com/reference/square/orders-api/create-order) request that tells Square to adjust the line item price for any item in an order. The API calculates these adjustments and applies them as [discounts](orders-api/discounts), [service charges](orders-api/service-charges), or [taxes](orders-api/taxes). Price adjustments are scoped as follows: * **Item level** - The price adjustment is applied specifically to an applicable line item. * **Order level** - The price adjustment is distributed across all the line item subtotals. ## Preview order price adjustments If your application shows the buyer what they will pay for an order when placed, you need to calculate the order before you actually create it. The Orders API provides the [CalculateOrder](https://developer.squareup.com/reference/square/orders-api/CalculateOrder) endpoint that takes the same request body as `CreateOrder`. To preview Square's calculation before you actually create an order, provide the same request body that you will use for `CreateOrder`. In the `CalculateOrder` response, you can see that line item prices now show any discounts, taxes, or service charges that Square applied. You can set these adjustment parameters directly in an order request or, in some cases, you can refer to catalog [taxes](catalog-api/design-a-catalog#taxes) or [discounts](catalog-api/design-a-catalog#discounts) configured by a seller. When you call `CreateOrder` with the same request body, an order is actually created with those calculated values. ## Rounding rules The Orders API performs its adjustment calculations using Bankers' Rounding, also known as Rounding Half to Even. Under Common Rounding rules (Round Half Up), half-values round up to the next closest integer. However, under Bankers' Rounding, half-values round to the closest even integer. This has the effect of rounding even numbers down, while leaving odd numbers unchanged. The following table shows examples of values rounded to two decimal places using Common Rounding and Bankers' Rounding: {% table %} * Value {% width="80px" %} * Common Rounding{% width="100px" %} * Bankers' Rounding --- * 0.505 * 0.51 * 0.50 (0 is the closest even and 1 is odd. Round down to .50) --- * 0.715 * 0.72 * (2 is the closest even and 1 is odd. Round up two 2) --- * 0.085 * 0.09 * 0.08 (8 is the closest even and 9 is odd. Round down to 8) {% /table %} ### Bankers' Rounding Bankers' Rounding is a rounding mode in which non-tie decimals (> 0 and ≠ 5) are rounded up or down to the nearest integer and ties (= 5) are rounded to the nearest even integer. For example, non-ties to two decimal places: 3.222 -> 3.22 and –3.228 -> –3.23. Ties to two decimal places: 3.225 -> 3.22 and –3.235 -> –3.24. ## Example use case To illustrate how the Orders API calculates totals, consider an online order from Raphael's Puppy-Care Emporium. The order consists of the following items: * Two boxes of chicken-flavored dog biscuits, at $15.00 per box. * A handmade sweater for dogs (blue color), at $50.00. * Three packages of beef-flavored chewy rawhide, at $12.00 per package. The following examples use the [CalculateOrder](https://developer.squareup.com/reference/square/orders-api/CalculateOrder) endpoint to demonstrate each step in the process. ## Initial item totals The first step is to calculate the initial total for each item, which is the base price of the item multiplied by the quantity. {% table %} * Quantity * Description * Base price * Item total --- * 2 * Dog Biscuits - Chicken Flavor * $15.00 * $30.00 --- * 1 * Handmade Sweater - Blue * $50.00 * $50.00 --- * 3 * Chewy Rawhide - Beef Flavor * $12.00 * $36.00 {% /table %} The subtotal for the order is the sum of all the item totals: $30.00 + $50.00 + $36.00 = $116.00. {% tabset %} {% tab id="Request" %} The following `CalculateOrder` request shows the items in the order and the totals calculated by the Orders API: ```` {% /tab %} {% tab id="Response" %} In this partial response example, the `gross_sales_money` attributes reflect the item totals, while `net_amount_due_money` reflects the subtotal for the order ($116.00). ```json { "order": { "line_items": [ { "quantity": "2", "name": "Dog Biscuits - Chicken Flavor", "base_price_money": { "amount": 1500, "currency": "USD" }, "gross_sales_money": { "amount": 3000, "currency": "USD" }, }, { "quantity": "1", "name": "Handmade Sweater - Blue", "base_price_money": { "amount": 5000, "currency": "USD" }, "gross_sales_money": { "amount": 5000, "currency": "USD" }, }, { "quantity": "3", "name": "Chewy Rawhide - Beef Flavor", "base_price_money": { "amount": 1200, "currency": "USD" }, "gross_sales_money": { "amount": 3600, "currency": "USD" }, } ], "total_money": { "amount": 11600, "currency": "USD" }, "net_amount_due_money": { "amount": 11600, "currency": "USD" } } } ``` {% /tab %} {% /tabset %} To learn about how discounts are applied to this example order, see [Order Discounts](orders-api/discounts). If your application will support service charges, see [Order Service Charges](orders-api/service-charges) to walk through applying service charges to this example order. To learn about applying taxes to the example order, see [Order Taxes](orders-api/taxes). ## See also * [Order Discounts](orders-api/discounts) * [Order Service Charges](orders-api/service-charges) * [Order Taxes](orders-api/taxes) --- # Order Discounts > Source: https://developer.squareup.com/docs/orders-api/discounts > Status: PUBLIC > Languages: All > Platforms: All Learn how to apply discounts to an order using the Orders API. **Applies to:** [Orders API](orders-api/what-it-does) | [Catalog API](catalog-api/what-it-does) {% subheading %}Learn how to apply discounts to an order using the Orders API.{% /subheading %} {% toc hide=true /%} ## Overview Building on the example order created in [Order Price Adjustments](orders-api/price-adjustments), learn how to apply discounts to the example order. If your application uses catalog pricing rules to calculate discount amounts, see [Automatically Apply Discounts](catalog-api/cookbook/auto-apply-discounts) for information about setting up discount pricing rules. After the Orders API calculates item totals, it considers any available discounts of these types: * **Discount on order total** - A discount can be defined in the order and based on a percentage (for example, 10% off) or based on a fixed amount and then applied to the order total. * **Discount on line item** - A discount can be defined in the order and applied to an individual item or the entire order. * **Pricing rule discount on line item** - A catalog pricing rule for [Square-applied order discounts](orders-api/apply-taxes-and-discounts/auto-apply-discounts) and applied to line items in the order. If an order has multiple discounts of different scopes and types, they're calculated and applied in this sequence: 1. Item-level percentage 2. Order-level percentage 3. Order-level fixed amount 4. Item-level fixed amount {% aside type="important" %} Because order-level fixed-amount discounts are applied before item-level fixed-amount discounts, the order-level discount can reduce an item's price before the item-level discount is calculated. If the item-level discount is close to the item's base price, it might not fully apply. To avoid this, convert the item-level fixed-amount discount to an equivalent percentage discount. {% /aside %} {% accordion %} {% slot "heading" %} ## Percentage discounts {% /slot %} ### On items Chicken-flavored dog biscuits have been discontinued, so this item receives a 7% discount. The `applied_discounts` attribute tells Square that this line item gets a specific discount and the `discounts` attribute defines what the discount is. `applied_discounts` can be set on any line item and reference any `discount` defined in the order. Note that `applied_discounts` is a list, which means that multiple discounts can be applied to a single line item. {% tabset %} {% tab id="Request" %} The following `CalculateOrder` request defines the discount (`DISCONTINUED-7-PCT`) and applies it to `Dog Biscuits - Chicken Flavor`: ```` {% /tab %} {% tab id="Response" %} In the response, `total_discount_money` for the item is $2.10, which is 7% of the `gross_sales_money` for the item. The item total is adjusted from $30.00 to $27.90, which lowers the order total from $116.00 to $113.90. ``` { "order": { ... "line_items": [ { ... "quantity": "2", "name": "Dog Biscuits - Chicken Flavor", "base_price_money": { "amount": 1500, "currency": "USD" }, "gross_sales_money": { "amount": 3000, "currency": "USD" }, ... "total_discount_money": { "amount": 210, "currency": "USD" }, "total_money": { "amount": 2790, "currency": "USD" }, ... "applied_discounts": [ { ... "discount_uid": "DISCONTINUED-7-PCT", "applied_money": { "amount": 210, "currency": "USD" } } ], ... }, ... ], "discounts": [ { "uid": "DISCONTINUED-7-PCT", "name": "Discontinued - 7% off", "percentage": "7", "applied_money": { "amount": 210, "currency": "USD" }, "type": "FIXED_PERCENTAGE", "scope": "LINE_ITEM" } ], ... "total_discount_money": { "amount": 210, "currency": "USD" }, ... "total_money": { "amount": 11390, "currency": "USD" }, ... "net_amounts": { "total_money": { "amount": 11390, "currency": "USD" }, ... "discount_money": { "amount": 210, "currency": "USD" }, ... }, "net_amount_due_money": { "amount": 11390, "currency": "USD" } } } ``` {% /tab %} {% /tabset %} ### On orders To celebrate National Puppy Day, the entire order receives a 12% discount. The Orders API calculates and applies order-level discounts as line-item price adjustments, so it apportions the discount across all the line-item subtotals. In this case, the `applied_discounts` attribute isn't used. The `discounts` attribute uses a `scope` parameter to tell Square to apply the discount to all line items. {% tabset %} {% tab id="Request" %} The following `CalculateOrder` request defines the discount (`NATL-PUPPY-12-PCT`) to be applied to all the items: ```` {% /tab %} {% tab id="Response" %} In the response, the 12% order discount is shown as `total_discount_money`. The amount is deducted from `gross_sales_money`, resulting in `total_money` for the item. The total `discount_money` for the order is $13.92. When subtracted from the $116.00 item total, the resulting `net_amount_due_money` is $102.08. ``` { "order": { ... "line_items": [ { ... "quantity": "2", "name": "Dog Biscuits - Chicken Flavor", "base_price_money": { "amount": 1500, "currency": "USD" }, "gross_sales_money": { "amount": 3000, "currency": "USD" }, ... "total_discount_money": { "amount": 360, "currency": "USD" }, "total_money": { "amount": 2640, "currency": "USD" }, ... "applied_discounts": [ { ... "discount_uid": "NATIONAL PUPPY DAY - 12 PERCENT OFF", "applied_money": { "amount": 360, "currency": "USD" } } ], ... }, { ... "quantity": "1", "name": "Handmade Sweater - Blue", "base_price_money": { "amount": 5000, "currency": "USD" }, "gross_sales_money": { "amount": 5000, "currency": "USD" }, ... "total_discount_money": { "amount": 600, "currency": "USD" }, "total_money": { "amount": 4400, "currency": "USD" }, ... "applied_discounts": [ { ... "discount_uid": "NATIONAL PUPPY DAY - 12 PERCENT OFF", "applied_money": { "amount": 600, "currency": "USD" } } ], ... }, { ... "quantity": "3", "name": "Chewy Rawhide - Beef Flavor", "base_price_money": { "amount": 1200, "currency": "USD" }, "gross_sales_money": { "amount": 3600, "currency": "USD" }, ... "total_discount_money": { "amount": 432, "currency": "USD" }, "total_money": { "amount": 3168, "currency": "USD" }, ... "applied_discounts": [ { ... "discount_uid": "NATIONAL PUPPY DAY - 12 PERCENT OFF", "applied_money": { "amount": 432, "currency": "USD" } } ], ... } ], "discounts": [ { "uid": "NATIONAL PUPPY DAY - 12 PERCENT OFF", "name": "National Puppy Day - 12% off", "percentage": "12", "applied_money": { "amount": 1392, "currency": "USD" }, "type": "FIXED_PERCENTAGE", "scope": "ORDER" } ], ... "total_discount_money": { "amount": 1392, "currency": "USD" }, ... "total_money": { "amount": 10208, "currency": "USD" }, ... "net_amounts": { "total_money": { "amount": 10208, "currency": "USD" }, ... "discount_money": { "amount": 1392, "currency": "USD" }, ... }, "net_amount_due_money": { "amount": 10208, "currency": "USD" } } } ``` In this example, you can see the discount of $13.92 shown in three places: * **Individual discount total** - The total for the single discount defined. If there were multiple discounts on the order, each discount would be totaled in `discounts[].applied_money`. * **Total order discounts** - The sum total of all discounts applied to the order in `total_discount_money`. * **Amount subtracted from order total** - The amount discounted from the total amount of the order in `net_amounts.discount_money`. The latter two attributes are always the same amount. When coding a report, such as a sales activity report, you can use the `net_amounts` attribute instead of `total_money` and `total_discount_money` to populate report line items. {% /tab %} {% /tabset %} {% /accordion %} {% accordion %} {% slot "heading" %} ## Fixed amount discounts {% /slot %} ### On items To thank customers for their business, a Puppy Appreciation Discount is applied to dog treats. Customers save $3.00 on chicken-flavor dog biscuits and $11.00 on beef-flavor chewy rawhide. {% tabset %} {% tab id="Request" %} The following `CalculateOrder` request defines two discounts (APPREC-3-USD and APPREC-11-USD) and applies them to the appropriate items: ```` {% /tab %} {% tab id="Response" %} In the response, $3.00 is deducted from the dog biscuits and $11.00 is deducted from the rawhide. The total `discount_money` for the order is $14.00. When subtracted from the $116.00 item total, the resulting `net_amount_due_money` is $102.00. {% aside type="info" %} An item-level fixed discount is applied to the extended price of the line item. In the example, two dog biscuits were ordered for a gross amount of $30. The fixed $3 discount is applied, leaving a net total of $27 for the line item. {% /aside %} ``` { "order": { ... "line_items": [ { ... "quantity": "2", "name": "Dog Biscuits - Chicken Flavor", "base_price_money": { "amount": 1500, "currency": "USD" }, "gross_sales_money": { "amount": 3000, "currency": "USD" }, ... "total_discount_money": { "amount": 300, "currency": "USD" }, "total_money": { "amount": 2700, "currency": "USD" }, ... "applied_discounts": [ { ... "discount_uid": "APPREC-3-USD", "applied_money": { "amount": 300, "currency": "USD" } } ], ... }, { ... "quantity": "1", "name": "Handmade Sweater - Blue", "base_price_money": { "amount": 5000, "currency": "USD" }, "gross_sales_money": { "amount": 5000, "currency": "USD" }, ... "total_discount_money": { "amount": 0, "currency": "USD" }, "total_money": { "amount": 5000, "currency": "USD" }, ... }, { ... "quantity": "3", "name": "Chewy Rawhide - Beef Flavor", "base_price_money": { "amount": 1200, "currency": "USD" }, "gross_sales_money": { "amount": 3600, "currency": "USD" }, ... "total_discount_money": { "amount": 1100, "currency": "USD" }, "total_money": { "amount": 2500, "currency": "USD" }, ... "applied_discounts": [ { ... "discount_uid": "APPREC-11-USD", "applied_money": { "amount": 1100, "currency": "USD" } } ], ... } ], "discounts": [ { "uid": "APPREC-3-USD", "name": "Customer Appreciation Discount - $3 off", "amount_money": { "amount": 300, "currency": "USD" }, "applied_money": { "amount": 300, "currency": "USD" }, "type": "FIXED_AMOUNT", "scope": "LINE_ITEM" }, { "uid": "APPREC-11-USD", "name": "Customer Appreciation Discount - $11 off", "amount_money": { "amount": 1100, "currency": "USD" }, "applied_money": { "amount": 1100, "currency": "USD" }, "type": "FIXED_AMOUNT", "scope": "LINE_ITEM" } ], ... "total_discount_money": { "amount": 1400, "currency": "USD" }, ... "total_money": { "amount": 10200, "currency": "USD" }, ... "net_amounts": { "total_money": { "amount": 10200, "currency": "USD" }, ... "discount_money": { "amount": 1400, "currency": "USD" }, ... }, "net_amount_due_money": { "amount": 10200, "currency": "USD" } } } ``` {% /tab %} {% /tabset %} ### On orders To celebrate the storewide Anniversary Sale, a discount of $5.00 is applied to the entire order. The Orders API calculates and applies order-level discounts as line-item price adjustments, so it evenly distributes the discount across all the line-item subtotals. {% tabset %} {% tab id="Request" %} The following `CalculateOrder` request defines the discount `(ANNI-SALE-5-USD)` to be applied to all the items: ```` {% /tab %} {% tab id="Response" %} In the response, the $5.00 discount is distributed across the item totals, in proportion to the $116.00 subtotal for the order. {% table %} * Item * Item total * % of $116.00 * Contribution to discount --- * Biscuits * $30.00 * 25.86% * $1.29 --- * Sweater * $50.00 * 43.10% * $2.16 --- * Rawhide * $36.00 * 31.03% * $1.55 {% /table %} The total `discount_money` for the order is $5.00. When subtracted from the $116.00 item total, the resulting `net_amount_due_money` is $111.00. ``` { "order": { ... "line_items": [ { ... "quantity": "2", "name": "Dog Biscuits - Chicken Flavor", "base_price_money": { "amount": 1500, "currency": "USD" }, "gross_sales_money": { "amount": 3000, "currency": "USD" }, ... "total_discount_money": { "amount": 129, "currency": "USD" }, "total_money": { "amount": 2871, "currency": "USD" }, ... "applied_discounts": [ { ... "discount_uid": "GLOBAL-SALES-5-DOLLARS-OFF", "applied_money": { "amount": 129, "currency": "USD" } } ], ... }, { ... "quantity": "1", "name": "Handmade Sweater - Blue", "base_price_money": { "amount": 5000, "currency": "USD" }, "gross_sales_money": { "amount": 5000, "currency": "USD" }, ... "total_discount_money": { "amount": 216, "currency": "USD" }, "total_money": { "amount": 4784, "currency": "USD" }, ... "applied_discounts": [ { ... "discount_uid": "GLOBAL-SALES-5-DOLLARS-OFF", "applied_money": { "amount": 216, "currency": "USD" } } ], ... }, { ... "quantity": "3", "name": "Chewy Rawhide - Beef Flavor", "base_price_money": { "amount": 1200, "currency": "USD" }, "gross_sales_money": { "amount": 3600, "currency": "USD" }, ... "total_discount_money": { "amount": 155, "currency": "USD" }, "total_money": { "amount": 3445, "currency": "USD" }, ... "applied_discounts": [ { ... "discount_uid": "GLOBAL-SALES-5-DOLLARS-OFF", "applied_money": { "amount": 155, "currency": "USD" } } ], "item_type": "ITEM" } ], "discounts": [ { "uid": "GLOBAL-SALES-5-DOLLARS-OFF", "name": "Global Sales - $5 off", "amount_money": { "amount": 500, "currency": "USD" }, "applied_money": { "amount": 500, "currency": "USD" }, "type": "FIXED_AMOUNT", "scope": "ORDER" } ], ... "total_discount_money": { "amount": 500, "currency": "USD" }, ... "total_money": { "amount": 11100, "currency": "USD" }, ... "net_amounts": { "total_money": { "amount": 11100, "currency": "USD" }, ... "discount_money": { "amount": 500, "currency": "USD" }, ... }, "net_amount_due_money": { "amount": 11100, "currency": "USD" } } } ``` {% /tab %} {% /tabset %} {% /accordion %} {% accordion %} {% slot "heading" %} ## Item catalog pricing rule discount {% /slot %} The owners of Raphael's Puppy-Care Emporium have decided to offer a Buy One, Get One Free discount to motivate customers to buy combinations of dog biscuit items. To expedite the checkout process, they set up a pricing rule in their catalog to [automatically apply discounts](catalog-api/cookbook/auto-apply-discounts). On every order, they add the [pricing_options](https://developer.squareup.com/reference/square/objects/Order#definition__property-pricing_options) attribute and set `auto_apply_discounts`. That's all they need to do. When set, any order that contains a catalog item that matches a price rule is automatically discounted. {% tabset %} {% tab id="Request" %} The following example shows an order for two cases of dog biscuits. The details of the line item are taken from the [CatalogItemVariation](https://developer.squareup.com/reference/square/objects/CatalogItemVariation) referenced by `catalog_object_id`. ```` {% /tab %} {% tab id="Response" %} Because Raphael's Puppy-Care Emporium is giving away a package of chewy rawhide for every two cases of biscuits purchased, the Order is automatically updated with the Buy One, Get One Free discount defined by the catalog as shown in this response: ``` { "order":{ "id":"5FLV8dgGYHgRdrE4XOR9W8RMxh4F", "location_id":"7WQ0KXC8ZSD90", "line_items":[ { "uid":"PpAcZ9GNazqgGpy99BHbUC", "catalog_object_id":"K5NRPU4HXO6O5RFFHLEW3TI2", "quantity":"1", "name":""Chewy Rawhide", "variation_name":"Chewy Rawhide - Beef Flavor", "base_price_money":{ "amount":500, "currency":"USD" }, "total_discount_money":{ "amount":500, "currency":"USD" }, "applied_discounts":[ { "uid":"ApDUTXKYyDukeiA98cG6mD", "discount_uid":"a5nircWuDBWzPBh797O5SD", "applied_money":{ "amount":500, "currency":"USD" } } ], "item_type":"ITEM" }, { "uid":"PpAcZ9GNazqgGpy99BHbUC", "catalog_object_id":"RZNRPU4HXO6O5ROGHLEW3TI2", "quantity":"2", "name":""Dog Biscuits", "variation_name":"Dog Biscuits - Beef Flavor", "base_price_money":{ "amount":1500, "currency":"USD" }, "item_type":"ITEM" } ], "discounts":[ { "uid":"a5nircWuDBWzPBh797O5SD", "catalog_object_id":"FJYAGNWCJSX42774FCLQZ25Z", "name":"BOGO", "percentage":"100.0", "applied_money":{ "amount":"500, "currency":"USD" }, "type":"FIXED_PERCENTAGE", "scope":"LINE_ITEM", "pricing_rule_id":"TL3AYTOI4DB4HYTL2WK3QJ6J" } ], "total_money":{ "amount":3000, "currency":"USD" }, "pricing_options":{ "auto_apply_discounts":true } } } ``` In this example, the chewy rawhide line item has an `applied_discount` attribute to indicate that it's the free item, while the two cases of biscuits are sold at the regular price. The `discounts` attribute on the order details the discount given on the order and references the pricing rule applied from the catalog. {% /tab %} {% /tabset %} {% /accordion %} ## See also * [Order Price Adjustments](orders-api/price-adjustments) * [Order Service Charges](orders-api/service-charges) * [Order Taxes](orders-api/taxes) * [Redeem Loyalty Points for a Discount](loyalty-api/walkthrough1/redeem-points) --- # Configure the Mobile Payments SDK for Square Stand or Square Kiosk > Source: https://developer.squareup.com/docs/mobile-payments-sdk/ios/square-stand > Status: PUBLIC > Languages: All > Platforms: iOS Configure the Mobile Payments SDK on iOS to use Square Stand or Square Kiosk to take card payments. **Applies to:** [Mobile Payments SDK - iOS](https://developer.squareup.com/docs/sdk/mobile-payments/ios) {% subheading %}Learn how to configure your Mobile Payments SDK iOS application to work with Square Stand and Square Kiosk.{% /subheading %} {% toc hide=true /%} ## Requirements and limitations You need to [install the Mobile Payments SDK](mobile-payments-sdk/ios#1-install-the-sdk-and-dependencies) and [authorize your application](mobile-payments-sdk/ios/configure-authorize) to take payments. {% aside type="info" %} Other than Square Readers, the Mobile Payments SDK cannot talk to any printers, cash drawers, barcode scanners, or other peripherals connected through Square Stand or Square Kiosk. {% /aside %} ## Update your Info.plist file for Square Stand To use [Square Stand](https://squareup.com/stand) or [Square Kiosk](https://squareup.com/us/en/hardware/kiosk) with your Mobile Payments SDK application, add the following key-value pairs to your `Info.plist` file. The SDK automatically detects whether a Square Stand or Square Kiosk is connected and uses it to accept card payments. Key | Values ----| ------ [UISupportedExternalAccessoryProtocols](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/iPhoneOSKeys.html#//apple_ref/doc/uid/TP40009252-SW4) | `com.squareup.s020`{% line-break /%}`com.squareup.s025`{% line-break /%}`com.squareup.s089`{% line-break /%}`com.squareup.protocol.stand` {% aside type="warning" %} Before you can submit an application using Square Stand or Square Kiosk to the Apple App Store, Square must notify Apple that your application is authorized to work with Square Stand or Square Kiosk. This process might take several weeks to complete. [Contact us](https://docs.google.com/forms/d/e/1FAIpQLSe5X3KEGZcwH_79t7jcywK9Q_sI7cw9ZHTzpuURjZQKIwvr0g/viewform) for assistance. {% /aside %} ## See also * [Build on iOS](mobile-payments-sdk/ios) --- # Migrate from the Reader SDK > Source: https://developer.squareup.com/docs/mobile-payments-sdk/migrate > Status: PUBLIC > Languages: All > Platforms: iOS, Android Learn how to migrate applications from the Reader SDK to use the Mobile Payments SDK. **Applies to:** [Mobile Payments SDK - Android](https://developer.squareup.com/docs/sdk/mobile-payments/android) | [Mobile Payments SDK - iOS](https://developer.squareup.com/docs/sdk/mobile-payments/ios) | [Mobile Authorization API](mobile-authz/what-it-does) | [OAuth API](oauth-api/overview) | [Payments API](payments-refunds) | [Orders API](orders-api/what-it-does) {% subheading %}Learn how to migrate applications from the Reader SDK to use the Mobile Payments SDK.{% /subheading %} {% toc hide=true /%} ## Overview The Mobile Payments SDK is the successor of the Reader SDK, providing a simple and PCI-compliant payment flow for iOS and Android applications. Developers should migrate their Reader SDK applications to the Mobile Payments SDK to benefit from new features and ensure continued support **before the retirement of the Reader SDK on December 31, 2025**. The following sections cover the major differences between the Reader SDK and the Mobile Payments SDK. When migrating your application, you can also reference the Mobile Payments SDK sample applications for [iOS](https://github.com/square/mobile-payments-sdk-ios) and [Android](https://github.com/square/mobile-payments-sdk-android). {% aside type="info" %} When migrating or upgrading to the Mobile Payments SDK **version 2.1 or above**, you are required to submit an application signature to Square prior to taking production payments. See the instructions for [iOS](mobile-payments-sdk/ios#application-signature) and [Android](mobile-payments-sdk/android#application-signature). {% /aside %} ## OAuth authentication The Reader SDK requires a mobile authorization code generated with the Mobile Authorization API to authorize the SDK, in addition to the standard OAuth flow. In contrast, the Mobile Payments SDK doesn't require a mobile authorization code, only the standard [PKCE OAuth flow](oauth-api/overview#pkce-flow). The Mobile Authorization API isn't required to use the Mobile Payments SDK. When a seller signs in to Square using your [authorization URL](oauth-api/create-urls-for-square-authorization) and approves your authorization request, your redirect URL receives the authorization response containing an authorization code. This code is used to call the `ObtainToken` endpoint, which returns the Square seller's `access_token` in the response. Use this access token with the Mobile Payments SDK `AuthorizationManager.authorize` method and when calling other Square APIs. To learn more about the OAuth workflow, see [OAuth API](oauth-api/overview). ## Payments, not transactions The Mobile Payments SDK is built on the [Payments API](payments-refunds), enabling a comprehensive integration with the rest of the Square Developer platform. In contrast, the Reader SDK is built on the now deprecated Transactions API, which shouldn't be used moving forward. Working with payments instead of transactions allows you to benefit from all the capabilities offered by this and other APIs, including: * Collecting developer [application fees](payments-api/take-payments-and-collect-fees) (a portion of each payment your application processes using the Mobile Payments SDK). * The [delayed capture of payments](payments-api/take-payments/card-payments/delayed-capture) and a configurable delay duration before completing or voiding a payment. * Adding tips, discounts, and taxes using the [Orders API](orders-api/what-it-does). * Associating a payment with a `reference_id` or `team_member_id`. For more information about modifying your application to use payments instead of transactions, see [Migrate from the Transactions API](payments-api/migrate-from-transactions-api). ## Reader management and settings screen Another change from the Reader SDK is the addition of programmatic reader management in the Mobile Payments SDK. You can use the `ReaderManager` to pair readers and get a list of readers connected to the mobile device running your application. The `ReaderInfo` class provides details about connected readers such as model, battery percentage, serial number, and card entry methods. You can use this class to create custom screens to display information about the status of readers to application users or use a Square-provided settings screen for quick integration. ## Store a card on file The Reader SDK includes a built-in workflow for saving a card on file. The Mobile Payments SDK relies on the [Cards API](https://developer.squareup.com/reference/square/cards-api) to save a card to a customer's profile. To store a card on file with the Mobile Payments SDK: 1. Authorize a payment with a credit card. This payment can be [delayed and canceled later](mobile-payments-sdk/ios/take-payments#delay-the-capture-of-payments) if you don’t want to charge the customer. 2. Note the `payment_id` value. 3. Use the `CreateCard` endpoint to save the card on file. For more information, see [Create a Card on File from a Payment ID](cards-api/walkthrough/card-from-payment-id#step-3-create-a-card-on-file-using-the-payment-id-as-the-source). ## Split tender payments To take multiple payments or accept different forms of payment on a single order, your application must take each payment separately with the Mobile Payments SDK. You should build your own checkout flow UI to give buyers the option of split tender payments, create separate `PaymentParameters` for each payment, and then make individual calls to `PaymentManager.startPayment()` (iOS) or `PaymentManager.startPaymentActivity()` (Android). If you're using the [Orders API](https://developer.squareup.com/reference/square/orders-api) and want to split a payment for an order, each payment made with the Mobile Payments SDK must include the `orderID` in the `PaymentParameters` and `autocomplete` must be set to `false`. To complete the payments, call the [PayOrder](https://developer.squareup.com/reference/square/orders-api/pay-order) endpoint, specifying each of the payment IDs. For more information, see [Pay for Orders](orders-api/pay-for-orders). ## Tipping Tips can be added to a payment before or after authorization by including an optional `tipMoney` value in the payment parameters or by calling the [UpdatePayment](https://developer.squareup.com/reference/square/payments-api/update-payment) endpoint and setting `tip_money` prior to completing a delayed payment. The Mobile Payments SDK doesn't include a built-in tipping UI, so you should build your own tipping UI for your application. ## Signature collection Signatures aren't required for inserted, tapped, or swiped card payments. The Mobile Payments SDK doesn't include a signature collection workflow. ## Receipts Your application must provide buyers the option to receive a digital or printed receipt. Receipts aren't sent directly by Square. [You can generate receipts for customers](mobile-payments-sdk/ios/take-payments#receipts) using `cardDetails` from the SDK's payment response or send customers a link to the Square-generated `receipt_url`, found in the Square `Payment` object. This URL is only present on `COMPLETED` payments. --- # Mobile Payments SDK > Source: https://developer.squareup.com/docs/mobile-payments-sdk > Status: PUBLIC > Languages: All > Platforms: All Use the Mobile Payments SDK to accept in-person payments using an embedded Square payment flow in iOS and Android applications. **Applies to:** [Mobile Payments SDK - Android](https://developer.squareup.com/docs/sdk/mobile-payments/android) | [Mobile Payments SDK - iOS](https://developer.squareup.com/docs/sdk/mobile-payments/ios) | [Reader SDK](reader-sdk/what-it-does) | [Payments API](payments-refunds) | [Orders API](orders-api/what-it-does) | [OAuth API](oauth-api/overview) {% subheading %}Use the Mobile Payments SDK to accept in-person payments in iOS and Android applications.{% /subheading %} {% toc hide=true /%}  ## Overview The Mobile Payments SDK lets developers accept in-person payments using an embedded Square payment flow in iOS and Android applications. Sellers can take mobile payments using a Square Reader or Square Stand. The Mobile Payments SDK is the successor of the Reader SDK and offers the following benefits: * **Offline payments** - Take and store payments locally on a mobile device. Offline payments allows you to build reliability into your application experience so sellers can keep taking payments even if devices lose their network connection. For more information, see [Offline Payments for Android](mobile-payments-sdk/android/offline-payments) or [Offline Payments for iOS](mobile-payments-sdk/ios/offline-payments). * **Mobile payment integration with the [Payments API](payments-refunds)** - Payments taken with the Mobile Payments SDK are linked to Square `Payment` objects to enable: * The delayed capture of payments. * Partial authorization, which is used to split payments across multiple credit cards. * A configurable delay duration before voiding an authorization. * Collecting application fees. * Associating a payment with reference ID, team member ID, or location ID. * Accepting cash payments. * **Mobile payment integration with the [Orders API](orders-api/what-it-does)** - Payments taken with the Mobile Payments SDK can be linked to Square `Order` objects to enable: * Itemization, where a payment is separated into different line items representing `Catalog` objects. * Applying price modifications such as taxes, discounts, or service charges on items. * Order-ahead functionality and sending SDK payments to Square Point of Sale for fulfillment. * **Sandbox support** - Authorize the Mobile Payments SDK with a Square Sandbox account to test your mobile payment integration with fake payments. You can simulate a virtual reader device to take test payments in the Square Sandbox without a physical card reader. * **Programmatic reader management** - Programmatically pair card readers and monitor their battery level, state, and available payment methods using the SDK `ReaderManager`. * **Sample applications** – Square provides Mobile Payments SDK sample applications for [iOS](https://github.com/square/mobile-payments-sdk-ios) (written in Swift using SwiftUI) and [Android](https://github.com/square/mobile-payments-sdk-android) (written in Kotlin using Compose) to demonstrate a basic implementation of initialization, authorization, requesting permissions, and taking payments in the Sandbox and in production. ## Requirements and limitations * The Mobile Payments SDK is currently available for accounts based in the United States, Canada, the United Kingdom, and Australia. * Using the Mobile Payments SDK to implement payment solutions in unattended terminals or unattended kiosks is strictly prohibited. For example, an outdoor vending machine is unattended because it can be accessed by users outside of normal business hours and might not be in the line of sight of a seller or worker. * The Mobile Payments SDK can only be used to implement payment solutions in attended terminals or attended kiosks. A terminal or kiosk is considered attended if all the following conditions are met: * It cannot be physically accessed by buyers outside the seller's normal business hours. * It's in the line of sight of the seller or one of the seller's workers. * The onsite seller and workers are trained to use the payment solution and are available to assist and support customers with completing transactions. * Card issuers impose transaction limits that might trigger a cardholder verification method (CVM) when a transaction or cumulative limit is reached. These limits vary by payment method and by country and region. Verification is handled in the Mobile Payments SDK by entering a personal identification number (PIN) during the payment flow when required by the card issuer. * When using Mobile Payments SDK version 2.1 or later, you must provide an application signature to Square before taking payments in production. Learn how to do so for [Android](mobile-payments-sdk/android#application-signature) and [iOS](mobile-payments-sdk/ios#application-signature). ## Mobile Payments SDK components The Mobile Payments SDK is divided into four managers that provide specific areas of functionality: * **Authorization** - The `AuthorizationManager` handles authorizing and deauthorizing your application to process payments on behalf of a Square seller using an [OAuth access token](oauth-api/what-it-does) and a [location ID](locations-api). OAuth access tokens are used to get authenticated and scoped access to any Square account. These tokens should be used even if you only plan to use the Mobile Payments SDK with your own Square account. For more information, see [OAuth sample applications](sample-apps#oauth-samples). * **Reader pairing and management** - The `ReaderManager` allows you to pair card readers, listen for reader status updates, and perform actions on reader devices. * **Payments** - The `PaymentManager` handles the payment flow for your application, which includes determining the available payment methods, taking payments, and accessing payments taken offline. * **Settings** – The `SettingsManager` provides an optional device management UI that you can use in your application and provides details about the current SDK version. ## OAuth permissions At minimum, your application must have the following permissions to use the Mobile Payments SDK: * `MERCHANT_PROFILE_READ` * `PAYMENTS_WRITE` * `PAYMENTS_WRITE_IN_PERSON` * To collect application fees, you must also have `PAYMENTS_WRITE_ADDITIONAL_RECIPIENTS` permission. * To add credit card surcharges, you must also have `ITEMS_READ` permission. Depending on its functionality and the other Square APIs used by your application, you might need to request other OAuth permissions for mobile payment integration from sellers. To learn which permissions are required for each API, see [Square OAuth Permissions Reference](oauth-api/square-permissions). {% aside type="important" %} Square OAuth access tokens expire after 30 days. Before expiration, applications must generate a new OAuth access token using the refresh token received when the authorization was first granted. {% /aside %} ## Hardware devices and capabilities The Mobile Payments SDK can be used to take payments with any version of Square Reader or Square Stand. Offline payment support is limited to the two most recent versions of Square Reader for contactless and chip, Square Reader for magstripe, and Square Stand second generation. ### Identify your Square Reader version All Square Readers have a 16-digit serial number. Each Square Reader lists its version number in the 5th to 8th digits of the serial number like this XXXX**MMMM**XXXXXXXX. When you locate the serial number, refer to the **MMMM sequence** in the serial number. {% table %} * Product name * Version * MMMM sequence * Charging port * Offline payments support --- * Square Reader for contactless and chip (2nd generation) * Version 1 (v1) * S171, S172 * USB-C * ✅ --- * Square Reader for contactless and chip (1st generation) * Version 3 (v3) * S109, S130, S146, S148, S192, S193 * Micro USB * ✅ --- * Square Reader for contactless and chip (1st generation) * Version 2 (v2) * S084, S095, S098, S103, S127 * Micro USB * ⛔️ --- * Square Reader for contactless and chip (1st generation) * Version 1 (v1) * S070, S092 * Micro USB * ⛔️ --- * Square Reader for magstripe * Version 1 (v1) * N/A * N/A * ✅ {% /table %} ### Identify your Square Stand version Each Square Stand lists its model number in the 5th to 8th digits of the serial number like this XXXX**MMMM**XXXXXXXX. When you locate the serial number, refer to the **MMMM sequence** in the serial number. {% table %} * Product name * Serial number * MMMM sequence * Color * Hardware * Offline payments support --- * Square Stand (2nd generation) * 16-digit serial number * S176, S155 * Black & White * Built-in contactless and chip reader * ✅ --- * Square Stand (1st generation) * 16-digit or 14-digit serial number * S142, S015, S020, S021, S067, S025, S068, S089, S089 * White * Built-in magstripe reader * ✅ {% /table %} The `ReaderManager` can be used to access the device serial number programmatically to determine the device version. A payment attempted with an offline flow on unsupported hardware results in an error. ### Update policy Developers should make their best efforts to update their working version of the Mobile Payments SDK as soon as new versions are available. Square supports a given version of the Mobile Payments SDK for 2 years following its GA (General Availability) release, unless an update is required to address critical security updates or vulnerabilities or as otherwise deemed necessary by Square. If it becomes necessary to discontinue support for an SDK version before the 2-year life expectancy, Square contacts you in advance using the email address associated with your Square account. ## See also * [Build on Android](mobile-payments-sdk/android) * [Build on iOS](mobile-payments-sdk/ios) --- # Build on iOS > Source: https://developer.squareup.com/docs/mobile-payments-sdk/ios > Status: PUBLIC > Languages: Swift, Objective C > Platforms: iOS **Applies to:** [Mobile Payments SDK - iOS](https://developer.squareup.com/docs/sdk/mobile-payments/ios) | [Payments API](payments-refunds) | [OAuth API](oauth-api/overview) {% subheading %}Learn how to build a secure in-person payment solution for iOS devices with the Mobile Payments SDK.{% /subheading %} {% line-break /%} {% toc hide=true /%} ## Overview The Mobile Payments SDK offers developers a secure way to integrate the Square checkout flow into their mobile applications, and accept in-person payments with a Square Reader or Square Stand. There are four steps required to take your first payment using the Mobile Payments SDK: 1. [Install and configure the Mobile Payments SDK in your Xcode application](#install-sdk) 2. [Authorize the Mobile Payments SDK](mobile-payments-sdk/ios/configure-authorize#authorize) 3. [Pair a card reader](mobile-payments-sdk/ios/pair-manage-readers) 4. [Take a payment](mobile-payments-sdk/ios/take-payments) For an example implementation, download the [Mobile Payments SDK sample application](https://github.com/square/mobile-payments-sdk-ios), written in Swift using SwiftUI. ## Requirements and limitations To build with the Mobile Payments SDK for iOS, the following must be true: * You have a Square account enabled for payment processing. If you haven't enabled payment processing on your account (or you're not sure), visit [squareup.com/activation](https://squareup.com/activation). * Your Square account is based in the United States, Canada, the United Kingdom, or Australia. If you're developing from outside these areas, you can take test payments in the Square Sandbox, but cannot take production payments. * You're using Xcode 15.1+. * You've set your Xcode iOS deployment target to iOS 16+. * You have a Square application ID. You can find your application ID on the [Developer Console](https://developer.squareup.com/apps). * Your application must [request an OAuth access token](build-basics/access-tokens#get-an-oauth-access-token) to authorize Square sellers using your application to take payments. * Your version of the Mobile Payments SDK adheres to the Square update policy. To limit risk to developers and their users, Square enforces an [update policy](mobile-payments-sdk#update-policy) requiring developers keep their version of the Mobile Payments SDK current. * When using the Mobile Payments SDK version 2.1 or later, you must provide an [application signature](#application-signature) to Square before taking payments in production. * You're generally familiar with developing applications on iOS. If you're new to iOS development, you should visit the [Apple Training Center](https://training.apple.com/us/en) before continuing. ## Privacy permissions The Mobile Payments SDK requires that each user grant access to their device's current location while using your application. This ensures that payments are taken in a Square-supported country and that each payment is associated with a specific seller location. At this time, the Mobile Payments SDK can only be used to take production payments in the United States, Canada, the United Kingdom, or Australia. Bluetooth must be enabled to take payments with a contactless and chip reader and microphone access must be granted to take payments with a magstripe reader. Include the following privacy key/value pairs in your application's `Info.plist` file: {% table %} * Key {% width="300px" %} * Description --- * [NSBluetoothAlwaysUsageDescription](https://developer.apple.com/documentation/bundleresources/information_property_list/nsbluetoothalwaysusagedescription) * Square uses Bluetooth to connect and communicate with Square readers and compatible accessories. --- * [NSLocationWhenInUseUsageDescription](https://developer.apple.com/documentation/bundleresources/information_property_list/nslocationwheninuseusagedescription) * Square needs to know where transactions take place to reduce risk and minimize payment disputes. --- * [NSMicrophoneUsageDescription](https://developer.apple.com/documentation/bundleresources/information_property_list/nsmicrophoneusagedescription) * Some Square readers use the microphone to communicate payment card data to your device. {% /table %} {% aside type="info" %} Some additional settings are required to [use the Mobile Payments SDK with Square Stand](mobile-payments-sdk/ios/square-stand). {% /aside %} ## Device compatibility The Mobile Payments SDK isn't compatible with custom devices from OEMs that violate Square's security rules or on devices that grant root access to the mobile device operating system code and kernel. These devices cannot connect to a contactless and chip reader. The following additional considerations apply when trying to run the SDK on custom devices: * The Mobile Payments SDK isn't supported for rooted or jailbroken devices. * The Mobile Payments SDK cannot run on custom operating systems. * Square prohibits using any software that provides the ability to record or capture screen data or that results in elevated privileges. * [Verify that your device is supported](https://squareup.com/us/en/compatibility?platform=iOS&brand=Apple) for Square readers. * Supported OS versions should be the latest or as close to the latest as possible. * Review Square's support window for the OS version and confirm support for the compilation target version and the runtime version: The compilation target currently supports iOS versions 15 and later. ## Application signature Developers using the Mobile Payments SDK version 2.1 or later are required to submit an application signature to Square prior to taking payments in production. This signature is an extra layer of security that ensures that your application hasn't been tampered with or otherwise modified by a user. To submit an application signature, log in to the Developer Console and choose your application. In the left pane, choose **Mobile Payments SDK**, and then choose **Add Signature**. You must provide your [bundle ID](https://developer.apple.com/documentation/appstoreconnectapi/bundle-ids) and [team ID](https://developer.apple.com/help/account/manage-your-team/locate-your-team-id/) to Square.   If you publish a single mobile application used by multiple Square sellers using the same Square application id, you only need to submit one signature per platform for that application. Any debug builds you create that take payments in production also require their own application signature. The **Mobile Payments SDK** page in the Developer Console lists each signature that you've submitted. If an application signature is deleted, the application is no longer able to take payments, but you can submit a new signature for your application at any time. {% aside type="info" %} If you're developing with the Mobile Payments SDK's [Flutter](mobile-payments-sdk/flutter) or [React Native](mobile-payments-sdk/react-native) plugins and publishing both an Android and iOS application, you must submit a signature for each application. {% /aside %} ## Data privacy consent To comply with data privacy laws, the Mobile Payments SDK requires merchants in Canada, the United Kingdom, and the European Union to grant or deny consent for performance and analytics tracking. Your application must handle consent appropriately for merchants in these regions using the `TrackingConsentManager` protocol. Before processing payments, check the current state of consent for the authorized merchant. {% tabset %} {% tab id="Swift" %} ```swift let consentState = trackingConsentManager.getTrackingConsentState() ``` {% /tab %} {% tab id="ObjectiveC" %} ```objectivec ConsentState consentState = [self.trackingConsentManager getTrackingConsentState]; ``` {% /tab %} {% /tabset %} The possible states are: * `pending` - Consent has not yet been provided * `granted` - Merchant has granted consent for tracking * `denied` - Merchant has denied consent for tracking * `notRequired` - Merchant is in a region where consent is not required for tracking When your application prompts merchants for consent, log their response using `setTrackingConsentStateIsGranted(_ isGranted: Bool)`. {% tabset %} {% tab id="Swift" %} ```swift trackingConsentManager.setTrackingConsentStateIsGranted(true) // or false ``` {% /tab %} {% tab id="ObjectiveC" %} ```objectivec [self.trackingConsentManager setTrackingConsentStateIsGranted:YES]; // or NO ``` {% /tab %} {% /tabset %} If you don't implement consent handling via the API, the SDK automatically displays a consent dialog modal to users in regions where consent is required. This modal: * appears on the first payment attempt or when the user first opens the [settings screen](mobile-payments-sdk/ios/pair-manage-readers#settings-manager). * is shown only once per application per device. * is not displayed to users outside required regions. Listen for changes to `trackingConsentState` by conforming to the `TrackingConsentStateObserver` protocol. {% aside type="important" %} If you use a custom UI for your payment prompt screen and have not provided your user's consent status with the API, the SDK returns an error from `startPayment`. You must handle consent before initiating any payment when using a custom payment UI. {% /aside %} ## Quickstart Get started with the Mobile Payments SDK by configuring your iOS application and taking a test payment in the Square Sandbox. Use the following steps to set up and test the Mobile Payments SDK in your own application. You can also download the [Mobile SDK sample application](https://github.com/square/mobile-payments-sdk-ios) (written in Swift) to see an example of initialization, authorization, requesting permissions, and taking payments in production or the Square Sandbox.  {% anchor id="install-sdk" /%} ## 1. Install the SDK and dependencies ### Option A: Install with Swift Package Manager Install the Mobile Payments SDK with Swift Package Manager by following these steps in your Xcode project: 1. Choose **File**, and then choose **Add Package Dependencies**. 2. Enter the repository URL: **https://github.com/square/mobile-payments-sdk-ios**. 3. Choose the **Exact Version** dependency rule and specify the version as `2.4.0`. 4. Ensure the **SquareMobilePaymentsSDK** product is added to your target. 5. Optionally, you can add the **MockReaderUI** product to your target to simulate a physical reader when one isn't present in a Sandbox environment.  ### Option B: Install with Cocoapods Install the Mobile Payments SDK with [Cocoapods](https://guides.cocoapods.org/using/getting-started.html) by adding the following to your Podfile: ``` use_frameworks! pod "SquareMobilePaymentsSDK", "~> 2.4.0" # To use a simulated reader device to take sandbox payments, add the optional MockReaderUI: pod "MockReaderUI", "~> 2.4.0", configurations: ['Debug'] ``` ## 2. Add a run script in the build phases for your target 1. On the **Build Phases** tab for your application target, choose the **+** button (at the top left of the pane). 2. Choose **New Run Script Phase**. The new run script phase should be the last build phase. 3. Paste the following into the editor panel of the new run script: ```shell FRAMEWORKS="${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}" "${FRAMEWORKS}/SquareMobilePaymentsSDK.framework/setup" ``` {% aside type="important" %} Ensure that the new run script phase is the last build phase. Otherwise, the build fails. {% /aside %} For the run script to execute correctly, your application must disable user script sandboxing. 1. On the **Build Settings** tab for your application target, find the **Build Options** section. 2. Update the **User Script Sandboxing** setting to **No** for all build configurations.  [Context: Swift, iOS] ## 3. Initialize the Mobile Payments SDK Before taking a payment or attempting any other operation with the Mobile Payments SDK, you must initialize it using the `initialize()` method and your Square application ID in your `AppDelegate`'s `didFinishLaunchingWithOptions` method. To get your application ID, sign in to the [Developer Console](https://developer.squareup.com/apps) and choose your application. If you don't have an application yet, see [Get Started](square-get-started) for steps to create a Square application. Your application opens to the **Credentials** page. This page contains the Square application ID used to initialize the Mobile Payments SDK. {% aside type="info" %} To test the Mobile Payments SDK in the Square Sandbox with mock readers and mock payments, switch the toggle at the top of the Developer Console to **Sandbox** to show your Sandbox credentials and initialize the SDK with your Sandbox application ID. To start taking production payments, you must initialize the Mobile Payments SDK again with your production application ID. {% /aside %} ```swift import UIKit import SquareMobilePaymentsSDK @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { MobilePaymentsSDK.initialize( applicationLaunchOptions: launchOptions, squareApplicationID: "<#your_square_application_id#>" ) return true } } ``` ## 4. Authorize the Mobile Payments SDK The `AuthorizationManager` handles authorization for the Mobile Payments SDK. Call the `authorize()` method with an [OAuth access token](oauth-api/overview) and location ID. If you're testing your application in the [Square Sandbox](devtools/sandbox/overview), you can use your personal access token and location ID from the **Sandbox** tab in the Developer Console. {% aside type="important" %} Don't deploy an application into production that contains your personal access token. For a secure authorization implementation in production, you should implement OAuth instead of using a personal access token. {% /aside %} When authorization completes, the `AuthorizeCompletionHandler` provides the result of the authorization, including any [errors](mobile-payments-sdk/ios/handling-errors). ```swift import SquareMobilePaymentsSDK class <#YourViewController#>: UIViewController { func authorizeMobilePaymentsSDK(accessToken: String, locationID: String) { guard MobilePaymentsSDK.shared.authorizationManager.state == .notAuthorized else { return } MobilePaymentsSDK.shared.authorizationManager.authorize( accessToken: accessToken, locationID: locationID) { error in if let authError = error { // Handle auth error print("error: \(authError.localizedDescription)") return } print("Square Mobile Payments SDK successfully authorized.") } } } ``` {% anchor id="request-location-bluetooth" /%} ## 5. Request location and Bluetooth permissions The Mobile Payments SDK requires the user to grant access to the device's current location while using your application. This permission is required for payment, so you should only start a payment if you've determined that location access has been granted. ```swift import CoreLocation class <#YourViewController#>: UIViewController { private lazy var locationManager = CLLocationManager() // Call this in your app's lifecycle func requestLocationPermission() { switch CLLocationManager.authorizationStatus() { case .notDetermined: locationManager.requestWhenInUseAuthorization() case .restricted, .denied: print("Show UI directing the user to the iOS Settings app") case .authorizedAlways, .authorizedWhenInUse: print("Location services have already been authorized.") @unknown default: fatalError() } } } ``` To use contactless readers (Square Reader for contactless and chip or Square Stand) with the SDK, you must request Bluetooth permission from users of your application. Use a method similar to the following `requestBluetoothPermissions()` method: ```swift import CoreBluetooth final class <#YourViewController#>: ViewController { private var centralManager: CBCentralManager? // Call this in your app's lifecycle func requestBluetoothPermissions() { guard CBManager.authorization == .notDetermined else { return } centralManager = CBCentralManager( delegate: self, queue: .main, options: nil ) } } extension <#YourViewController#>: CBCentralManagerDelegate { func centralManagerDidUpdateState(_ central: CBCentralManager) { // Check state and prompt for permission if necessary } } ``` {% anchor id="mock-readers" /%} ## 6. Test with mock readers in the Square Sandbox Physical card readers aren't supported in the Square Sandbox. To take test payments, you must simulate a virtual reader with the Mock Reader UI. The Mock Reader UI consists of a floating button that can be dragged anywhere on the screen and persists as you navigate between screens in your application. Instantiate the Mock Reader UI and show the button with `try? self.mockReaderUI?.present()`. ```swift import MockReaderUI private lazy var mockReaderUI: MockReaderUI? = { do { return try MockReaderUI(for: MobilePaymentsSDK.shared) } catch { assertionFailure("Could not instantiate a mock reader UI: \(error.localizedDescription)") } return nil }() try? mockReaderUI?.present() ``` Click the following button to simulate connecting a magstripe reader or a contactless and chip reader to your device.  When mock readers have been connected and you [begin a payment](#begin-payment), click the button again to test your application's payment flow and simulate a swiped, inserted, or tapped card with the mock reader. Choose **Payment details** to select different card brands and payment behaviors (approved or declined) for testing purposes. To hide the mock reader UI in your application, call `self.mockReaderUI?.dismiss()`. {% aside type="info" %} When testing card payment flows, any inserted cards must be removed through the mock reader UI before beginning a new payment. {% /aside %} To pair physical readers for production payments, see [Pair and Manage Card Readers](mobile-payments-sdk/ios/pair-manage-readers). ## 7. Set up a payment The `PaymentManager` handles payments in the Mobile Payments SDK. Prior to starting a payment, you create `PaymentParameters` to represent details of an individual payment and `PromptParameters` to indicate how the payment prompts are presented to the buyer. When setting up a payment, you must conform to the `PaymentManagerDelegate` to [handle results and errors](mobile-payments-sdk/ios/handling-errors) from a payment. If the payment completes successfully, a `Payment` object is returned, which can be accessed on the [Sandbox Square Dashboard](testing/sandbox) or with the [Payments API](https://developer.squareup.com/reference/square/payments-api). If the payment fails, the response contains a `PaymentError`. ### Payment parameters Before beginning a payment, create a `PaymentParameters` object. At a minimum, you must include the payment amount, a [payment attempt ID](mobile-payments-sdk/ios/take-payments#idempotency-and-payment-attempts), and the processing mode, which [determines whether payments can be taken offline](mobile-payments-sdk/ios/offline-payments). For the complete list of payment parameter values and details, see [iOS technical reference](https://developer.squareup.com/docs/sdk/mobile-payments/ios). ### Prompt parameters Each payment must also have `PromptParameters` configured, consisting of a `mode` and `additionalMethods` available to buyers. Square provides a payment prompt screen that displays a UI to buyers prompting them to swipe, insert, or tap their card, depending on the payment options available. To use this screen for your quickstart application, set the `PromptParameters.mode` to `default`.  If you create your own custom payment prompt UI by passing `custom` in the `PromptParameters.mode`, use the `availableCardInputMethodsObserver` to update your UI if the available card entry methods change (for example, if a magstripe reader is inserted or removed from the mobile device). {% anchor id="begin-payment" /%} ## 8. Take a payment When you're ready to begin a payment, call `PaymentManager.startPayment()` with the `PaymentParameters` and `PromptParameters` you created for this payment. ```swift import SquareMobilePaymentsSDK class <#YourViewController#>: UIViewController { private var paymentHandle: PaymentHandle? func startPayment() { paymentHandle = MobilePaymentsSDK.shared.paymentManager.startPayment( makePaymentParameters(), promptParameters: PromptParameters( mode: .default, additionalMethods: .all ), from: self, delegate: self ) } func makePaymentParameters() -> PaymentParameters { PaymentParameters( paymentAttemptID: UUID().uuidString, amountMoney: Money(amount: 100, currency: .USD), processingMode: processingMode.onlineOnly ) } } extension <#YourViewController#>: PaymentManagerDelegate { public func paymentManager( _ paymentManager: PaymentManager, didFinish payment: Payment ) { print("Payment Did Finish: \(payment)") } public func paymentManager( _ paymentManager: PaymentManager, didFail payment: Payment, withError error: Error ) { // Handle Payment Failed print("error: \(error.localizedDescription)") } public func paymentManager( _ paymentManager: PaymentManager, didCancel payment: Payment ) { // Handle Payment Canceled } } ``` During each payment, Square takes control of the screen display to ensure that buyer information is handled securely and that the final confirmation of the payment is correctly shown to the buyer. While a payment is in progress, the `UIViewController` passed to `startPayment` must remain presented or pushed until your `PaymentManagerDelegate` receives a callback indicating the payment has completed, failed, or was canceled. After the payment starts, it cannot be canceled by the developer.  If the payment completes successfully, a `Payment` object is returned to your payment callback. This payment can be accessed on the Square Dashboard or with the [Payments API](https://developer.squareup.com/reference/square/payments-api). If the payment fails, the response contains an [error](mobile-payments-sdk/ios/handling-errors). ## Next steps After installing the Mobile Payments SDK and testing your application with test payments in the Square Sandbox, learn more about configuring your application to take production payments: * [Authorize the Mobile Payments SDK](mobile-payments-sdk/ios/configure-authorize#authorize) * [Pair and Manage Card Readers](mobile-payments-sdk/ios/pair-manage-readers) * [Take a Payment](mobile-payments-sdk/ios/take-payments) [Context: Objective C, iOS] ## 3. Initialize the Mobile Payments SDK Before taking a payment or attempting any other operation with the Mobile Payments SDK, you must initialize it using the `initialize()` method and your Square application ID in your `AppDelegate`'s `didFinishLaunchingWithOptions` method. To get your application ID, sign in to the [Developer Console](https://developer.squareup.com/apps) and choose your application. If you don't have an application yet, see [Get Started](square-get-started) for steps to create a Square application. Your application opens to the **Credentials** page. This page contains the Square application ID used to initialize the Mobile Payments SDK. {% aside type="info" %} To test the Mobile Payments SDK in the Square Sandbox with mock readers and mock payments, switch the toggle at the top of the Developer Console to **Sandbox** to show your Sandbox credentials and initialize the SDK with your Sandbox application ID. To start taking production payments, you must initialize the Mobile Payments SDK again with your production application ID. {% /aside %} ```objectivec #import "AppDelegate.h" @import SquareMobilePaymentsSDK; @interface AppDelegate () // … @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [SQMPMobilePaymentsSDK initializeWithApplicationLaunchOptions:launchOptions squareApplicationID:@"<#your_square_application_id#>"]; return YES; } } ``` ## 4. Authorize the Mobile Payments SDK The `SQMPAuthorizationManager` handles authorization for the Mobile Payments SDK. Call the `authorize()` method with an [OAuth access token](oauth-api/overview) and location ID. If you're testing your application in the [Square Sandbox](devtools/sandbox/overview), you can use your personal access token and location ID from the **Sandbox** tab in the Developer Console. {% aside type="important" %} Don't deploy an application into production that contains your personal access token. For a secure authorization implementation in production, you should implement OAuth instead of using a personal access token. {% /aside %} When authorization completes, the `AuthorizeCompletionHandler` provides the result of the authorization, including any [errors](mobile-payments-sdk/ios/handling-errors). ```objectivec #import