Skip to content
Log in

StoreConnect API

On this page

:::note This article documents the commerce API: the system-to-system REST surface for creating customers and carts, taking payment, and reading orders and products, authenticated with the store’s API key. To build or manage a store with an AI agent instead, see the end-to-end store build sequence and how to connect an agent to your store. :::

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 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 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.

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.
  • 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.

Was this article helpful?

Was this article helpful?