# StoreConnect API

Source: https://support.storeconnect.com/articles/storeconnect-api · Last modified 14 August 2026

The StoreConnect API lets external applications run a complete commerce flow against a store without using the StoreConnect storefront: create customers, build carts, take payment, save cards, start and manage subscriptions, and read orders and products. Everything a customer does on your website, your own application can do through this API.

Typical uses include SaaS billing (your application signs customers up to subscription plans and StoreConnect handles payment, invoicing, and renewals), headless storefronts, and back-office integrations.

## Authentication and base URL

Every request sends a bearer token made up of your store's Salesforce ID and your API key, separated by a colon:

```

Authorization: Bearer STORE_ID:API_KEY
```

The API is available at two equivalent base URLs:

| Form | Example |
|------|---------|
| Subdomain | `https://api.yourstore.com/v1` |
| Path | `https://yourstore.com/api/v1` |

Before the examples below will run, the store needs an `api` subdomain, a store record pointed at it, and an API key. See [API configuration](api-configuration) for that setup.

The API key is read from the `api.auth_key` store variable on your **Store** record in Salesforce, falling back to a key issued by Support for the store. Path-based access can be turned off with the `api.disable_path_access` store variable. See [Store variables](store-variables) for how to set store variables.

Requests without a valid token receive `401 Unauthorized`.

:::note
Your store's live OpenAPI specification is served at `https://api.yourstore.com/v1/api.yml`, and a browsable Swagger UI at `https://api.yourstore.com/`. The specification is always current for the version your store runs.
:::

## Concepts

The API works with the same records your store and Salesforce org use:

| Record | What it is |
|--------|------------|
| Contact | A person: name, email, and phone. Belongs to an account. |
| Account | The customer record that holds billing and shipping addresses and syncs to Salesforce. |
| Cart | A basket of items being prepared for checkout. |
| Order | The result of a successful checkout. Syncs to Salesforce. |
| Subscription | Created automatically when an order contains a subscription product. Renewals generate new orders on schedule. |
| Payment method | A card saved against a contact at the payment gateway for recurring or later charges. |

Record IDs returned by the API (the `id` field) are StoreConnect IDs, with one exception: payment providers in the `GET /store` response return their Salesforce ID. Endpoints that take an ID in the path accept either the StoreConnect ID or the Salesforce ID of the same record. IDs sent in a request body are not always interchangeable, so the sections below say which form each one takes.

List endpoints (`/products`, `/orders`, `/subscriptions`, `/payment_methods`) accept `page` and `per_page` query parameters and return `X-Pagination-Current-Page`, `X-Pagination-Per-Page`, and `X-Pagination-Total-Records` response headers.

## Store and products

`GET /store` returns the store's configuration, including the payment providers your application charges against:

```json

{
  "name": "Your Store",
  "currency": { "code": "USD", "symbol": "$" },
  "locale": "en-US",
  "logo": { "id": "…", "sizes": { "original": "…" } },
  "timezone": "America/Los_Angeles",
  "payment_providers": [
    { "id": "PROVIDER_ID", "name": "Stripe", "method": "stripe", "publishable_key": "pk_test_…" }
  ]
}
```

Only active providers are listed. A provider's `id` is its Salesforce ID, and this is the value you pass as `provider_id` at checkout and when saving a card. `publishable_key` is the provider's client-side key for tokenizing cards in the browser; it is `null` for gateways that have no client-side key. Secret keys are never exposed.

`GET /products` returns the paginated catalog and `GET /products/{id}` returns one product with extended detail (content sections, variants, and related products). The fields you need most:

| Field | Description |
|-------|-------------|
| `id`, `code`, `name`, `summary` | Identity and display fields. |
| `pricing.current`, `pricing.original`, `pricing.tax_inclusive` | Current pricing. |
| `pricing.subscription` | Present for subscription products: term, term count and unit, and total price. Empty for one-off products. |
| `stock` | Available-to-sell totals and per-location stock levels. |
| `images`, `categories`, `tags`, `taxes` | Catalog metadata. |

When you sell subscription plans, each plan is a product whose `pricing.subscription` describes the billing term.

## Customers: contacts and accounts

### Create a contact

`POST /contacts` finds or creates a contact by email, and finds or creates its account:

```json

{
  "first_name": "Alex",
  "last_name": "Taylor",
  "email": "alex@example.com",
  "phone": "+1 555 0100",
  "company_name": "Example Co",
  "external_reference": "your-system-id-123",
  "billing_address": {
    "street": "123 Example Street",
    "city": "San Francisco",
    "state": "CA",
    "country": "US",
    "postal_code": "94105"
  }
}
```

- `first_name`, `last_name`, and `email` identify the contact. If a contact with the email already exists it is reused.
- The account name comes from `company_name` when given, otherwise from the contact's name.
- `external_reference` stores your own system's identifier on the contact and is returned in every contact response.
- `country` is the two-letter ISO code. `state` is required for countries with state or province lists (for example US, AU, CA).

The response returns the contact and its `account_id`:

```json

{
  "id": "…",
  "first_name": "Alex",
  "last_name": "Taylor",
  "email": "alex@example.com",
  "phone": "+1 555 0100",
  "account_id": "…",
  "external_reference": "your-system-id-123",
  "custom_data": {}
}
```

`GET /contacts/{id}` accepts a StoreConnect ID, a Salesforce ID, or an email address. `PATCH /contacts/{id}` updates `first_name`, `last_name`, `email`, and `phone`.

### Read and update an account

`GET /accounts/{id}` returns the account with both addresses:

```json

{
  "id": "…",
  "name": "Example Co",
  "display_name": "Example Co",
  "tax_entity_code": null,
  "billing_address": { "street": "…", "city": "…", "state": "…", "country": "…", "postal_code": "…" },
  "shipping_address": { "street": "…", "city": "…", "state": "…", "country": "…", "postal_code": "…" },
  "custom_data": {}
}
```

`PATCH /accounts/{id}` updates `name`, `tax_entity_code`, `billing_address`, and `shipping_address`. Address updates are partial, so only the fields you send change:

```json

{
  "shipping_address": {
    "street": "500 Delivery Lane",
    "city": "Oakland",
    "state": "CA",
    "country": "US",
    "postal_code": "94607"
  }
}
```

## How billing and shipping addresses work

Addresses live in two places, and they serve different purposes:

| Where | Purpose |
|-------|---------|
| Account | The customer record in Salesforce, used for tax, invoicing, and CRM. |
| Cart | Captured at checkout and copied onto the order. Subscription renewal orders reuse the original order's addresses. |

Points to be aware of:

- When `POST /contacts` creates an account, the billing address you supply is also copied into the account's shipping address. To store a different shipping address, follow up with `PATCH /accounts/{id}`.
- The cart's shipping address, not the account's, is what appears on the order and on every renewal order for a subscription started from that cart. Set it on the cart at checkout time if it matters for fulfillment.
- A cart's `billing_address` is required and must be complete (all five fields). Its `shipping_address` is optional for digital goods, and required when the store has shipping enabled and the cart contains a physical product.

## Carts and checkout

### Create a cart

`POST /carts`:

```json

{
  "contact_id": "CONTACT_ID",
  "items": [
    { "product_id": "PRODUCT_ID", "quantity": 1 }
  ],
  "billing_address": { "street": "…", "city": "…", "state": "…", "country": "…", "postal_code": "…" },
  "shipping_address": { "street": "…", "city": "…", "state": "…", "country": "…", "postal_code": "…" }
}
```

The response includes priced items and the checkout mode:

```json

{
  "id": "…",
  "status": "shopping",
  "contact_id": "…",
  "account_id": "…",
  "items": [
    { "product_id": "…", "product_name": "Pro Plan", "quantity": 1, "unit_price": 29.0, "has_trial": false, "trial_days": 0 }
  ],
  "total_amount": 29.0,
  "total_payable": 29.0,
  "checkout_mode": "charge_and_save",
  "billing_address": { "…": "…" },
  "shipping_address": { "…": "…" },
  "order_id": null,
  "custom_data": {}
}
```

`checkout_mode` tells your application what the checkout call will do with the card:

| Mode | Meaning |
|------|---------|
| `charge` | One-off items only. The card is charged and not saved. |
| `charge_and_save` | The cart contains a subscription. The card is charged now and saved for renewals. |
| `save_card_only` | The cart contains a subscription with a trial (billing delay). The card is saved and nothing is charged now. |

`PATCH /carts/{id}` updates `items` (replaces the item list), `billing_address`, `shipping_address`, `contact_id`, and `abandoned`. `GET /carts/{id}` returns the cart, and `DELETE /carts/{id}` removes it.

### Check out

`POST /carts/{id}/checkout` completes the purchase:

```json

{
  "payment": {
    "token": "PAYMENT_TOKEN",
    "method": "stripe",
    "provider_id": "PROVIDER_ID"
  }
}
```

- `token` is a one-time card token from your payment gateway's client-side tokenization (for example Stripe Elements). The API never accepts raw card numbers.
- `method` and `provider_id` identify the gateway; both come from the `payment_providers` list in the `GET /store` response.

Depending on `checkout_mode`, checkout saves the card, charges it, or both, then creates the order. The response includes the order and, when the cart contained a subscription product, the subscription that was started.

Checkout is idempotent per cart: if the same cart is checked out twice (for example a retry after a timeout), the second call returns the already-created order rather than charging again.

The checkout response includes everything that was created:

```json

{
  "order": { "id": "…", "reference": "…", "status": "…", "total_amount": 29.0, "total_paid": 29.0, "items": [ { "…": "…" } ] },
  "payment_method": { "id": "…", "card_brand": "visa", "last_four": "4242", "is_default": true },
  "payment": { "status": "…", "amount": 29.0 },
  "subscription": { "id": "…", "status": "active", "next_billing_date": "…" }
}
```

- `payment` is present when a charge was made (`charge` and `charge_and_save` modes).
- `payment_method` is present when the card was saved (`charge_and_save` and `save_card_only` modes).
- `subscription` is present when the cart contained a subscription product.

## Payment methods and saved cards

Checking out a cart that contains a subscription saves the card automatically. These endpoints let you manage saved cards outside a purchase.

`GET /payment_methods?contact_id={id}` lists a contact's active saved cards. `contact_id` is required, not a filter: a request without it returns `404`.

```json

[
  {
    "id": "…",
    "type": "card",
    "card_brand": "visa",
    "last_four": "4242",
    "expires_at": "…",
    "exp_month": 12,
    "exp_year": 2030,
    "is_default": true,
    "status": "active",
    "display_name": "visa ending in 4242",
    "created_at": "…",
    "custom_data": {}
  }
]
```

`POST /payment_methods` vaults a new card from a gateway token and makes it the contact's default. Use it when a customer replaces their card:

```json

{
  "contact_id": "CONTACT_ID",
  "payment": { "token": "PAYMENT_TOKEN", "provider_id": "PROVIDER_ID" }
}
```

`DELETE /payment_methods/{id}` deactivates a saved card and returns `204 No Content`. The record is not deleted, so it stops appearing in the list above but any history that references it is preserved.

### Collecting card details without handling them

`POST /payment_method_sessions` with a `contact_id` returns a short-lived URL you can embed in an iframe:

```json

{ "token": "…", "expires_in": 1800, "path": "/embedded_payment/save_card?token=…" }
```

Open `https://yourstore.com{path}` in an iframe and the customer enters their card on a StoreConnect-hosted form; the card is vaulted against the contact without any purchase. The token is valid for 30 minutes and works for that contact only. This is the recommended way to collect card details in production, because raw card data never touches your application.

## Subscriptions

A subscription is created automatically when a checked-out cart contains a subscription product. Renewal orders and renewal charges then happen on schedule inside StoreConnect. See [Subscription renewal orders](subscription-renewal-orders).

`GET /subscriptions?contact_id={id}` lists a contact's subscriptions, and `GET /subscriptions/{id}` returns one. Key fields:

| Field | Description |
|-------|-------------|
| `status` | `active`, `cancelled`, `expired`, `suspended`, or `delinquent`. |
| `type` | `evergreen` or `fixed`. |
| `product_id`, `product_name`, `period_price`, `period_length`, `period_type` | The plan and its billing term. |
| `start_date`, `end_date`, `next_billing_date`, `next_renewal_date`, `trial_ends_at` | Lifecycle dates. |
| `pending_plan_change` | A scheduled downgrade that has not taken effect yet, or `null`. |
| `last_plan_change` | The most recent completed plan change, with direction and proration amount. |
| `payment_source` | Masked identifier and expiry of the card that renewals charge. |

`GET /subscriptions/{id}/payment_method` returns the card behind the subscription: `{ "subscription_id": "…", "payment_provider": "…", "identifier": "…", "expires_at": "…" }`.

### Change plan

`PATCH /subscriptions/{id}` with a `product_id` moves the subscription to another plan:

```json

{ "product_id": "NEW_PLAN_PRODUCT_ID" }
```

StoreConnect decides the direction and timing from the price difference, so you do not pass them:

- A more expensive plan is an upgrade and takes effect immediately, with proration according to the store's plan change settings.
- A cheaper plan is a downgrade and takes effect at the next billing date. Until then it appears in `pending_plan_change`.

Plan changes apply to evergreen subscriptions only. When the change is not possible the endpoint returns `422` with type `invalid` and a reason such as `not_evergreen`, `subscription_not_active`, `same_price`, or `payment_method_required`. A `PATCH` with only `custom_data` merges your data without changing the plan.

### Cancel

`DELETE /subscriptions/{id}` cancels an evergreen subscription and returns it with `status: "cancelled"`. Subscriptions that are fixed term or already ended return `422` with type `invalid` and reason `not_cancellable`.

## Orders, payments, and refunds

`GET /orders?contact_id={id}` lists orders and `GET /orders/{id}` returns one:

```json

{
  "id": "…",
  "reference": "…",
  "status": "…",
  "total_amount": 29.0,
  "total_tax": 2.9,
  "total_paid": 29.0,
  "total_payable": 0.0,
  "customer": { "id": "…", "first_name": "…", "last_name": "…", "email": "…", "phone": "…" },
  "customer_notes": null,
  "items": [ { "id": "…", "name": "…", "quantity": 1, "price": 29.0, "total": 29.0, "tax": 2.9 } ],
  "shipping_address": { "…": "…" },
  "custom_data": {}
}
```

Orders collected at a store location return a `collection_point_id` instead of `shipping_address`.

`POST /orders` creates an order directly in a single call: customer, addresses, and items together, with optional `collection_point_id` or `shipping_rate_id` (one or the other), `customer_notes`, and `finalise_order`. Use it when payment happens outside StoreConnect; for card payments through the API, prefer the carts and checkout flow above.

`POST /payments` records a payment taken outside StoreConnect against an order. It does not charge a card:

```json

{ "order_id": "ORDER_ID", "amount": 29.0, "reference": "your-receipt-ref", "source": "External POS" }
```

All four fields are required, and `amount` must be greater than zero. Unlike IDs in a path, `order_id` here must be the StoreConnect ID; a Salesforce ID returns `404`. `GET /payments/{id}` returns the payment with its status, amounts, and per-item allocations.

### Refunds

Three endpoints drive refunds:

1. `GET /orders/{order_id}/refunds/options` lists the payments that can be refunded, with the remaining refundable credit per payment.
2. `GET /orders/{order_id}/refunds/items` lists the order items that can be refunded, with quantities and refundable amounts.
3. `POST /orders/{order_id}/refunds` requests the refund:

```json

{
  "salesforce_user_id": "005…",
  "reason": "Customer request",
  "requires_approval": false,
  "refund_items": [
    { "payment_id": "…", "order_item_id": "…", "quantity": 1, "amount": -29.0 }
  ]
}
```

Refund amounts are negative. With `requires_approval: false` the refund is processed immediately; otherwise it waits for approval in Salesforce. The response returns the created refund payment IDs.

## Shipping rate estimates

`POST /shipping_rate_estimates` quotes shipping before checkout. Send either a `cart_id` (the cart's items and shipping address are used) or an ad hoc list:

```json

{
  "items": [ { "product_id": "PRODUCT_ID", "quantity": 2 } ],
  "address": { "street": "…", "city": "…", "state": "…", "country": "…", "postal_code": "…" }
}
```

The response groups rates by delivery group:

```json

{
  "delivery_groups": [
    {
      "key": "…",
      "shipping_methods": "…",
      "rates": [
        { "type": "provider", "id": "…", "name": "Express", "price_excluding_tax": "12.5", "provider": "…" }
      ]
    }
  ]
}
```

Two optional extras: send `outlet_id` to quote against a specific outlet rather than the request's default, and note that a request carrying more than 100 items is rejected with `422`, type `request`, and reason `too_many_items`.

:::note
`price_excluding_tax` is returned as a decimal string, unlike order and payment amounts, which are JSON numbers.
:::

## Custom data

`POST` and `PATCH` requests on contacts, accounts, carts, and checkout accept a `custom_data` object of your own keys. Values merge into any existing custom data rather than replacing it, and come back in every response for the record. Use it to hold references your application needs, such as workspace or tenant IDs.

## Error handling

Errors return a consistent envelope:

```json

{ "error": { "type": "not_found", "details": {} } }
```

| Status | When |
|--------|------|
| `401` | Missing or invalid bearer token. Body is `{"error": "Unauthorized"}`. |
| `404` | Record not found, with `type` set to `not_found`. |
| `422` | Validation or processing failure, where `type` describes the failure, with the reason in `details`. |

The `type` values you will see on a `422` are:

| Type | When |
|------|------|
| `invalid` | A request that was understood but cannot be applied, such as a plan change to a product at the same price, or a payment for zero. This is the most common `422`. |
| `request` | A malformed or incomplete request, such as a cart with no billing address or a shipping estimate with neither a `cart_id` nor items and an address. |
| `invalid_account`, `invalid_contact` | The account or contact failed validation on create or update, with the field errors in `details`. |
| `payment_failed`, `order_failed` | The gateway declined the charge, or the order could not be created after payment. |

For example, creating a cart without a complete billing address returns:

```json

{ "error": { "type": "request", "details": { "reason": "billing_address_required" } } }
```

## Live test: a minimal end-to-end flow with curl

This walkthrough runs the whole flow (customer, cart, checkout, subscription) from a terminal.

### Before you start

Confirm all four of these, or the walkthrough fails partway through:

- A test store with the API set up, following [API configuration](api-configuration).
- The store's Salesforce ID and its API key.
- An active payment provider on that store, in test mode. Stripe is used below.
- At least one purchasable product. Use a subscription product to exercise steps 6 and 7 in full.

:::warning
Run this against a test store only. The card token below (`tok_visa`) is a Stripe test-mode token; in production your client-side code generates the token, so card details never touch your servers.
:::

Set your credentials once:

```bash

export API_BASE="https://api.yourstore.com/v1"
export AUTH="Authorization: Bearer STORE_ID:API_KEY"
```

1.  Confirm authentication and find your payment provider:

    ```bash

    curl -s "$API_BASE/store" -H "$AUTH"
    ```

    Note the `id` and `method` of the provider you will charge against in the `payment_providers` array.

2.  Pick a product to sell:

    ```bash

    curl -s "$API_BASE/products" -H "$AUTH"
    ```

    Note the product `id` of a subscription plan or product.

3.  Create the customer:

    ```bash

    curl -s -X POST "$API_BASE/contacts" \
      -H "$AUTH" -H "Content-Type: application/json" \
      -d '{
        "first_name": "Alex",
        "last_name": "Taylor",
        "email": "alex@example.com",
        "billing_address": { "street": "123 Example Street", "city": "San Francisco", "state": "CA", "country": "US", "postal_code": "94105" }
      }'
    ```

    Note `id` (the contact) and `account_id` in the response.

4.  (Optional) Set a shipping address on the account, so it differs from the billing address copied across in step 3:

    ```bash

    curl -s -X PATCH "$API_BASE/accounts/ACCOUNT_ID" \
      -H "$AUTH" -H "Content-Type: application/json" \
      -d '{ "shipping_address": { "street": "500 Delivery Lane", "city": "Oakland", "state": "CA", "country": "US", "postal_code": "94607" } }'
    ```

5.  Create the cart:

    ```bash

    curl -s -X POST "$API_BASE/carts" \
      -H "$AUTH" -H "Content-Type: application/json" \
      -d '{
        "contact_id": "CONTACT_ID",
        "items": [{ "product_id": "PRODUCT_ID", "quantity": 1 }],
        "billing_address": { "street": "123 Example Street", "city": "San Francisco", "state": "CA", "country": "US", "postal_code": "94105" }
      }'
    ```

    Note the cart `id` and its `checkout_mode`.

6.  Check out with a test card token:

    ```bash

    curl -s -X POST "$API_BASE/carts/CART_ID/checkout" \
      -H "$AUTH" -H "Content-Type: application/json" \
      -d '{ "payment": { "token": "tok_visa", "method": "stripe", "provider_id": "PROVIDER_ID" } }'
    ```

    The response confirms the order and, for subscription products, the new subscription.

7.  Verify the results. The checkout response already contains the order and subscription; confirm they read back correctly:

    ```bash

    curl -s "$API_BASE/orders/ORDER_ID" -H "$AUTH"
    curl -s "$API_BASE/subscriptions?contact_id=CONTACT_ID" -H "$AUTH"
    curl -s "$API_BASE/payment_methods?contact_id=CONTACT_ID" -H "$AUTH"
    ```

    The order shows as paid (`total_payable` is `0.0`), the subscription is `active` with a `next_billing_date`, and, for subscription carts, the card is saved against the contact.

If every step returns the expected response, the store is configured correctly end to end: authentication, product catalog, customer creation, payment gateway, and order creation are all working.

---

## Follow StoreConnect

- [Email Newsletter](https://getstoreconnect.com/c/lp-newsletter)
- [LinkedIn Newsletter](https://www.linkedin.com/build-relation/newsletter-follow?entityUrn=7444956928444862464)
- [YouTube](https://www.youtube.com/channel/UCngKdP2x8l1wcbAKW3tvU8g)
- [LinkedIn](https://www.linkedin.com/company/storeconnect)
- [X / Twitter](https://x.com/storeconnecthq)

## Popular Links

- [Partners](https://getstoreconnect.com/partners)
- [News](https://getstoreconnect.com/articles/news)
- [Events](https://getstoreconnect.com/articles/events)
- [Feature Comparison](https://getstoreconnect.com/how-we-compare)
- [Download a free trial](https://appexchange.salesforce.com/appxListingDetail?listingId=a0N3A00000FMkeKUAT)
- [Book a Demo](https://getstoreconnect.com/contact)

## Documentation

- [Help documentation](https://support.storeconnect.com/help-documentation)
- [AI agents](https://support.storeconnect.com/ai)
- [Videos & tutorials](https://support.storeconnect.com/videos-tutorials)
- [Developer reference](https://support.storeconnect.com/developer-reference)
- [Release notes](https://support.storeconnect.com/release-notes)
- [Troubleshooting](https://support.storeconnect.com/troubleshooting)
- [Trust Center](https://trust.getstoreconnect.com/)
- [Status Page](https://status.storeconnect.com/)

## Contact

- info@getstoreconnect.com
- US +1 415 745 3230
- AUS +61 2 8365 2308

100 S Ashley Dr, Suite 600-2461
Tampa FL 33602-600 USA

Level 22, Sydney Place
180 George Street
Sydney, NSW, 2000, AUS

---

StoreConnect Support — https://support.storeconnect.com/articles/storeconnect-api