Store Platforms

Squarespace API Integration: API Keys vs OAuth, Webhooks and Rate Limits

Squarespace API integration explained: API key vs OAuth, token refresh, the 300 requests/min limit, order webhooks and Products API v2, with working code.

Photo de profil de Long Nguyen

Long Nguyen

Développeur fullstack · Ingénieur IA · Chercheur

• • 4 min de lecture •

What a Squarespace API integration can actually do

A Squarespace API integration connects your own software to a merchant site through the Squarespace Commerce APIs: REST endpoints served over HTTPS from api.squarespace.com, JSON only, authenticated with either an API key or an OAuth 2.0 access token. They cover the commerce side of a site (products, orders, inventory, contacts, discounts, transactions) and can push order and contact events to your server by webhook.

The useful mental model is one API per business object, each with its own permission level:

API What it does Watch out for
Orders Read one-time and subscription orders, import orders from other sales channels, mark orders fulfilled, trigger shipment notifications Donations are not included; use Transactions
Products Create, update and delete products, variants and images v2 is current; v1.0 and v1.1 are legacy
Inventory Read and adjust stock for product variants Separate permission from Products
Contacts Customers, subscribers and donors, address books, marketing preferences Replaces the Profiles API for new work
Discounts Manage discount codes, eligibility and amounts Read and write are separate scopes
Transactions Financial transactions for orders and donations Read only
Webhook Subscriptions Subscribe your endpoint to site events OAuth only; API keys are rejected

Two details save rework. The Profiles API is in maintenance mode, so anything new should use Contacts. And because the Orders API leaves out donations, a reporting or accounting integration that must capture every payment needs the Transactions API as well.

Squarespace API key or OAuth: decide before you write code

This is the one decision that shapes everything downstream, because it determines whether you can use webhooks, what rate limits apply, and how you store credentials.

  API key OAuth 2.0
Intended for A custom application on one merchant site Commercial apps and Extensions used by many sites
Requirement The site owner needs the Commerce Advanced plan Your app registered with Squarespace as an OAuth client
How you get access Generate a key in the site's settings The site owner approves your app on a Squarespace confirmation page
Lifetime Does not expire while the site stays active Access token 30 minutes, refresh token 7 days
Permissions Read Only or Read and Write per API, chosen at creation Scopes such as website.orders, requested per authorization
Webhooks Not supported Supported
Create Order limit 100 requests per hour per site Limit does not apply

My rule of thumb: if the integration serves more than one merchant, you are on OAuth whether you like it or not. If it serves exactly one store you control or manage, an API key is the shortest path, but you give up webhooks and must poll for changes. That trade is usually fine for order and inventory sync at small-store volumes, and it is not fine if the business expects near-instant reactions to new orders.

The official authentication and permissions guide lists the permission levels for each API and confirms that a request only ever sees data for the site that owns the key or token.

How to get a Squarespace API key and make your first request

  1. Log in to the Squarespace site (it must be on the Commerce Advanced plan).
  2. Go to Settings, then Advanced, then Developer API Keys, and click Generate Key.
  3. Name the key and pick the APIs and permission level it needs. Grant the minimum: a shipping sync usually needs Orders (Read and Write) and nothing else.
  4. Copy the key immediately. It is displayed once.

Every request needs an Authorization: Bearer header and a User-Agent header that describes your app. Requests without a User-Agent are rejected, and default values such as curl/7.54.0 can attract stricter rate limiting, so set your own. Plain HTTP is rejected.

curl 'https://api.squarespace.com/1.0/commerce/orders?paymentStates=PAID,PARTIALLY_PAID' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: acme-orders-sync/1.0'

Put the key in a secrets manager, not in source control. Because it never expires, a leaked key stays valid until someone deletes it in the dashboard.

How the Squarespace OAuth flow and token refresh work

Squarespace uses the standard authorization-code flow. You register the client, send the site owner to the authorize URL, receive a one-time code on your redirect URI, and exchange it for tokens. Registration asks for a client name, an icon, redirect URIs, and links to your terms and privacy policy; Squarespace then issues a client ID and secret.

Squarespace's help center says the older request form for new OAuth applications is being retired on in favor of a self-service process. The developer docs still link to the developer-apps page, so check the current registration path before you plan a timeline.

https://login.squarespace.com/api/1/login/oauth/provider/authorize
  ?client_id=YOUR_CLIENT_ID
  &redirect_uri=https://app.example.com/oauth/callback
  &scope=website.orders.read,website.inventory
  &state=RANDOM_CSRF_VALUE
  &access_type=offline
Scope Grants
website.orders / website.orders.read Send order data and mark orders fulfilled / view orders and fulfillment
website.products / website.products.read View and modify products / view products
website.inventory / website.inventory.read View and update stock / view stock
website.contacts / website.contacts.read Manage contacts and address books / view them
website.discounts / website.discounts.read View and manage discounts / view discounts
website.transactions.read Transactional order and donation data

Four details from the OAuth guide cause most failed first attempts:

  • The authorization code is valid for two minutes and works once. It arrives URL-encoded, so decode it before sending it to the token endpoint.
  • The token endpoint wants a Basic header built from client_id:client_secret, plus a User-Agent. A missing User-Agent shows up as an error referencing SEC-43.
  • Access tokens last 30 minutes. Without access_type=offline you never receive a refresh token and the user must reauthorize.
  • Refresh tokens last 7 days and are single-use: each refresh returns a new refresh token, and the old one is invalidated once the new access token is used.

That last rule is where production integrations break. If two workers refresh at the same time, one of them presents an already-spent refresh token and the chain dies, which forces the merchant to reauthorize. Serialize refreshes behind a lock, and save the new token pair to durable storage before you use it. Because each refresh issues a fresh seven-day token, a worker that runs continuously stays connected, but one that is down for more than a week loses access.

import base64
import threading
import time
import requests

TOKEN_URL = 'https://login.squarespace.com/api/1/login/oauth/provider/tokens'
USER_AGENT = 'acme-orders-sync/1.0'
_lock = threading.Lock()  # use a database row lock if you run several processes


def _basic(client_id, client_secret):
    raw = f'{client_id}:{client_secret}'.encode()
    return 'Basic ' + base64.b64encode(raw).decode()


def get_access_token(store, client_id, client_secret):
    # store: dict with access_token, access_token_expires_at, refresh_token
    with _lock:
        if store['access_token_expires_at'] - time.time() > 10:
            return store['access_token']
        resp = requests.post(
            TOKEN_URL,
            headers={
                'Authorization': _basic(client_id, client_secret),
                'Content-Type': 'application/json',
                'User-Agent': USER_AGENT,
            },
            json={'grant_type': 'refresh_token', 'refresh_token': store['refresh_token']},
            timeout=30,
        )
        resp.raise_for_status()
        data = resp.json()
        store['access_token'] = data['access_token']
        store['access_token_expires_at'] = float(data['access_token_expires_at'])
        store['refresh_token'] = data['refresh_token']  # persist before using the token
        return store['access_token']

When any call returns 401, the token is expired or the merchant revoked access. Stop retrying and surface a reconnect prompt to the user.

Build the Squarespace integration yourself or hand it off

The endpoints themselves are not hard. The cost sits in the surrounding work: OAuth client registration and review, token storage that survives concurrency, reconciliation jobs for missed events, and testing against real store data such as payment plans, subscriptions and variant-heavy catalogs. If the integration is a side feature of your product and you have an engineer with a few free weeks, build it. If it connects Squarespace to a marketplace, ERP or fulfillment system that the business depends on, it is usually cheaper to have a team that does store-platform API integrations scope it first.

Squarespace API rate limits and how to stay under them

Limit Value Applies to
General rate limit 300 requests per minute (about 5 per second) Commerce APIs
Response when exceeded 429 Too Many Requests, with a one-minute cool-down Commerce APIs
Create Order 100 requests per hour per website API key authentication only; not applied to OAuth

The limits are generous for sync work and tight for bulk work. Polling orders once a minute uses well under 1% of the budget; a catalog migration that creates products and uploads images one call at a time can hit it in seconds. The Create Order cap matters more than it looks: importing historical orders from another channel with an API key tops out at 100 per hour per site, which is a multi-day job for a large backlog. If you need to import in bulk, that is an argument for OAuth.

Because a 429 carries a full minute of cool-down, back off for a minute rather than retrying quickly, which only extends the wait.

import time
import requests

USER_AGENT = 'acme-orders-sync/1.0'


def sqsp_get(path, token, params=None):
    url = f'https://api.squarespace.com{path}'
    headers = {'Authorization': f'Bearer {token}', 'User-Agent': USER_AGENT}
    for _ in range(3):
        r = requests.get(url, headers=headers, params=params, timeout=30)
        if r.status_code == 429:
            time.sleep(60)  # documented cool-down is one minute
            continue
        r.raise_for_status()
        return r.json()
    raise RuntimeError('still rate limited after 3 attempts')

Squarespace also documents an Idempotency-Key header. Read that guide before you add retries to any call that creates something, so a timeout followed by a retry does not create a duplicate.

Squarespace webhooks: what fires and what does not

Webhooks are managed through the Webhook Subscriptions API, which requires an OAuth access token. An API key cannot create, list or delete subscriptions. You register an HTTPS endpoint and a list of topics; the documented topics center on order.create, order.update, extension.uninstall, and the contact and address create, update and delete events. The token must hold a matching scope: website.orders or website.orders.read for order topics, website.contacts for contact and address topics, and none for extension.uninstall.

import requests

resp = requests.post(
    'https://api.squarespace.com/1.0/webhook_subscriptions',
    headers={
        'Authorization': f'Bearer {access_token}',
        'User-Agent': 'acme-orders-sync/1.0',
        'Content-Type': 'application/json',
    },
    json={
        'endpointUrl': 'https://sync.example.com/hooks/squarespace',
        'topics': ['order.create', 'order.update', 'extension.uninstall'],
    },
    timeout=30,
)
resp.raise_for_status()
secret = resp.json()['secret']  # returned only on create and on secret rotation

The response includes a secret, a hexadecimal value used to verify that a notification really came from Squarespace. It is shown only when you create the subscription or rotate the secret, so store it immediately. Follow the verifying-notifications guide in the webhook docs for the exact signature check, and reject any request that fails it. After you rotate a secret, the previous one stops working.

Use the send-test-notification action to exercise your endpoint. It is a one-time call and is not retried if it fails or times out. For real deliveries, confirm the delivery and retry behavior in the webhook docs, and make your handler idempotent either way: acknowledge fast, queue the order ID, and do the work asynchronously.

Subscribe to extension.uninstall even if you only care about orders. It is how you learn that a merchant disconnected, so you can stop syncing and delete their tokens.

How to sync Squarespace orders without missing any

Treat webhooks as a doorbell and the list endpoint as the source of truth. The reason is in the Orders API's own rules:

  • By default, listing orders returns only the payment states NOT_CHARGED, AUTHORIZED, PAID and REFUNDED. Orders that are PENDING, FAILED, PARTIALLY_PAID, REFUND_PENDING or REFUND_FAILED are excluded unless you pass them in the paymentStates parameter.
  • Payment plans (a deposit at checkout, then scheduled installments) are a single order whose paymentState moves as installments arrive. For those orders, order.create fires only when the state reaches PAID, meaning after every installment is collected. No webhook fires for the deposit or for intermediate installments.
  • order.update covers fulfillment, refunds, cancellation, marking pending, and email changes.

So a webhook-only integration silently misses newly placed payment-plan orders for weeks. The pattern that holds up: handle order.create and order.update events by fetching the order by ID and upserting it, and separately run a scheduled job that lists orders with paymentStates set to include PARTIALLY_PAID and reconciles anything your database lacks. Key your records on the Squarespace order ID so replays are harmless.

Going the other way, the Orders API can mark an order fulfilled and trigger the shipment notification, which is the natural hook for a shipping or 3PL integration. It can also import orders from third-party channels, subject to the Create Order limit above.

Syncing products and inventory with the Products API v2

Use v2 for new work. It supports four product types: physical, service, gift card and download. The legacy v1.0 and v1.1 versions remain supported but are narrower. Physical products work in all versions, service and gift card products only in v2, and download products have the most restrictions: in v2 they can be read and updated but not created or deleted, and they cannot have variants.

Things that shape a catalog sync:

  • Every product belongs to exactly one store page, and it is purchasable only if both the store page is enabled and the product is set to visible. A product that imports successfully but is not selling is usually one of those two settings.
  • A product can carry up to 100 images. Image upload is asynchronous, and there is a separate endpoint to check processing status, so poll it before you assume an image is live or assign it to a variant.
  • The categories a store page uses to group products are not available through the API. Plan a manual step, or a different approach, for category structure.
  • Stock lives on variants and is handled by the Inventory API with its own permission or scope, so a sync that changes prices and stock needs both.

Decide which system owns each field before you write the first sync job. A common split is that the merchandising system owns titles, descriptions and images, while the inventory system owns stock counts. Squarespace's documented webhook topics are order- and contact-centered, so check the current list before designing push-based stock sync; polling the Inventory API on a schedule is the safe default.

What the Squarespace API cannot do

  • No webhooks on an API key. Webhook subscriptions require OAuth.
  • No donation data in Orders. Use the Transactions API.
  • No store-page categories. They are not exposed by the Products API.
  • No new work on Profiles. It is in maintenance mode; use Contacts.
  • No open access to form submissions. The Forms API key is documented for the Zapier integration only.
  • No cross-site access. A key or token can only see the one site that owns it, so a multi-store integration needs one credential set per store.

Checklist before you ship a Squarespace integration

  1. Chose API key or OAuth on purpose, knowing the webhook and Create Order consequences.
  2. Requested the smallest permission set that does the job.
  3. Set a descriptive User-Agent on every request, including token calls.
  4. Serialized refresh-token use and persisted the new pair before using it.
  5. Backed off for a full minute on 429.
  6. Verified webhook signatures, stored the secret, and made handlers idempotent.
  7. Added a reconciliation job that includes PARTIALLY_PAID orders.
  8. Handled 401 and extension.uninstall by disconnecting the merchant cleanly.

If you would rather have a scoped plan than a checklist, tell us which systems Squarespace needs to talk to and we will come back with an approach and a quote through the free quote form.

FAQ

Questions fréquentes

Does Squarespace have a public API?

Yes. Squarespace offers Commerce APIs for orders, products, inventory, contacts, discounts, transactions and webhook subscriptions. They are REST APIs at api.squarespace.com that return JSON and authenticate with an API key or an OAuth 2.0 access token.

Do I need a paid Squarespace plan to use the API?

For a custom application that uses an API key, the site needs the Commerce Advanced plan. OAuth is different: apps built as Squarespace Extensions can be used by customers on any plan, but the developer must register the app as an OAuth client with Squarespace.

What is the Squarespace API rate limit?

The Commerce APIs allow 300 requests per minute, about five per second. Going over returns a 429 response with a one-minute cool-down. The Create Order endpoint has a separate limit of 100 requests per hour per website when you authenticate with an API key; that limit does not apply to OAuth.

Can I use Squarespace webhooks with an API key?

No. The Webhook Subscriptions API requires an OAuth access token, and API keys are not supported. If you use an API key you need to poll the Orders API on a schedule instead.

How long do Squarespace OAuth tokens last?

Access tokens last 30 minutes. If you request access_type=offline you also receive a refresh token that lasts 7 days. Refresh tokens are single-use: each refresh returns a new refresh token and invalidates the old one, so store the newest pair and avoid refreshing from several workers at once.

Why did my Squarespace order webhook not fire for a new order?

For a payment-plan order, the order.create webhook fires only when the order becomes fully PAID, not when the deposit is collected. To see those orders as they are placed, list orders with the paymentStates parameter set to include PARTIALLY_PAID.

Should I use the Profiles API or the Contacts API?

Use the Contacts API for new integrations. Squarespace has put the Profiles API in maintenance mode and points new work to Contacts, which manages customers, subscribers and donors along with address books and marketing preferences.

Restez informé avec Netalith

Recevez des ressources de développement, des mises à jour produit et des offres spéciales directement dans votre boîte mail.