Skip to content
Coritan Docs

Sell with the Commerce API

Run a store with Coritan as the merchant of record: products, regions, prices, orders, refunds and fulfilment from your systems.

View as Markdown

The Commerce API is the back office of your organization's store for goods. Your own systems use it to run the catalogue, prices, stock, shipping, promotions, orders, refunds and fulfilment. Shoppers buy through the Store API, which your storefront calls with a publishable key.

Coritan is the store's merchant of record. A Coritan company is the seller on every order: it takes the payment, issues the invoice and credit notes in its own name and accounts for the tax. Your balance records the part of each sale that is yours.

Every route is under https://api.coritan.com/api/v1/orgs/{org_slug}/commerce/. The API reference lists them under Commerce, with the product routes under Catalog & Services and the customer routes under Customers. This page covers the main tasks, and the reference has every field. The organization's Commerce tab does the same tasks in the dashboard (Run your store from the Commerce tab).

  • Ask support to turn commerce on for your organization. We create the store in test mode, with one sales channel called Web store. Until then, every route answers 404 with commerce_not_enabled.
  • Get a credential: an organization API key with the scopes your system needs, or a member's access token. Authenticate explains both.
  • Have your company's legal details and its owners' names ready for the merchant profile. The store takes real orders only after we approve it.

Send one of these with every request:

  • An organization API key in the X-API-Key header, for your own systems. Create it through the API with the scopes it needs, as Create a key with scopes shows. A key made in the dashboard has no scopes, so every route here refuses it.
  • A member's token in Authorization: Bearer, for a person: their coritan.com access token, or the token of their staff console session. Their role decides what the token can do.
Shell
curl "https://api.coritan.com/api/v1/orgs/acme/commerce/store" \
  -H "X-API-Key: $ORG_API_KEY"

When a request carries X-API-Key, we read the key and ignore any Authorization header. No route asks for a step-up.

Each route needs one scope from a key, or a role from a member. A :write scope includes the :read scope of its area. commerce:* grants every scope, and an area with :*, such as commerce.orders:*, grants every scope in that area.

Scope What it allows Lowest role
commerce.store:read Reading the store, its publishable keys and its event log. Read only
commerce.store:write Changing the store's settings, publishable keys, sales channels, regions, shipping profiles, zones and options, and fulfilment providers. Admin
commerce.catalog:read Reading products, tags, collections, categories, price lists, regions, shipping, stock locations, inventory items, sales channels and imports. Read only
commerce.catalog:write Changing products with their options, variants, prices and images; collections, categories and price lists; stock locations, inventory items and the locations each sales channel sells from; importing from Shopify. Admin
commerce.inventory:write Setting stock levels. Tier 3 support
commerce.customers:read Reading customers, customer groups and gift cards. Tier 1 support
commerce.customers:write Changing customers and customer groups. Tier 3 support
commerce.orders:read Reading orders with their documents, fulfilments and returns, the return reasons, and fulfilment providers with their deliveries and events. Tier 1 support
commerce.orders:write Changing an order and adding notes to it. Tier 2 support
commerce.refunds:write Refunding and cancelling orders, and refunding returns. Billing
commerce.promotions:write Reading and changing promotions, and issuing, changing and adjusting gift cards. Admin
commerce.fulfillment:write Creating fulfilments, marking them dispatched, shipped, delivered or cancelled, and sending a fulfilment provider's events. Tier 3 support
commerce.returns:write Opening, approving, declining, receiving and cancelling returns, and changing the return reasons. Reading them takes commerce.orders:read. Tier 2 support
commerce.finance:read Reading the balance, the ledger, disputes, payouts and tax reports. Billing
commerce.disputes:write Changing a dispute's evidence, sending it and accepting a dispute. Billing

The lowest role applies to a member's token. The roles form a ladder, and each role can do what the roles below it can: Read only, then Tier 1, Tier 2 and Tier 3 support, then Billing, then Admin and Owner. Organization roles and permissions describes them. The merchant profile takes an owner's or admin's token, and no key can reach it, because it holds the identities of your company's owners. Asking for a payout also takes an owner's or admin's token, and no key can ask for one.

GET /commerce/store answers the store with its settings and its status, the merchant review's status under merchant, can_go_live, and the store's sales_channels.

Status What it means
test The status a new store starts in. It takes test orders, paid with test payments through test publishable keys.
live The store also takes real orders, through live publishable keys. It needs an approved merchant profile, and Coritan makes the switch.
suspended Shoppers cannot buy: the Store API answers 503 with store_unavailable. This API keeps working.
disabled Every route answers 404 with commerce_not_enabled, except reading the store and the merchant profile.

Test and live business stay apart. Orders, gift cards and events carry livemode, which is false for test ones, and test orders never reach your balance.

Coritan sells your goods in its own name, so we check who you are before the store goes live. The profile routes take an owner's or admin's token.

  1. Read the profile with GET /commerce/merchant-profile. The first read creates it as a draft, filled in from your organization's details. The answer has profile, missing_for_submission with what review still needs, and editable.

  2. Fill it in with PUT /commerce/merchant-profile. Send the fields you change; the others keep their values.

    Shell
    curl -X PUT "https://api.coritan.com/api/v1/orgs/acme/commerce/merchant-profile" \
      -H "Authorization: Bearer $CORITAN_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "legal_name": "Acme Goods Ltd",
        "country_code": "GB",
        "support_email": "support@example.com",
        "product_description": "Printed T-shirts and mugs.",
        "address": {"line1": "1 Example Street", "city": "London", "postal_code": "EC1A 1AA", "country_code": "GB"},
        "ship_from": {"line1": "Unit 4, Example Park", "city": "Leeds", "postal_code": "LS1 1AA", "country_code": "GB"},
        "return_address": {"line1": "Unit 4, Example Park", "city": "Leeds", "postal_code": "LS1 1AA", "country_code": "GB"},
        "beneficial_owners": [{"name": "Alex Example", "date_of_birth": "1985-04-12", "country_code": "GB", "ownership_percent": 100}]
      }'
    
  3. Send it to review with POST /commerce/merchant-profile/submit. When something is missing, the answer is 422 with the missing fields in missing.

Review needs legal_name, country_code, support_email, product_description, at least one entry in beneficial_owners, and three addresses: address for the company, ship_from and return_address, each with at least line1, city and country_code. An address also takes company, first_name, last_name, line2, province, postal_code and phone. Each owner takes a name, a date_of_birth, a country_code and an ownership_percent from 0 to 100, and we keep the first 10. The profile also takes trading_name, legal_form, registration_number, tax_id, website, support_phone, up to 20 product_categories, expected_monthly_volume and volume_currency.

Status What it means
draft You are filling it in. Changing a submitted profile brings it back here, and so does Coritan reopening a rejected or suspended one.
submitted Waiting for review.
approved Coritan can switch the store to live.
restricted Review stopped short of approval. Read review_notes, change the profile and submit it again.
rejected Review turned it down. You can change it and submit it again.
suspended Coritan suspended the merchant, which also suspends a live store.

An approved or suspended profile is locked: changing it answers 409 with profile_locked, and support makes any change. After review, the profile's terms show the fee, the reserve and the payout delay Coritan set, and review_notes hold what the reviewer wrote.

A shopper can check out once the store has a region for where they live, a stock location, shipping, products with prices and a publishable key for your storefront. Money is always a whole number in the currency's smallest unit: 2500 in EUR is €25.00.

PATCH /commerce/store changes the store's name, default_currency and supported_currencies, default_locale and supported_locales (language tags such as en-GB), allowed_origins, storefront_url, support_email, and the keys under settings.

Shell
curl -X PATCH "https://api.coritan.com/api/v1/orgs/acme/commerce/store" \
  -H "X-API-Key: $ORG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"default_currency": "EUR", "allowed_origins": ["https://shop.example.com"], "storefront_url": "https://shop.example.com", "settings": {"terms_url": "https://shop.example.com/terms", "terms_version": "2026-09"}}'

allowed_origins lists the sites whose browsers may call the Store API, each as a scheme and host, up to 20. The default currency is always among the supported ones.

Setting What it does
guest_checkout true by default. false makes shoppers sign in before they pay.
order_access_days For how many days after an order is placed its access token still opens it. Defaults to 90.
terms_url, terms_version When terms_url is set, shoppers must accept your terms to pay, and each order records the version they accepted (up to 40 characters).
privacy_url Your privacy policy, which the Store API passes to your storefront.
auto_dispatch false stops us sending fulfilments to any provider on our own.
release_delay_minutes The least time every fulfilment provider waits after an order is placed, up to 10,080 (a week).
risk_high_value The amount above which a first order counts as high value, in minor units. Defaults to 50000.
returns_window_days For how many days after an item is delivered a shopper can ask to return or exchange it, counted from when it shipped if no delivery is recorded. Defaults to 30, and 0 stops shoppers from asking.
refund_policy, shipping_policy Your refund and shipping policies as text, up to 5,000 characters each. Dispute evidence quotes them.
payout_schedule How often we create payouts: daily, weekly or monthly. Defaults to weekly.

Setting a key to null removes it, which brings back its default. A key the store does not know answers 422.

A region is a group of countries that share a currency, tax handling and payment methods. A cart belongs to one region and is priced in its currency.

Shell
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/commerce/regions" \
  -H "X-API-Key: $ORG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Europe", "currency_code": "EUR", "countries": ["DE", "FR", "NL"], "payment_providers": ["stripe", "paypal"]}'
  • payment_providers says whether shoppers in the region pay with stripe, paypal or both, and defaults to ["stripe"]. PayPal takes only currencies with two decimal places. A test cart can also pay with manual, a test payment that moves no money.
  • Prices exclude tax unless you set tax_inclusive to true. We work out the tax on each cart from Coritan's own rates for the shopper's address, and charge none where Coritan does not collect tax. The region's automatic_taxes field changes nothing.
  • A country belongs to one region at most, and only where Coritan sells. A country we do not sell into yet answers 409 with market_not_enabled.
  • The first enabled region becomes the store's default. Send is_default: true to move the default. The default region cannot be disabled or deleted.
  • A region's currency joins the store's supported currencies. Deleting a region also deletes the prices set for it.

A store has up to 50 regions.

A stock location is a place that holds your goods, such as a warehouse. Stock is counted per location.

Shell
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/commerce/stock-locations" \
  -H "X-API-Key: $ORG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Leeds warehouse", "address": {"line1": "Unit 4, Example Park", "city": "Leeds", "postal_code": "LS1 1AA", "country_code": "GB"}, "is_default": true}'

Set fulfillment_provider_id on a location that a fulfilment provider runs. A store has up to 100 locations. A sales channel sells from every enabled location until you choose some with PUT /commerce/sales-channels/{channel_id}/stock-locations.

Shipping has three parts:

  • A shipping profile groups products that ship the same way. The store's default profile covers every product that names no other, and you cannot delete it. Add a custom or gift_card profile with POST /commerce/shipping-profiles, up to 50.
  • A shipping zone is where you ship: a list of up to 300 places in geo, each a country_code, optionally narrowed by province_code and postal_prefix. A store has up to 200 zones.
  • A shipping option is what the shopper chooses, with its price, for one zone and one profile. A store has up to 500 options.
Shell
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/commerce/shipping-zones" \
  -H "X-API-Key: $ORG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Western Europe", "geo": [{"country_code": "DE"}, {"country_code": "FR"}, {"country_code": "NL"}]}'

curl -X POST "https://api.coritan.com/api/v1/orgs/acme/commerce/shipping-options" \
  -H "X-API-Key: $ORG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"zone_id": 12, "name": "Standard", "currency_code": "EUR", "price_type": "free_over", "amount": 490, "free_over_amount": 5000, "delivery_min_days": 2, "delivery_max_days": 4}'

An option without profile_id belongs to the default profile. Its price_type is one of these, worked out from the items of its profile after discounts:

price_type Price
flat amount.
free_over amount, or nothing once the subtotal reaches free_over_amount.
weight_tiers The first of tiers whose up_to, in grams, covers the weight.
subtotal_tiers The first of tiers whose up_to, in minor units, covers the subtotal.

tiers holds up to 50 {up_to, amount} entries in ascending order, and only the last may leave up_to as null. An option no tier covers is not offered. min_subtotal and max_subtotal limit the subtotals an option is offered for, and provider_id and provider_code tell a fulfilment provider which service to use. At checkout, the shopper picks one option for each profile among the items that ship, from a zone that matches the address and in the cart's currency.

Create publishable keys and sales channels

Section titled Create publishable keys and sales channels

Your storefront sends a publishable key to the Store API. It names the store and is safe to put in a browser.

Shell
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/commerce/publishable-keys" \
  -H "X-API-Key: $ORG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"label": "Web shop", "livemode": false}'

The answer is 201 with the key, whose token starts with pk_test_, or with pk_live_ when livemode is true. A live key works once the store is live. GET /commerce/publishable-keys shows every key with its token, and DELETE /commerce/publishable-keys/{key_id} revokes one; a revoked key can keep working for up to 15 seconds.

A sales channel is a place you sell, such as your website or a marketplace. A key sells the products of the channels in its sales_channel_ids, or of the default channel when it names none. A store has up to 50 channels. The default channel cannot be disabled or deleted, and PATCH /commerce/sales-channels/{channel_id} with is_default: true moves the default.

A product has options, such as size and colour, and variants, one for each combination you sell. Each variant has its own SKU, prices and stock.

Shell
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/commerce/products" \
  -H "X-API-Key: $ORG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Logo T-shirt",
    "handle": "logo-t-shirt",
    "status": "published",
    "options": [{"title": "Size", "values": ["S", "M", "L"]}],
    "variants": [
      {"title": "S", "sku": "TSHIRT-S", "options": {"Size": "S"}, "weight_g": 180, "prices": [{"currency_code": "EUR", "amount": 2500}]},
      {"title": "M", "sku": "TSHIRT-M", "options": {"Size": "M"}, "weight_g": 190, "prices": [{"currency_code": "EUR", "amount": 2500}]},
      {"title": "L", "sku": "TSHIRT-L", "options": {"Size": "L"}, "weight_g": 200, "prices": [{"currency_code": "EUR", "amount": 2500}]}
    ],
    "tags": ["clothing"]
  }'

The answer is 201 with the whole product. Keep these rules in mind:

  • A product is a draft until you set status to published, and the Store API shows published products only. archived hides it.
  • It sells in the store's default channel unless you send sales_channel_ids.
  • A handle or a SKU belongs to one product. A taken handle answers 409 with handle_taken, and a taken SKU 409 with sku_taken. Without a handle, we make one from the title.
  • A variant tracks stock unless you set manage_inventory to false, and it cannot be ordered beyond its stock unless allow_backorder is true. A tracked variant with no stock set cannot be bought unless it allows backorders.
  • A price can be narrowed to a region_id, or to a quantity with min_quantity and max_quantity, and can carry a compare_at_amount to show as the old price.
  • A product has up to 10 options, 250 values an option, 500 variants, 250 tags and 250 images.

PATCH /commerce/products/{product_id} changes the fields you send. When you send options, variants, sales_channel_ids, collection_ids, category_ids or tags, the list replaces the product's: name each variant you keep by its id or sku, or we delete it. The options, variants and images also have routes of their own under the product. DELETE /commerce/products/{product_id} archives the product: orders keep its variants, and its handle and SKUs are free for new products.

Images are links: POST /commerce/products/{product_id}/images takes a url, alt text, and optionally a variant_id, a rank and the image's width and height.

  • A collection is a set of products your storefront shows together. A manual collection holds the products you set with PUT /commerce/collections/{collection_id}/products. A smart collection holds every product that matches its rules, as {"match": "all", "conditions": [{"field": "tag", "op": "equals", "value": "clothing"}]}, where field is tag, product_type, vendor or title, op is equals or contains, and match is all or any.
  • A category sits in a tree through parent_id. The Store API hides a category, and everything under it, while its is_active is false or its is_internal is true.
  • Tags are words on the product. GET /commerce/product-tags lists the ones in use, most used first.

A price list changes what variants cost for a while or for some customers. POST /commerce/price-lists takes a title, a kind, a status of draft or active, optionally starts_at, ends_at and customer_group_ids, and prices that each name a variant_id.

  • In a sale list, the shopper pays the lowest of the variant's own price and every active sale list they qualify for.
  • An override list they qualify for replaces all of those.

The Store API shows what the price would be without any list as original_amount, which your storefront can strike through. PUT /commerce/price-lists/{price_list_id}/prices replaces a list's prices, up to 5,000.

POST /commerce/imports takes the CSV that Shopify's admin exports and queues it for import. Send the file as the multipart field file, as a text/csv body, or as JSON {"csv": "...", "filename": "..."}. Try it first with dry_run=true: a dry run goes through the same steps and saves nothing but its report.

Shell
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/commerce/imports?dry_run=true&currency_code=EUR" \
  -H "X-API-Key: $ORG_API_KEY" \
  -F "file=@products_export.csv"
  • mode=upsert, the default, updates the products whose handle the store already has, and mode=create_only skips them.
  • Prices are read in currency_code, which defaults to the store's default currency and must be one the store sells in.
  • Variant Inventory Qty becomes the stock at location_id, which defaults to the store's default location.
  • Shopify's active, draft and archived become published, draft and archived. A new product without a status is a draft.
  • A blank cell keeps the store's value. A blank Variant Inventory Tracker turns stock tracking off for the variant, as in Shopify.

A file is at most 10 MB and 5,000 rows, and must be UTF-8. We read it before we queue it, and a file we cannot import at all, such as one without a Handle column, answers 422 with the reason. Otherwise the answer is 202 with the queued import, and we import the products in batches of 50. A store runs one import at a time: while one is queued or running, another answers 409 with import_in_progress and that import's import_id. The route's earlier path, POST /commerce/imports/shopify-products, works the same way and also answers 202 with the queued import.

Follow the import with GET /commerce/imports/{import_id}:

  • status starts as queued, moves to running, and ends as completed or failed. A failed import says why in error.
  • progress counts the products_processed out of products_total.
  • report counts what changed: products_created, products_updated, products_skipped, variants_created, variants_updated, images_added, levels_set and categories_created. Its errors list up to 500 rows the import could not use, each with its spreadsheet row, its handle and a message, and error_count counts them all. The rest of the file still imports.

When an import fails part way, the products in the batches it finished stay imported, so send the file again to import the rest. GET /commerce/imports lists the store's imports newest first, without their errors: 20 a page by default and up to 100, narrowed by status.

Each variant that tracks stock has an inventory item, with a level at each location. A level has a stocked_quantity (on the shelf), a reserved_quantity (held for orders that have not shipped), an incoming_quantity, and an available_quantity, which is stocked less reserved. Shipping takes units off both stocked and reserved, and cancelling an order gives its reserved units back.

Set one item's level at one location with stocked_quantity, or change it by a delta:

Shell
curl -X PUT "https://api.coritan.com/api/v1/orgs/acme/commerce/inventory-items/301/levels/7" \
  -H "X-API-Key: $ORG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"delta": -2}'

Set many at one location by SKU, up to 1,000 a request:

Shell
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/commerce/inventory/levels/batch" \
  -H "X-API-Key: $ORG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"location_id": 7, "levels": [{"sku": "TSHIRT-S", "stocked_quantity": 40}, {"sku": "TSHIRT-M", "stocked_quantity": 55}]}'

The answer lists the levels it set and any unknown_skus. A SKU sent twice takes its last count, and stock never goes below zero. GET /commerce/inventory-items?low_stock=5 finds the items with 5 or fewer available.

Offer promotions and gift cards

Section titled Offer promotions and gift cards
Shell
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/commerce/promotions" \
  -H "X-API-Key: $ORG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"code": "WELCOME10", "status": "active", "value_type": "percentage", "percentage": 10, "target": "order", "conditions": {"first_order_only": true}, "per_customer_limit": 1}'
  • value_type is percentage, fixed (an amount with its currency_code) or free_shipping, and target is order, items or shipping.
  • conditions can ask for a min_subtotal in the promotion's currency_code, items from product_ids or collection_ids, a shopper in one of customer_group_ids, or first_order_only.
  • usage_limit, per_customer_limit, starts_at and ends_at bound its use. A promotion that is not combinable applies only on its own.
  • Codes are unique in the store and match whatever the case. A new promotion is a draft until you make it active.
  • is_automatic: true makes a promotion without a code that applies to every cart it fits. Up to 50 can be active at once.

A promotion that an order used cannot be deleted: set its status to disabled instead.

Shell
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/commerce/gift-cards" \
  -H "X-API-Key: $ORG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"currency_code": "EUR", "amount": 5000, "recipient_email": "alex@example.com", "message": "Thank you for your help."}'

The answer holds the card and its code. We show the code this once and do not email it, so send it to the recipient yourself. POST /commerce/gift-cards/{gift_card_id}/adjust adds or takes away an amount with a note, and a card's balance cannot go below zero. PATCH sets a card's status to active or disabled. A card is exhausted when its balance runs out and expired after its expires_at. The gift cards that shoppers buy in your store are emailed to their recipients.

A customer here is one of your organization's customers who has ordered from the store or has a store profile. GET /commerce/customers searches them with q, and PATCH /commerce/customers/{customer_id} changes your note about them, their tags, tax_exempt, and their vat_id with vat_id_valid once you have checked it. You can set accepts_marketing to false only: shoppers opt in themselves.

A customer group collects customers for price lists and promotions. Create one with POST /commerce/customer-groups and set its members with PUT /commerce/customer-groups/{group_id}/customers, up to 1,000. Deleting a group keeps its customers.

A cart becomes an order when the shopper pays through the Store API. We email them a confirmation, and later a notice if you cancel or refund the order.

GET /commerce/orders lists orders newest first, with count for everything the filters match:

Filter Takes
status pending, completed, canceled, archived or requires_action
payment_status not_paid, awaiting, captured, partially_refunded, refunded, canceled or requires_action
fulfillment_status not_fulfilled, partially_fulfilled, fulfilled, partially_shipped, shipped, partially_delivered, delivered, partially_returned, returned or canceled
q An order number such as #1001, an order_ ID, or part of an email address
customer_id, email, livemode, risk_hold Exact values
placed_from, placed_to Dates; placed_to is not included
limit, offset Up to 200 a page; 50 by default
Shell
curl "https://api.coritan.com/api/v1/orgs/acme/commerce/orders?payment_status=captured&fulfillment_status=not_fulfilled&livemode=true" \
  -H "X-API-Key: $ORG_API_KEY"

Reach an order by its id or its order_ public ID. Order numbers start at 1001. GET /commerce/orders/{order_id} answers the order with its items, totals, payments, refunds, fulfilments, documents, timeline and risk, the Coritan company that sold it, and what is left to refund under refundable. It also lists the order's returns, each with its kind, status and any exchange_order_id, and for an exchange order, exchange_for names the return and the order it replaces. GET /commerce/orders/{order_id}/documents lists the invoice and credit notes as the Coritan company issued them.

PATCH /commerce/orders/{order_id} corrects the email or the shipping_address, or changes the note (up to 2,000 characters) and metadata. The address keeps its country, and in the US and Canada its state or province, because the order was taxed for them. The address cannot change once part of the order has shipped, which answers 409 with already_shipped, or once a fulfilment has gone to its provider, which answers 409 with fulfillment_started: ask the provider to change it.

POST /commerce/orders/{order_id}/notes adds a note of up to 500 characters to the timeline, and customer_visible: true shows it on the shopper's order page.

We hold an order that shows two or more of these signals, or that the payment provider rates as high risk:

  • It is the first order from its email and above risk_high_value.
  • The billing and shipping countries differ.
  • The shopper's IP address is in another country than the shipping address.
  • The payment provider rates it as elevated risk.

A held order has risk_hold: true, and risk lists the reasons. Nothing on it can be fulfilled until you release it, a fulfilment provider receives nothing for it, and any gift cards it sold cannot be spent. When you have checked the order, release it:

Shell
curl -X PATCH "https://api.coritan.com/api/v1/orgs/acme/commerce/orders/order_01j8z3k4m5n6p7q8r9s0t1v2w3" \
  -H "X-API-Key: $ORG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"risk_hold": false}'

To turn the order down instead, cancel it.

POST /commerce/orders/{order_id}/refunds refunds part or all of an order. Send an amount, which we spread over what is left, or items with each order_item_id and quantity, plus shipping: true to refund the shipping too. Add a reason of up to 64 characters and a note.

Shell
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/commerce/orders/order_01j8z3k4m5n6p7q8r9s0t1v2w3/refunds" \
  -H "X-API-Key: $ORG_API_KEY" \
  -H "Idempotency-Key: refund-1043-1" \
  -H "Content-Type: application/json" \
  -d '{"items": [{"order_item_id": 5521, "quantity": 1}], "reason": "damaged"}'

We refund the card or PayPal payment first and credit the rest to any gift cards the shopper paid with, and we issue a credit note. The answer is 201 with the refund and the order.

A payment that reaches an order after it was placed, such as a shopper paying twice, is refunded on its own. The order's payments mark it with unmatched: true. Send its id as payment_id, with an amount or with none to refund all that is left of it. Such a refund takes no items or shipping. The payment the order was paid with answers 422 there, and an amount above what is left of that payment answers 422 with refund_too_large.

A refund takes an optional Idempotency-Key header of up to 128 characters. A retry with the same key and body within 24 hours gets the first answer back and refunds nothing more. The same key with another body, or for another order, answers 409 with idempotency_key_reused, and a retry while the first request runs answers 409 with idempotency_in_progress. A refund that fails keeps no key, so you can send it again with the same one.

A refund moves the stock of the units it names in items, and only those. An amount, or shipping on its own, moves no stock.

  • Without restock, the refunded units come first from those that have not shipped. They will never ship: we release their stock and take them out of any pending fulfilment we have not yet tried to send, and cancel one that is left empty. When a fulfilment that a provider already has still holds them, the order's timeline names it, so ask the provider to take them out. Any other refunded units have shipped, and their stock does not move.
  • Send restock: true when shipped units have come back to you. The refunded units then come first from the shipped units not yet returned: they go back in stock at the location_id you send, or where they shipped from, and count in the item's returned_quantity. Any others come from units that have not shipped, as without restock.

restock needs items, and location_id needs restock: true and must be one of the store's stock locations. Otherwise the answer is 422.

Units that an open return holds are refunded through the return, as Refund a return describes. Asking for more of an item in items than open returns leave answers 409 with units_in_return: return_id names a return that holds units of the item, and refundable_quantity says how many you can refund on the order. Refunding an exchange order, which sends an exchange's new items or a claim's replacements, answers 409 with exchange_order.

POST /commerce/orders/{order_id}/cancel refunds whatever is left, gives the stock back and ends the order. Once any of it has shipped, refund it instead. Cancelling an exchange order refunds nothing, and what its new items took becomes refundable again on the order they came from.

Warning

A refund or a cancellation sends money back to the shopper at once, and neither can be undone.

Refunds and cancellations share a limit of 30 a minute for the organization.

A fulfilment is a set of an order's items that leave together from one stock location. Gift cards and items that need no shipping never go into one. You ship fulfilments yourself, or a fulfilment provider ships them, such as a 3PL warehouse that we send each fulfilment to.

When an order is placed, we create its fulfilments: one for each provider and location its items go to. An order on risk hold gets them when you release it. For each item, we choose the first of these that applies:

  1. The provider of the shipping option the shopper chose.
  2. The provider that runs the stock location holding the item.
  3. Your only enabled provider, when you have exactly one.
  4. You.

GET /commerce/orders/{order_id}/fulfillments lists an order's fulfilments with the requests we sent to each provider, and each item's unfulfilled_quantity.

Status What it means
pending No provider has it yet. You ship it, or it waits for the release delay or for you to dispatch it.
requested Its provider received it.
accepted Its provider took it on.
exception Something needs you. The fulfilment's exception field says what.
shipped It left the stock location.
delivered It arrived.
canceled, rejected You cancelled it, or its provider refused it. Its items are free for a new fulfilment.

A fulfilment that no webhook provider ships stays pending until you ship it:

  1. Find its id in GET /commerce/orders/{order_id}/fulfillments.

  2. Mark it shipped, with its tracking. To ship part of it, add items: those units become a fulfilment of their own, and the answer's remainder keeps the rest under the old ID.

    Shell
    curl -X POST "https://api.coritan.com/api/v1/orgs/acme/commerce/orders/order_01j8z3k4m5n6p7q8r9s0t1v2w3/fulfillments/88/ship" \
      -H "X-API-Key: $ORG_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"tracking": [{"carrier": "DHL", "number": "00340434161234567890", "url": "https://www.dhl.com/track?id=00340434161234567890"}]}'
    
  3. Mark it delivered with POST .../fulfillments/{fulfillment_id}/deliver. A fulfilment you never marked shipped is marked shipped first, without tracking.

A shipment takes up to 20 tracking entries, each with a carrier and a number of up to 100 characters, and optionally a url. The Store API shows the shopper each shipment with its tracking.

POST .../fulfillments/{fulfillment_id}/cancel cancels a fulfilment that has not shipped, with an optional reason, and frees its items. When its provider may already have it, we send the provider a fulfillment.cancel. Cancelling a fulfilment refunds nothing.

To ship freed items, add a fulfilment for them. Name each order_item_id and quantity, whose IDs are in the order's items, and optionally the provider_id or location_id to use:

Shell
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/commerce/orders/order_01j8z3k4m5n6p7q8r9s0t1v2w3/fulfillments" \
  -H "X-API-Key: $ORG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"items": [{"order_item_id": 5521, "quantity": 1}], "provider_id": 3}'

A fulfilment takes only units that no other fulfilment holds, and asking for more answers 409 with quantity_exceeds_unfulfilled. A webhook provider whose auto_dispatch is on receives the new fulfilment at once, without the release delay.

A provider is manual or webhook. A manual provider is a team that ships by hand, such as your own warehouse: we send it nothing, and it reports what it ships with an organization API key. A webhook provider is an HTTPS endpoint at your 3PL. We send it each fulfilment to ship, and it sends back what happened.

Shell
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/commerce/fulfillment-providers" \
  -H "X-API-Key: $ORG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"kind": "webhook", "name": "Example 3PL", "endpoint_url": "https://3pl.example.com/coritan/orders", "config": {"release_delay_minutes": 60}}'

The answer holds secrets with the outbound_secret, which signs what we send, and the inbound_secret, which the 3PL signs its events with. We show them this once. POST .../rotate-secrets makes new ones, and the old ones stop working at once. POST .../test sends a signed ping and reports what the endpoint answered.

config key What it does
auto_dispatch true by default: we send each fulfilment once the release delay has passed. false waits for you to dispatch it.
release_delay_minutes How long after the order is placed we wait, from 0 to 10,080. It gives you time to change or cancel an order.
service_code_map Maps a shipping option's ID or provider code to the 3PL's service code, up to 200 entries.
field_map Renames the top-level keys of the body we send, for a 3PL that expects other names.
include_prices true by default. false leaves prices and totals out of the body.
returns false by default. true makes a webhook provider receive the goods that come back from returns of units it shipped. We send it return.requested and return.canceled, and it answers with return.received, as Have a 3PL receive the goods explains.

The endpoint must use https on a public host. A provider that is disabled receives nothing, and what waited for it goes out when you enable it again. You cannot delete a provider while a fulfilment that has not shipped uses it. A store has up to 20 providers.

Check what we send to a provider

Section titled Check what we send to a provider

Each request is a POST with a JSON body whose schema is coritan.fulfillment.v1 and whose type is fulfillment.create, fulfillment.cancel or ping, or for a provider that receives returns, return.requested or return.canceled. A fulfillment.create holds the order with its shipping address, shipping method and seller, the fulfillment with its location, and the items. A return.requested holds the order, the return with the location its goods go to, and the items, as Have a 3PL receive the goods describes. Every request carries these headers:

Header Holds
X-Coritan-Signature t=<unix seconds>,v1=<hex HMAC-SHA256>
X-Coritan-Event The type
X-Coritan-Delivery The request's ID
Idempotency-Key A key that stays the same when we retry the request

To check a request, compute the HMAC-SHA256 of <t>.<raw body> with the outbound secret, compare it with v1, and refuse a t that is too old:

Python
import hashlib
import hmac
import time


def verify(secret: str, header: str, body: bytes, tolerance: int = 300) -> bool:
    parts = dict(item.split("=", 1) for item in header.split(","))
    timestamp = int(parts["t"])
    expected = hmac.new(secret.encode(), f"{timestamp}.".encode() + body, hashlib.sha256).hexdigest()
    return abs(time.time() - timestamp) <= tolerance and hmac.compare_digest(expected, parts["v1"])

Answer with a 2xx status to accept the request. A JSON answer with a reference saves it as the fulfilment's provider_reference, or the return's for a return.requested. We retry after 408, 429, a 5xx, a timeout of 10 seconds or a failed connection, 1, 5, 30, 120 and 480 minutes later, and then give up. Any other answer fails at once, and we do not follow redirects. A fulfillment.create that fails puts the fulfilment in exception; fix the cause and send it again with POST .../fulfillments/{fulfillment_id}/dispatch. When a return.requested fails, we note it in the order's timeline, and you receive the goods yourself when they arrive. After five failures in a row we send the commerce.fulfillment.provider_failing webhook.

GET /commerce/fulfillment-providers/{provider_id}/deliveries lists what we sent and how the endpoint answered, and GET .../events lists what the provider sent us. Filter the deliveries by kind: create, cancel, ping, return_create or return_cancel.

The 3PL reports back with POST /api/v1/orgs/{org_slug}/commerce/fulfillment-providers/{provider_id}/events. It signs the raw body with the inbound secret in X-Coritan-Signature, in the same form as above and within 5 minutes, or sends an organization API key with commerce.fulfillment:write in X-API-Key.

JSON
{
  "id": "evt-3pl-10492",
  "type": "fulfillment.shipped",
  "data": {
    "fulfillment_id": 88,
    "tracking": [{"carrier": "DHL", "number": "00340434161234567890"}]
  }
}

id is the 3PL's own event ID, up to 128 characters. An event already processed answers 200 with status: duplicate and changes nothing, so the 3PL can send it again safely. One that failed can be sent again under the same ID.

type data What it does
fulfillment.accepted fulfillment_id or order_id, and reference The 3PL took the fulfilment.
fulfillment.rejected fulfillment_id, and reason The 3PL refused it, and its items are free again.
fulfillment.shipped fulfillment_id or order_id, tracking, and items by sku or order_item_id with quantity Marks it shipped. Without items, everything in it shipped.
fulfillment.delivered fulfillment_id or order_id Marks it delivered.
fulfillment.exception fulfillment_id and message Marks a problem with it.
inventory.levels levels, each with sku, stocked_quantity and incoming_quantity, and location_id Sets stock at a location the provider runs, up to 1,000 SKUs.
return.received return_id, location_id, and items by sku or order_item_id with quantity and condition Receives a return we sent the provider. sellable units, the default, go back in stock at location_id, which must be a location the provider runs, or else where the return.requested said. damaged units do not. Without items, every unit comes back sellable. It refunds nothing.

order_id is the order's order_ public ID. The answer is 200 with status processed, ignored, failed or duplicate. A body that is not an event answers 422, and one over 256 KB answers 413.

The store's events reach your organization webhooks with names that start with commerce.. Subscribe a webhook to the exact names, or to * for everything. The delivery, retries and signature are the ones Receive organization webhooks describes.

JSON
{
  "id": "evt_48213",
  "type": "commerce.order.placed",
  "livemode": true,
  "created_at": "2026-09-26T14:03:11Z",
  "resource": {"type": "order", "id": "1742"},
  "data": {"order_id": 1742, "public_id": "order_01j8z3k4m5n6p7q8r9s0t1v2w3", "display_id": 1043, "livemode": true, "risk_hold": false}
}

An event can arrive more than once, so skip an id you have handled.

Event When data
commerce.product.created, .updated, .deleted A product changes. product_id, handle
commerce.inventory.updated Stock changes through the API, an import, a provider, a refund or a return. location_id, inventory_item_ids, skus
commerce.order.placed A shopper completes a cart. order_id, public_id, display_id, livemode, risk_hold
commerce.order.updated An order's email, address, note or risk hold changes. The same, plus changes
commerce.order.canceled An order is cancelled. The same, plus reason, refund_id, refunded_amount, gift_card_amount, currency_code
commerce.payment.captured A payment is taken. order_id, payment_id, amount, currency_code, provider, livemode
commerce.payment.refunded Money goes back to the shopper. order_id, public_id, refund_id, amount, gift_card_amount, total, currency_code, reason, cancellation, livemode
commerce.customer.created A guest order creates a customer. customer_id, email, is_guest
commerce.customer.updated A customer's store profile or groups change. customer_id, changed
commerce.gift_card.issued A gift card is sold or issued. The code is never in the event. id, last4, currency_code, amount, recipient_email, issuing_order_id, expires_at
commerce.fulfillment.created We or you create a fulfilment. order_id, fulfillment_id, provider_id, items
commerce.fulfillment.accepted The provider takes a fulfilment on. order_id, fulfillment_id, reference
commerce.fulfillment.rejected, .canceled The provider refuses a fulfilment, or it is cancelled. order_id, fulfillment_id, reason
commerce.fulfillment.shipped A fulfilment ships. order_id, fulfillment_id, tracking, items
commerce.fulfillment.delivered A fulfilment arrives. order_id, fulfillment_id
commerce.fulfillment.exception A fulfilment needs you, such as when its provider's endpoint refused it. order_id, fulfillment_id, message
commerce.fulfillment.provider_failing A provider's endpoint fails five times in a row. We send it again only after a request succeeds. provider_id, name, consecutive_failures, last_error
commerce.dispute.opened, .won, .lost A shopper's dispute opens, or ends in your favour or theirs. order_id, dispute_id, amount, currency_code
commerce.payout.created, .paid, .failed, .canceled A payout is created, completed, declined or cancelled. id, currency_code, amount, platform_fee, net_amount, status, payout_method, entry_count
commerce.return.requested, .approved, .rejected, .received, .refunded, .completed, .canceled A return, exchange or claim is asked for or opened, approved, declined, received, refunded, settled with nothing to refund, or cancelled. return_id, order_id, public_id, kind, claim_type, status, currency_code, livemode, and the fields each one adds
commerce.exchange.created We create an exchange order to send an exchange's new items or a claim's replacements. return_id, kind, order_id, public_id, exchange_order_id, exchange_public_id, display_id, items, livemode

GET /commerce/events shows the same events with whether we delivered each one, filtered by event_type (without the commerce. prefix) and status (pending, dispatched or failed).

Read the balance and the ledger

Section titled Read the balance and the ledger

GET /commerce/balance answers, for each currency your live orders were paid in:

Field What it is
pending Money from sales that is not yours to take yet.
available Money that is yours.
reserved Money held back as a reserve until its release date.
payable available less reserved, and never below zero.

A sale becomes available after the payout delay in your merchant profile's terms, 7 days unless we set another, or when it is delivered, if that comes first.

GET /commerce/ledger lists every movement, newest first, filtered by currency_code, status, entry_type, order_id, created_from and created_to. Each order books its sale and shipping, the tax that the Coritan company owes, and our commission, which is the fee percentage in your terms plus any fixed fee. A refund books the reverse and returns the commission on what it refunds. A dispute books chargeback rows, as Answer disputes on store orders describes. Test orders book to a separate ledger that you read with livemode=false, and never count towards the balance.

We pay the payable amount out on your store's schedule. A payout marks the rows it pays paid_out and books a payout row against them, as Get store payouts and tax reports describes.

Most Commerce API routes read no Idempotency-Key, so a request that timed out may have succeeded. Before you send it again, check:

  • Create a product with an explicit handle. If the first request succeeded, the retry answers 409 with handle_taken and creates nothing.
  • Read the order before you retry a refund sent without an Idempotency-Key. A refund never exceeds what is left, and while an earlier one waits for the payment provider, a new one answers 409 with refund_in_progress.
  • Set stock with stocked_quantity: sending the same count twice gives the same result, where a repeated delta counts twice.
  • A provider's events carry their own id, so a repeated event changes nothing.

Refunding an order and every write on a return take an Idempotency-Key, as Refund or cancel an order and Handle returns, exchanges and claims explain.

Sending a dispute's evidence, accepting a dispute and asking for a payout take an Idempotency-Key, as Answer disputes on store orders and Ask for a payout explain.

In the Store API, completing a cart takes an Idempotency-Key, as Complete the order explains.

An error answers with a status and a detail object that holds a machine-readable error code and a message to show a person. Some errors add fields, such as field for the input that is wrong.

JSON
{"detail": {"error": "invalid", "message": "currency_code must be a currency code such as EUR.", "field": "currency_code"}}
Status error Meaning
403 scope_required, role_required, people_only, forbidden The credential cannot do this.
404 not_found, commerce_not_enabled The thing, or the store, does not exist for this organization.
409 conflict or a specific code, such as handle_taken The request clashes with the store's state.
422 invalid or a specific code The input is wrong; field names it.
429 rate_limited Too many refunds and cancellations, dispute answers or payout requests. Retry-After says when to try again.
502 refund_failed, payment_provider_error The payment provider refused a refund, or a dispute's evidence or acceptance.

Errors from the organization API key itself, such as a key that is missing, revoked or used from the wrong address, are on Create organization API keys.

Your storefront can list the published products through the Store API with a test publishable key, and a test order placed there appears in GET /commerce/orders?livemode=false. Once we approve the merchant profile and switch the store to live, live keys take real orders, and each one reaches your ledger.

404 with commerce_not_enabled
Commerce is not turned on for the organization, or Coritan disabled the store. Ask support.
403 with scope_required
The key lacks the scope in the answer's scope. Keys cannot change, so create one with the scopes it needs and revoke the old one.
403 with role_required
The member's role is below the lowest role for the route, as the answer's permission shows. Scopes and roles lists them.
403 with people_only
The merchant profile routes and payout requests refuse every key. Call them with an owner's or admin's token.
409 with market_not_enabled
A country in the region is one where Coritan does not sell yet. Take it out of the region.
409 with country_in_other_region
The country is already in another region. Take it out of that region first.
409 with default_region or default_channel
You tried to disable or delete the default region or sales channel. Make another one the default first.
409 with handle_taken or sku_taken
Another product has that handle or SKU. Choose another, or change the product that has it.
409 with import_in_progress
The store runs one import at a time, and another is queued or running. Follow it with GET /commerce/imports/{import_id}, using the import_id in the answer, and send the file again once it has finished.
409 with order_on_hold
The order is on risk hold. Release the hold before you fulfil it.
409 with already_shipped
Part of the order has shipped, so it cannot be cancelled or have its address changed. Refund the items instead.
409 with fulfillment_started
A fulfilment provider already has the order with its address. Ask the provider to change the address.
409 with quantity_exceeds_unfulfilled
You named more units than there are: other fulfilments hold them, or the fulfilment you are shipping part of holds fewer. The answer's unfulfilled says how many there are. To move units between fulfilments, cancel one first.
409 with refund_in_progress
An earlier refund is still waiting for the payment provider. Read the order again in a few minutes.
409 with gift_card_used
The order sold a gift card that has been spent, so the order cannot be refunded in full. Refund the other items.
409 with promotion_in_use
An order used the promotion. Set its status to disabled instead of deleting it.
409 with limit_reached
The store has as many of these as it can hold. Delete one you no longer need.
422 listing missing
The merchant profile lacks what review needs. Fill in the fields in missing and submit it again.
429 with rate_limited
The organization made more than 30 refunds and cancellations in a minute, or went over the limit for dispute answers or payout requests. Wait for the time in Retry-After.
A fulfilment stays requested or goes to exception
Read GET /commerce/fulfillment-providers/{provider_id}/deliveries to see how the endpoint answered, fix the endpoint, then dispatch the fulfilment again.

API operations on this page

MethodPathWhat it does
GET/api/v1/orgs/{org_slug}/commerce/balanceGet balance
GET/api/v1/orgs/{org_slug}/commerce/categoriesList categories
POST/api/v1/orgs/{org_slug}/commerce/categoriesA category; isinternal keeps it out of the Store API
GET/api/v1/orgs/{org_slug}/commerce/categories/{category_id}Get category
PATCH/api/v1/orgs/{org_slug}/commerce/categories/{category_id}Moving a category under itself or one of its children is a 409
DELETE/api/v1/orgs/{org_slug}/commerce/categories/{category_id}A category with children answers 409; move or delete them first
GET/api/v1/orgs/{org_slug}/commerce/collectionsList collections
POST/api/v1/orgs/{org_slug}/commerce/collectionsCreate collection
GET/api/v1/orgs/{org_slug}/commerce/collections/{collection_id}The collection and its product ids, in the collection's order
PATCH/api/v1/orgs/{org_slug}/commerce/collections/{collection_id}Turning a manual collection smart drops the products added by hand
DELETE/api/v1/orgs/{org_slug}/commerce/collections/{collection_id}The collection goes; its products stay
PUT/api/v1/orgs/{org_slug}/commerce/collections/{collection_id}/productsA manual collection's products become exactly these, in this order
GET/api/v1/orgs/{org_slug}/commerce/customer-groupsEvery group, by name, with its customer count
POST/api/v1/orgs/{org_slug}/commerce/customer-groupsCreate group
GET/api/v1/orgs/{org_slug}/commerce/customer-groups/{group_id}The group; its customers are GET /commerce/customers?groupid=
PATCH/api/v1/orgs/{org_slug}/commerce/customer-groups/{group_id}Update group
DELETE/api/v1/orgs/{org_slug}/commerce/customer-groups/{group_id}Delete group
PUT/api/v1/orgs/{org_slug}/commerce/customer-groups/{group_id}/customersSet group customers
GET/api/v1/orgs/{org_slug}/commerce/customersNewest first, with each customer's order count, spend and groups
GET/api/v1/orgs/{org_slug}/commerce/customers/{customer_id}The customer, their saved addresses, note, tags, groups and spend
PATCH/api/v1/orgs/{org_slug}/commerce/customers/{customer_id}Note, tags, tax exemption, VAT id and its check; acceptsmarketing takes only false
GET/api/v1/orgs/{org_slug}/commerce/eventsWhat the store announced, newest first: the log behind its webhooks
GET/api/v1/orgs/{org_slug}/commerce/fulfillment-providersList providers
POST/api/v1/orgs/{org_slug}/commerce/fulfillment-providersA provider
GET/api/v1/orgs/{org_slug}/commerce/fulfillment-providers/{provider_id}Get provider
PATCH/api/v1/orgs/{org_slug}/commerce/fulfillment-providers/{provider_id}Rename, enable or disable, move the endpoint, change settings
DELETE/api/v1/orgs/{org_slug}/commerce/fulfillment-providers/{provider_id}Refused while a fulfillment that has not shipped uses it; disable it instead
GET/api/v1/orgs/{org_slug}/commerce/fulfillment-providers/{provider_id}/deliveriesRequests sent (or waiting to be sent) to the provider, newest first
GET/api/v1/orgs/{org_slug}/commerce/fulfillment-providers/{provider_id}/eventsEvents the provider sent, newest first, with what became of each
POST/api/v1/orgs/{org_slug}/commerce/fulfillment-providers/{provider_id}/eventsOne event from a fulfillment provider
POST/api/v1/orgs/{org_slug}/commerce/fulfillment-providers/{provider_id}/rotate-secretsNew outbound and inbound secrets, returned this once
POST/api/v1/orgs/{org_slug}/commerce/fulfillment-providers/{provider_id}/testSend a signed ping and report what the endpoint answered
GET/api/v1/orgs/{org_slug}/commerce/gift-cardsNewest first
POST/api/v1/orgs/{org_slug}/commerce/gift-cardsIssue a card
GET/api/v1/orgs/{org_slug}/commerce/gift-cards/{gift_card_id}The card and its latest transactions, newest first
PATCH/api/v1/orgs/{org_slug}/commerce/gift-cards/{gift_card_id}Disable or re-enable a card, move its expiry, correct its recipient
POST/api/v1/orgs/{org_slug}/commerce/gift-cards/{gift_card_id}/adjustMove the balance by a signed amount; 409 giftcardbalancenegative below zero
GET/api/v1/orgs/{org_slug}/commerce/importsThe store's imports, newest first, without their error lists; count counts every match
POST/api/v1/orgs/{org_slug}/commerce/importsQueue a Shopify product export for import and answer 202 with it
POST/api/v1/orgs/{org_slug}/commerce/imports/shopify-productsQueue a Shopify product export for import and answer 202 with it
GET/api/v1/orgs/{org_slug}/commerce/imports/{import_id}One import: its status, progress, counts and every row it could not use
POST/api/v1/orgs/{org_slug}/commerce/inventory/levels/batchSet levels by sku
GET/api/v1/orgs/{org_slug}/commerce/inventory-itemsInventory items by SKU, with their levels
GET/api/v1/orgs/{org_slug}/commerce/inventory-items/{item_id}Get item
PATCH/api/v1/orgs/{org_slug}/commerce/inventory-items/{item_id}What customs and shipping need to know about the item
PUT/api/v1/orgs/{org_slug}/commerce/inventory-items/{item_id}/levels/{location_id}Set level
GET/api/v1/orgs/{org_slug}/commerce/ledgerEvery movement of the store's money, newest first
GET/api/v1/orgs/{org_slug}/commerce/merchant-profileGet profile
PUT/api/v1/orgs/{org_slug}/commerce/merchant-profileUpdate profile
POST/api/v1/orgs/{org_slug}/commerce/merchant-profile/submitSend the profile to platform review
GET/api/v1/orgs/{org_slug}/commerce/ordersNewest first; count is every order the filters match
GET/api/v1/orgs/{org_slug}/commerce/orders/{order_id}The order with its items, payments, refunds, fulfillments, documents and timeline
PATCH/api/v1/orgs/{org_slug}/commerce/orders/{order_id}Update order
POST/api/v1/orgs/{org_slug}/commerce/orders/{order_id}/cancelRefund what is left, give the stock back and end the order
GET/api/v1/orgs/{org_slug}/commerce/orders/{order_id}/documentsThe invoice and credit notes, with the issuing entity's details as issued
GET/api/v1/orgs/{org_slug}/commerce/orders/{order_id}/fulfillmentsThe order's fulfillments with their 3PL requests, and each item's progress
POST/api/v1/orgs/{org_slug}/commerce/orders/{order_id}/fulfillmentsFulfill chosen units
POST/api/v1/orgs/{org_slug}/commerce/orders/{order_id}/fulfillments/{fulfillment_id}/cancelCancel before it ships (409 alreadyshipped after)
POST/api/v1/orgs/{org_slug}/commerce/orders/{order_id}/fulfillments/{fulfillment_id}/deliverDelivered
POST/api/v1/orgs/{org_slug}/commerce/orders/{order_id}/fulfillments/{fulfillment_id}/dispatchDispatch fulfillment
POST/api/v1/orgs/{org_slug}/commerce/orders/{order_id}/fulfillments/{fulfillment_id}/shipShipped, with tracking
POST/api/v1/orgs/{org_slug}/commerce/orders/{order_id}/notesA note on the timeline; customervisible shows it on the shopper's order page
POST/api/v1/orgs/{org_slug}/commerce/orders/{order_id}/refundsRefund an amount, or items and shipping, never more than is left
GET/api/v1/orgs/{org_slug}/commerce/price-listsList price lists
POST/api/v1/orgs/{org_slug}/commerce/price-listsCreate price list
GET/api/v1/orgs/{org_slug}/commerce/price-lists/{price_list_id}Get price list
PATCH/api/v1/orgs/{org_slug}/commerce/price-lists/{price_list_id}Fields change when named; prices replaces the list's prices
DELETE/api/v1/orgs/{org_slug}/commerce/price-lists/{price_list_id}The list and its prices go; base prices are untouched
PUT/api/v1/orgs/{org_slug}/commerce/price-lists/{price_list_id}/pricesReplace price list prices
GET/api/v1/orgs/{org_slug}/commerce/product-tagsThe tags on live products, most used first: what a tag picker suggests
GET/api/v1/orgs/{org_slug}/commerce/productsProducts, most recently changed first
POST/api/v1/orgs/{org_slug}/commerce/productsCreate product
GET/api/v1/orgs/{org_slug}/commerce/products/{product_id}Get product
PATCH/api/v1/orgs/{org_slug}/commerce/products/{product_id}Fields change when named
DELETE/api/v1/orgs/{org_slug}/commerce/products/{product_id}Archived and gone from every list and the Store API
POST/api/v1/orgs/{org_slug}/commerce/products/{product_id}/imagesAn https image
PATCH/api/v1/orgs/{org_slug}/commerce/products/{product_id}/images/{image_id}Update image
DELETE/api/v1/orgs/{org_slug}/commerce/products/{product_id}/images/{image_id}If it was the thumbnail, the next image takes its place
POST/api/v1/orgs/{org_slug}/commerce/products/{product_id}/optionsAn option such as Size
PATCH/api/v1/orgs/{org_slug}/commerce/products/{product_id}/options/{option_id}Update option
DELETE/api/v1/orgs/{org_slug}/commerce/products/{product_id}/options/{option_id}Delete option
POST/api/v1/orgs/{org_slug}/commerce/products/{product_id}/variantsCreate variant
PATCH/api/v1/orgs/{org_slug}/commerce/products/{product_id}/variants/{variant_id}Fields change when named; prices replaces the variant's base prices
DELETE/api/v1/orgs/{org_slug}/commerce/products/{product_id}/variants/{variant_id}Gone from the catalog; orders keep naming it, and its SKU is free
PUT/api/v1/orgs/{org_slug}/commerce/products/{product_id}/variants/{variant_id}/pricesReplace variant prices
GET/api/v1/orgs/{org_slug}/commerce/promotionsNewest first
POST/api/v1/orgs/{org_slug}/commerce/promotionsA code (or an automatic promotion)
GET/api/v1/orgs/{org_slug}/commerce/promotions/{promotion_id}The promotion and what it has given, per currency
PATCH/api/v1/orgs/{org_slug}/commerce/promotions/{promotion_id}Update promotion
DELETE/api/v1/orgs/{org_slug}/commerce/promotions/{promotion_id}A promotion no order used
GET/api/v1/orgs/{org_slug}/commerce/publishable-keysList keys
POST/api/v1/orgs/{org_slug}/commerce/publishable-keysA key for a storefront
DELETE/api/v1/orgs/{org_slug}/commerce/publishable-keys/{key_id}Revoke key
GET/api/v1/orgs/{org_slug}/commerce/regionsEvery region of the store, disabled ones included
POST/api/v1/orgs/{org_slug}/commerce/regionsA region
GET/api/v1/orgs/{org_slug}/commerce/regions/{region_id}Get region
PATCH/api/v1/orgs/{org_slug}/commerce/regions/{region_id}Fields change when named; countries replaces the region's
DELETE/api/v1/orgs/{org_slug}/commerce/regions/{region_id}The region, its countries and the prices set for it go
GET/api/v1/orgs/{org_slug}/commerce/sales-channelsList channels
POST/api/v1/orgs/{org_slug}/commerce/sales-channelsCreate channel
PATCH/api/v1/orgs/{org_slug}/commerce/sales-channels/{channel_id}Update channel
DELETE/api/v1/orgs/{org_slug}/commerce/sales-channels/{channel_id}Delete channel
GET/api/v1/orgs/{org_slug}/commerce/sales-channels/{channel_id}/stock-locationsThe locations a channel sells from
PUT/api/v1/orgs/{org_slug}/commerce/sales-channels/{channel_id}/stock-locationsThe channel sells from exactly these locations; an empty list means every enabled location
GET/api/v1/orgs/{org_slug}/commerce/shipping-optionsList options
POST/api/v1/orgs/{org_slug}/commerce/shipping-optionsAn option in a zone for a profile (the default one when profileid is left out)
GET/api/v1/orgs/{org_slug}/commerce/shipping-options/{option_id}Get option
PATCH/api/v1/orgs/{org_slug}/commerce/shipping-options/{option_id}Update option
DELETE/api/v1/orgs/{org_slug}/commerce/shipping-options/{option_id}Carts that chose it choose again; orders keep the name and amount they were charged
GET/api/v1/orgs/{org_slug}/commerce/shipping-profilesEvery profile, the default first
POST/api/v1/orgs/{org_slug}/commerce/shipping-profilesCreate profile
PATCH/api/v1/orgs/{org_slug}/commerce/shipping-profiles/{profile_id}Update profile
DELETE/api/v1/orgs/{org_slug}/commerce/shipping-profiles/{profile_id}A profile no product and no option uses
GET/api/v1/orgs/{org_slug}/commerce/shipping-zonesEvery zone, with how many options each holds
POST/api/v1/orgs/{org_slug}/commerce/shipping-zonesCreate zone
GET/api/v1/orgs/{org_slug}/commerce/shipping-zones/{zone_id}Get zone
PATCH/api/v1/orgs/{org_slug}/commerce/shipping-zones/{zone_id}Update zone
DELETE/api/v1/orgs/{org_slug}/commerce/shipping-zones/{zone_id}The zone and its options
GET/api/v1/orgs/{org_slug}/commerce/stock-locationsEvery location, the default first, with the channels that sell from it
POST/api/v1/orgs/{org_slug}/commerce/stock-locationsA warehouse, shop or 3PL site
GET/api/v1/orgs/{org_slug}/commerce/stock-locations/{location_id}Get location
PATCH/api/v1/orgs/{org_slug}/commerce/stock-locations/{location_id}Isdefault: true moves the default here
DELETE/api/v1/orgs/{org_slug}/commerce/stock-locations/{location_id}Delete location
GET/api/v1/orgs/{org_slug}/commerce/storeThe store, its sales channels, and where its merchant review stands
PATCH/api/v1/orgs/{org_slug}/commerce/storeUpdate store