Create a Product and Complete Checkout
This guide shows a tested local end-to-end flow for Shopware development:
- Create a category and product with the Admin API
- Read the product with the Store API
- Add the product to a cart
- Register a customer in the current Store API context
- Place an order
- Handle payment if needed
It is written as a golden path for local development on http://127.0.0.1:8000. Troubleshooting tips appear below.
What to expect in local development
A few details matter a lot in local setups:
- Store API requests use
sw-access-key, notsw-access-token - On this setup,
/store-api/contextis called withGET - Store API context tokens are ephemeral and may expire during longer debugging sessions
registerorloginmay return a newSw-Context-Token- If the context changes, your cart may no longer contain the items you added earlier
- Product creation requires a price in the system default currency
- Customer registration requires real IDs such as
salutationIdandcountryId
Because of that, the safest approach is to follow one known good sequence from start to finish.
Before you start
Make sure your local Shopware instance is running:
- Storefront:
http://127.0.0.1:8000 - Administration:
http://127.0.0.1:8000/admin
You also need:
curljq- An admin user, for example
admin / shopwarein a local default setup - A Store API sales channel access key
Step 1: Get an Admin API token
For local development, you can use the Administration password grant shortcut:
ADMIN_TOKEN=$(curl -s -X POST "http://127.0.0.1:8000/api/oauth/token" \
-H "Content-Type: application/json" \
-d '{
"grant_type": "password",
"client_id": "administration",
"scopes": "write",
"username": "admin",
"password": "shopware"
}' | jq -r '.access_token')
printf '%s\n' "$ADMIN_TOKEN"Step 2: Inspect your local API schemas
Use the generated schemas for two different questions:
- OpenAPI spec: Which endpoint should I call, and what does the payload look like?
- Entity schema: Which fields and associations exist on product, category, order, and other entities?
Download the Admin API schemas:
curl -s "http://127.0.0.1:8000/api/_info/openapi3.json" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-o openapi.jsonDownload the entity schema as well:
curl -s "http://127.0.0.1:8000/api/_info/open-api-schema.json" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-o entity-schema.jsonQuick checks:
ls -lh openapi.json entity-schema.json
head -n 5 openapi.json
head -n 5 entity-schema.jsonYou can do the same for the Store API later with /store-api/_info/openapi3.json.
Step 3: Look up the IDs you need before creating products
A product usually needs related IDs, such as:
taxIdsalesChannelIdcurrencyId
Use Admin API search endpoints for this.
Find a tax ID
curl -s -X POST "http://127.0.0.1:8000/api/search/tax" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"limit": 10,
"sort": [{ "field": "name", "order": "ASC" }]
}' | jqChoose the tax rate you want to use — for example, the standard rate.
Find a sales channel ID and Store API access key
curl -s -X POST "http://127.0.0.1:8000/api/search/sales-channel" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"limit": 10,
"includes": {
"sales_channel": ["id", "name", "accessKey"]
}
}' | jqPick the sales channel you want to test against — for example, Storefront.
Find a currency ID
Use the system default currency, because product writes require a price in the default currency:
curl -s -X POST "http://127.0.0.1:8000/api/search/currency" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"limit": 10,
"includes": {
"currency": ["id", "name", "isoCode", "isSystemDefault"]
}
}' | jqSave the IDs you want to use
TAX_ID="<example string>"
SALES_CHANNEL_ID="<example string>"
CURRENCY_ID="<example string>"
STORE_API_ACCESS_KEY="<example string>"Step 4: Create a category with the Admin API
CATEGORY_ID=$(uuidgen | tr '[:upper:]' '[:lower:]' | tr -d '-')
curl -s -X POST "http://127.0.0.1:8000/api/category" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"id\": \"$CATEGORY_ID\",
\"name\": \"My Example Category\",
\"active\": true
}"Verify it:
curl -s -X POST "http://127.0.0.1:8000/api/search/category" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"ids\": [\"$CATEGORY_ID\"],
\"includes\": {
\"category\": [\"id\", \"name\", \"active\"]
}
}" | jqThe ids and includes criteria are standard search-criteria features.
Step 5: Create a product with the Admin API
PRODUCT_ID=$(uuidgen | tr '[:upper:]' '[:lower:]' | tr -d '-')
PRODUCT_NUMBER="MyExample-001"
curl -s -X POST "http://127.0.0.1:8000/api/product" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"id\": \"$PRODUCT_ID\",
\"name\": \"My Example Product\",
\"productNumber\": \"$PRODUCT_NUMBER\",
\"stock\": 10,
\"active\": true,
\"taxId\": \"$TAX_ID\",
\"price\": [
{
\"currencyId\": \"$CURRENCY_ID\",
\"gross\": 19.99,
\"net\": 16.80,
\"linked\": true
}
],
\"visibilities\": [
{
\"salesChannelId\": \"$SALES_CHANNEL_ID\",
\"visibility\": 30
}
],
\"categories\": [
{ \"id\": \"$CATEGORY_ID\" }
]
}"About visibilities
Use visibilities to assign a product to one or more sales channels and define how visible it should be in each channel.
Each visibility entry contains:
{
"salesChannelId": "<sales-channel-id>",
"visibility": 30
}visibility is required because product availability is resolved per sales channel. A product is only considered available in a sales-channel context when it has a matching visibility entry for that salesChannelId.
Allowed values:
10(VISIBILITY_LINK): hide in listings and search20(VISIBILITY_SEARCH): hide in listings30(VISIBILITY_ALL): visible everywhere
Verify it:
curl -s -X POST "http://127.0.0.1:8000/api/search/product" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-H "sw-inheritance: 1" \
-d "{
\"ids\": [\"$PRODUCT_ID\"],
\"associations\": {
\"categories\": {}
},
\"includes\": {
\"product\": [\"id\", \"name\", \"productNumber\", \"active\", \"translated\", \"categories\"],
\"category\": [\"id\", \"name\"]
}
}" | jqThe sw-inheritance header tells the API to consider parent-child inheritance when reading products and variants.
Step 6: Create a Store API context
The Store API uses two important headers:
sw-access-key: authenticates against the Store API sales channelsw-context-token: identifies the shopper context
Get a fresh Store API context:
curl -i "http://127.0.0.1:8000/store-api/context" \
-H "sw-access-key: $STORE_API_ACCESS_KEY"Look for the Sw-Context-Token response header and store its value:
STORE_CONTEXT_TOKEN="REPLACE_ME"
echo "$STORE_CONTEXT_TOKEN"You can also extract it automatically:
STORE_CONTEXT_TOKEN=$(curl -si "http://127.0.0.1:8000/store-api/context" \
-H "sw-access-key: $STORE_API_ACCESS_KEY" \
| tr -d '\r' | awk -F': ' 'tolower($1)=="sw-context-token" {print $2}')
echo "$STORE_CONTEXT_TOKEN"Step 7: Read the product with the Store API
Use a Store API search request with search criteria. The following example searches by term and sorts by name:
curl -s -X POST "http://127.0.0.1:8000/store-api/search" \
-H "sw-access-key: $STORE_API_ACCESS_KEY" \
-H "sw-context-token: $STORE_CONTEXT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"term": "My Example Product",
"limit": 5,
"sort": [
{ "field": "name", "order": "ASC", "naturalSorting": true }
],
"includes": {
"product": ["id", "name", "translated", "calculatedPrice"]
}
}' | jqThe following example shows how to find the product you created in a storefront-style listing:
curl -s -X POST "http://127.0.0.1:8000/store-api/search" \
-H "sw-access-key: $STORE_API_ACCESS_KEY" \
-H "sw-context-token: $STORE_CONTEXT_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"filter\": [
{ \"type\": \"equals\", \"field\": \"active\", \"value\": true },
{ \"type\": \"equals\", \"field\": \"productNumber\", \"value\": \"$PRODUCT_NUMBER\" }
],
\"sort\": [
{ \"field\": \"name\", \"order\": \"ASC\" }
],
\"page\": 1,
\"limit\": 10,
\"includes\": {
\"product\": [\"id\", \"name\", \"productNumber\", \"translated\", \"calculatedPrice\"]
}
}" | jqUseful search-criteria options:
includesrestricts the response to the fields you needpageandlimitcontrol paginationfilterapplies exact or nested filteringtermperforms a weighted text searchsortcontrols ordering
If your project uses a different Store API search/listing endpoint, confirm the exact path in your local store-api/_info/openapi3.json.
Step 8: Add the product to the cart
Use the product ID from the previous step:
curl -s -X POST "http://127.0.0.1:8000/store-api/checkout/cart/line-item" \
-H "sw-access-key: $STORE_API_ACCESS_KEY" \
-H "sw-context-token: $STORE_CONTEXT_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"items\": [
{
\"id\": \"$PRODUCT_ID\",
\"referencedId\": \"$PRODUCT_ID\",
\"type\": \"product\",
\"quantity\": 1
}
]
}" | jqVerify the cart:
curl -s -X GET "http://127.0.0.1:8000/store-api/checkout/cart" \
-H "sw-access-key: $STORE_API_ACCESS_KEY" \
-H "sw-context-token: $STORE_CONTEXT_TOKEN" | jqIf your local schema shows slightly different cart payload requirements, trust your local store-api OpenAPI spec.
Step 9: Prepare checkout state
Before placing an order, you typically need:
- a customer in the current Store API context
- active billing and shipping addresses
- a shipping method
- a payment method
You can inspect your local Store API reference in the browser at http://127.0.0.1:8000/store-api/_info/stoplightio.html, or inspect the raw schema:
curl -s "http://127.0.0.1:8000/store-api/_info/openapi3.json" -o store-openapi.json
jq -r '.paths | keys[]' store-openapi.json | grep -E 'checkout|account|address|payment|shipping|order|context'Typical relevant endpoints include:
/account/register/account/login/account/address/context/payment-method/shipping-method/checkout/cart/checkout/order/handle-payment
Rule of thumb:
- Admin API: create and manage data
- Store API: act like a shopper
Step 10: Register or log in as a customer, then place the order
On a typical local setup, POST /store-api/checkout/order requires a logged-in customer. If you call it with an anonymous context, Shopware returns Customer is not logged in. First, register a customer or log in within the current Store API context.
10.1 Fetch salutationId and countryId
For registration, fetch real IDs for:
salutationIdfrom/store-api/salutationcountryIdfrom/store-api/country
For example:
COUNTRY_ID="019d2b446e29724fa4715c70c9c3eae1"
SALUTATION_ID="019d2b446e2573ba9a3ea2d002050cbe"10.2 Register the customer
curl -i -X POST "http://127.0.0.1:8000/store-api/account/register" \
-H "sw-access-key: $STORE_API_ACCESS_KEY" \
-H "sw-context-token: $STORE_CONTEXT_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"salutationId\": \"$SALUTATION_ID\",
\"firstName\": \"FirstName\",
\"lastName\": \"LastName\",
\"email\": \"name-test@example.com\",
\"password\": \"shopware123!\",
\"acceptedDataProtection\": true,
\"storefrontUrl\": \"http://127.0.0.1:8000\",
\"billingAddress\": {
\"firstName\": \"FirstName\",
\"lastName\": \"LastName\",
\"street\": \"Teststr. 1\",
\"zipcode\": \"12345\",
\"city\": \"Test City\",
\"countryId\": \"$COUNTRY_ID\"
}
}"Use the Sw-Context-Token value from the response for subsequent requests:
STORE_CONTEXT_TOKEN="REPLACE_ME_WITH_NEW_TOKEN"10.3 Re-add the product if the context token changed
After /store-api/account/register or /store-api/account/login, Shopware may return a new Sw-Context-Token. If the token changes, add the product to the cart again in that new context before calling /store-api/checkout/order.
curl -s -X POST "http://127.0.0.1:8000/store-api/checkout/cart/line-item" \
-H "sw-access-key: $STORE_API_ACCESS_KEY" \
-H "sw-context-token: $STORE_CONTEXT_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"items\": [
{
\"id\": \"$PRODUCT_ID\",
\"referencedId\": \"$PRODUCT_ID\",
\"type\": \"product\",
\"quantity\": 1
}
]
}" | jqVerify that the cart is not empty:
curl -s "http://127.0.0.1:8000/store-api/checkout/cart" \
-H "sw-access-key: $STORE_API_ACCESS_KEY" \
-H "sw-context-token: $STORE_CONTEXT_TOKEN" | jq10.4 Place the order
Once the Store API context has:
- A logged-in customer
- A cart with your product
- Valid billing and shipping data
You can usually place the order directly on a local default setup. If your instance requires an explicit payment or shipping selection first, inspect /payment-method, /shipping-method, and /context.
Create the order:
curl -s -X POST "http://127.0.0.1:8000/store-api/checkout/order" \
-H "sw-access-key: $STORE_API_ACCESS_KEY" \
-H "sw-context-token: $STORE_CONTEXT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"customerComment": "Test order from local API walkthrough"
}' | jqIf the cart is empty, Shopware returns the message: Cart is empty.
In that case, add the product to the cart again in the current context, then retry the order request.
10.5 Handle payment if required
Some payment methods require an extra payment step after order creation:
curl -s -X POST "http://127.0.0.1:8000/store-api/handle-payment" \
-H "sw-access-key: $STORE_API_ACCESS_KEY" \
-H "sw-context-token: $STORE_CONTEXT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"orderId": "REPLACE_ME",
"finishUrl": "http://127.0.0.1:8000/checkout/finish",
"errorUrl": "http://127.0.0.1:8000/checkout/confirm"
}' | jqIf /store-api/handle-payment returns "redirectUrl": null, the selected payment method does not require an additional redirect-based flow.
Troubleshooting
Schema endpoints return 500 or missing-table errors
Your database may not be initialized correctly. Re-run installation and setup.
Product does not show up in the Store API
Check all of the following:
- The product is
active - The product has a valid
price - The product has
visibilitiesfor your sales channel - You are using the correct Store API access key
- Your Storefront sales channel domain matches your local URL
Which headers matter most?
For this walkthrough:
- Admin API:
Authorization: Bearer $ADMIN_TOKEN, optionallysw-language-id,sw-version-id,sw-inheritance,sw-currency-iddepending on your use case - Store API:
sw-access-key,sw-context-token