Shopify
Shopify: a company's online shop connected with an app of its own; every 5 minutes the shop's products come in as the company's products and its orders as sales orders, invoiced when sent and paid to the payment provider's balance; a drop shipper's suppliers get a purchase order for what they ship.
| Operation | Summary | Scope | Idempotency-Key |
|---|---|---|---|
GET /v1/shopify |
Get Shopify | admin |
|
POST /v1/shopify/authorize |
Start connecting a Shopify shop on its consent page | admin |
|
POST /v1/shopify/connections |
Connect a Shopify shop | admin |
|
POST /v1/shopify/connections/consent |
Connect a Shopify shop with its consent page's answer | admin |
|
DELETE /v1/shopify/connections/{shopify_connection_id} |
Disconnect Shopify | admin |
|
GET /v1/shopify/connections/{shopify_connection_id}/orders |
List the shop's orders | admin |
|
GET /v1/shopify/connections/{shopify_connection_id}/payouts |
List the shop's payouts | admin |
|
GET /v1/shopify/connections/{shopify_connection_id}/products |
List the shop's products | admin |
|
GET /v1/shopify/connections/{shopify_connection_id}/suppliers |
List the shop's vendors | admin |
|
PUT /v1/shopify/connections/{shopify_connection_id}/suppliers |
Set who ships a shop vendor's goods | admin |
|
POST /v1/shopify/connections/{shopify_connection_id}/sync |
Read the shop now | admin |
GET /v1/shopify
Get Shopify
Whether a shop can be connected here, the permissions its app needs, and each company connected: the shop, its currency, how far its products were read and the last error.
| Required scope | admin (admin implies every scope) |
| Operation id | get_shopify_v1_shopify_get |
Example request
curl "$ERP_URL/v1/shopify" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | ShopifySetup |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"available": true,
"scopes": [
"string"
],
"redirect_uri": "string",
"connections": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity": "string",
"shop_domain": "string",
"shop_name": "string",
"admin_url": "string",
"currency": "string",
"taxes_included": true,
"method": "app",
"status": "active",
"products_read_to": "2026-09-21T09:30:00Z",
"last_sync_at": "2026-09-21T09:30:00Z",
"last_error": "string",
"products": 1,
"orders_read_to": "2026-09-21T09:30:00Z",
"orders": 1,
"orders_open": 1,
"orders_with_problems": 1,
"linked_by": {},
"linked_at": "2026-09-21T09:30:00Z"
}
]
}
POST /v1/shopify/authorize
Start connecting a Shopify shop on its consent page
The shop's consent page for the company's own app: open url; the shop sends the browser
back to redirect_uri (which the app must allow) with a query string for
POST /v1/shopify/connections/consent, within 15 minutes. For a shop the app's client
credentials cannot reach. 409 in a test copy or when this system has no address to come back
to.
| Required scope | admin (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | authorize_v1_shopify_authorize_post |
Request body
application/json (required): ShopifyAuthorize
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | Code (or id) of the company. |
shop_domain |
string | yes | The shop's own address. (pattern ^[a-z0-9][a-z0-9-]{0,59}\.myshopify\.com$) |
client_id |
string | yes | The client id of the company's app. (min length 10; max length 200) |
client_secret |
string | yes | That app's client secret: it travels sealed in state and checks the shop's answer; never shown again. (min length 10; max length 500) |
Example request
curl -X POST "$ERP_URL/v1/shopify/authorize" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"client_id": "3f5e8a1b9c2d4e6f7a8b9c0d1e2f3a4b",
"client_secret": "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",
"entity": "CH01",
"shop_domain": "alpine-goods.myshopify.com"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | ShopifyAuthorizeOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"url": "string",
"redirect_uri": "string"
}
POST /v1/shopify/connections
Connect a Shopify shop
Connect a company's shop with an app of the company's own: its client id and secret (an
app made in Shopify's Dev Dashboard and installed on a shop of the app's own organization)
or a custom app's access token; for any other shop use its consent page
(POST /v1/shopify/authorize). The shop is asked for its name, currency and whether its
prices include the tax (proving the app). From then on its products come in as products here
every 5 minutes (the first round reads them all), and the orders placed from then on as sales
orders. Connecting again replaces the secret. 409
in a test copy or when the company is connected to another shop; 422 when the shop refuses
the app.
| Required scope | admin (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | connect_v1_shopify_connections_post |
Request body
application/json (required): ShopifyConnectInput
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | Code (or id) of the company. |
shop_domain |
string | yes | The shop's own address, <name>.myshopify.com (in the shop's admin under Settings, Domains). (pattern ^[a-z0-9][a-z0-9-]{0,59}\.myshopify\.com$) |
client_id |
string or null | The client id of the company's app, made in Shopify's Dev Dashboard and installed on the shop; with client_secret. (min length 10; max length 200) |
|
client_secret |
string or null | That app's client secret. Kept sealed, never shown again. (min length 10; max length 500) | |
access_token |
string or null | Instead of an app's client id and secret: a custom app's Admin API access token (shpat_...). Kept sealed, never shown again. (min length 10; max length 500) |
Example request
curl -X POST "$ERP_URL/v1/shopify/connections" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"client_id": "3f5e8a1b9c2d4e6f7a8b9c0d1e2f3a4b",
"client_secret": "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",
"entity": "CH01",
"shop_domain": "alpine-goods.myshopify.com"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | ShopifyConnection |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity": "string",
"shop_domain": "string",
"shop_name": "string",
"admin_url": "string",
"currency": "string",
"taxes_included": true,
"method": "app",
"status": "active",
"products_read_to": "2026-09-21T09:30:00Z",
"last_sync_at": "2026-09-21T09:30:00Z",
"last_error": "string",
"products": 1,
"orders_read_to": "2026-09-21T09:30:00Z",
"orders": 1,
"orders_open": 1,
"orders_with_problems": 1,
"linked_by": {},
"linked_at": "2026-09-21T09:30:00Z"
}
POST /v1/shopify/connections/consent
Connect a Shopify shop with its consent page's answer
Finish connecting with the query string the shop's consent page sent the browser back with (only for whoever started it): Shopify's signature is checked with the app's secret and the code traded for the app's lasting token. 422 when the answer is not theirs, too old or not signed by Shopify, or the shop refuses the code; 409 as above.
| Required scope | admin (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | connect_consent_v1_shopify_connections_consent_post |
Request body
application/json (required): ShopifyCallbackInput
| Field | Type | Required | Description |
|---|---|---|---|
query |
string | yes | The query string the shop's consent page sent the browser back with, as it came (its signature covers every parameter). (min length 1; max length 4000) |
Example request
curl -X POST "$ERP_URL/v1/shopify/connections/consent" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "code=0907a61c0c8d55e99db179b68161bc00&hmac=700e2d…&shop=alpine-goods.myshopify.com&state=eyJvcmciOiI….Xq3v…×tamp=1793017200"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | ShopifyConnection |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity": "string",
"shop_domain": "string",
"shop_name": "string",
"admin_url": "string",
"currency": "string",
"taxes_included": true,
"method": "app",
"status": "active",
"products_read_to": "2026-09-21T09:30:00Z",
"last_sync_at": "2026-09-21T09:30:00Z",
"last_error": "string",
"products": 1,
"orders_read_to": "2026-09-21T09:30:00Z",
"orders": 1,
"orders_open": 1,
"orders_with_problems": 1,
"linked_by": {},
"linked_at": "2026-09-21T09:30:00Z"
}
DELETE /v1/shopify/connections/{shopify_connection_id}
Disconnect Shopify
Disconnect: nothing more is read and the secret is deleted (the app is the company's to uninstall in Shopify). The products stay, and so does which variant each is sold as, so connecting the same shop again makes no product twice.
| Required scope | admin (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | disconnect_v1_shopify_connections__shopify_connection_id__delete |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | shopify_connection_id |
string (uuid) | yes | Id of the connection (GET /v1/shopify). |
Example request
curl -X DELETE "$ERP_URL/v1/shopify/connections/7d0e1f2a-3b4c-4d5e-8f6a-7b8c9d0e1f2a" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 204 | No content | |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
GET /v1/shopify/connections/{shopify_connection_id}/orders
List the shop's orders
The shop's orders taken into the books (those placed after the shop was connected), each with its sales order, the invoice of each fulfilment and whether it is paid, and what keeps it from being done, in plain words.
| Required scope | admin (admin implies every scope) |
| Operation id | list_orders_v1_shopify_connections__shopify_connection_id__orders_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | shopify_connection_id |
string (uuid) | yes | Id of the connection (GET /v1/shopify). |
| query | state |
"cancelled" | "done" | "open" or null |
Only the orders in this state: open, done or cancelled. |
|
| query | problems |
boolean | Only the orders a step of which was refused. (default false) |
|
| query | limit |
integer | At most this many, the newest first. (min 1; max 1000; default 100) |
Example request
curl "$ERP_URL/v1/shopify/connections/7d0e1f2a-3b4c-4d5e-8f6a-7b8c9d0e1f2a/orders" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of ShopifyOrderOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
[
{
"order_id": 1,
"name": "string",
"placed_at": "2026-09-21T09:30:00Z",
"customer": "string",
"paid": true,
"charged": "string",
"sales_order_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"sales_order": "string",
"state": "cancelled",
"problem": "string",
"invoices": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": null,
"status": "string",
"total": "string",
"charged": "string",
"paid": true
}
],
"purchase_orders": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": "string",
"vendor": "string",
"status": "string"
}
],
"credit_notes": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"number": null,
"status": "string",
"total": "string",
"refunded": "string",
"paid": true
}
]
}
]
GET /v1/shopify/connections/{shopify_connection_id}/payouts
List the shop's payouts
The payouts of Shopify Payments read from the shop (those of the last 30 days each round, from the day the shop was connected): what each paid out to the bank, the fees and the chargebacks Shopify kept of it, the entry that booked them against the payment provider's balance, whether its bank line was posted to that balance, and what keeps it from being booked, in plain words.
| Required scope | admin (admin implies every scope) |
| Operation id | list_payouts_v1_shopify_connections__shopify_connection_id__payouts_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | shopify_connection_id |
string (uuid) | yes | Id of the connection (GET /v1/shopify). |
| query | limit |
integer | At most this many, the newest first. (min 1; max 1000; default 100) |
Example request
curl "$ERP_URL/v1/shopify/connections/7d0e1f2a-3b4c-4d5e-8f6a-7b8c9d0e1f2a/payouts" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of ShopifyPayoutOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
[
{
"payout_id": 1,
"issued_at": "2026-09-21T09:30:00Z",
"status": "string",
"currency": "string",
"net": "string",
"fees": "string",
"adjustments": "string",
"state": "booked",
"journal_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"journal_entry": "string",
"in_bank": true,
"problem": "string"
}
]
GET /v1/shopify/connections/{shopify_connection_id}/products
List the shop's products
The shop's variants sold here, each with the product it is sold as, its SKU, name, price and status as last read.
| Required scope | admin (admin implies every scope) |
| Operation id | list_variants_v1_shopify_connections__shopify_connection_id__products_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | shopify_connection_id |
string (uuid) | yes | Id of the connection (GET /v1/shopify). |
| query | limit |
integer | At most this many, by name. (min 1; max 1000; default 200) |
Example request
curl "$ERP_URL/v1/shopify/connections/7d0e1f2a-3b4c-4d5e-8f6a-7b8c9d0e1f2a/products" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of ShopifyVariantOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
[
{
"variant_id": 1,
"product_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"product": "string",
"sku": "string",
"name": "string",
"price": "string",
"status": "string",
"vendor": "string",
"cost": "string",
"read_at": "2026-09-21T09:30:00Z"
}
]
GET /v1/shopify/connections/{shopify_connection_id}/suppliers
List the shop's vendors
The vendors the shop's products name, by name, each with the company's vendor who ships its goods to the customers (drop shipping), or none when the company ships them itself.
| Required scope | admin (admin implies every scope) |
| Operation id | list_suppliers_v1_shopify_connections__shopify_connection_id__suppliers_get |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | shopify_connection_id |
string (uuid) | yes | Id of the connection (GET /v1/shopify). |
Example request
curl "$ERP_URL/v1/shopify/connections/7d0e1f2a-3b4c-4d5e-8f6a-7b8c9d0e1f2a/suppliers" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of ShopifySupplierOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
[
{
"shop_vendor": "string",
"variants": 1,
"without_cost": 1,
"vendor": "string",
"vendor_name": "string"
}
]
PUT /v1/shopify/connections/{shopify_connection_id}/suppliers
Set who ships a shop vendor's goods
Name the company's vendor who ships the goods of one of the shop's vendors straight to the
customers (drop shipping), or none (vendor null) when the company ships them itself. From
the next round, an order's lines of those goods raise a purchase order of that vendor at their
cost per item in the shop, sent to the customer's address; those already raised stay. 422
when the vendor is blocked or inactive.
| Required scope | admin (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | set_supplier_v1_shopify_connections__shopify_connection_id__suppliers_put |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | shopify_connection_id |
string (uuid) | yes | Id of the connection (GET /v1/shopify). |
Request body
application/json (required): ShopifySupplierInput
| Field | Type | Required | Description |
|---|---|---|---|
shop_vendor |
string | yes | The vendor the shop's products name. (min length 1; max length 255) |
vendor |
string or null | yes | Code (or id) of the company's active vendor who ships them to the customers; null when the company ships them itself. |
Example request
curl -X PUT "$ERP_URL/v1/shopify/connections/7d0e1f2a-3b4c-4d5e-8f6a-7b8c9d0e1f2a/suppliers" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"shop_vendor": "Alpine Outfitters",
"vendor": "V-00004"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | ShopifySupplierOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"shop_vendor": "string",
"variants": 1,
"without_cost": 1,
"vendor": "string",
"vendor_name": "string"
}
POST /v1/shopify/connections/{shopify_connection_id}/sync
Read the shop now
A round now (they run every 5 minutes): read the products that changed, make products of the new variants and take over new names, prices and whether the shop still sells each; then read the orders that changed, make a sales order of each new one and the purchase orders of its drop-shipped goods, invoice what was sent and pay the invoices of paid orders to the payment provider's balance; then read the payouts and book the fees and chargebacks of those on their way to the bank. 409 when the shop stopped accepting the app (connect again) or in a test copy.
| Required scope | admin (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | sync_connection_v1_shopify_connections__shopify_connection_id__sync_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | shopify_connection_id |
string (uuid) | yes | Id of the connection (GET /v1/shopify). |
Example request
curl -X POST "$ERP_URL/v1/shopify/connections/7d0e1f2a-3b4c-4d5e-8f6a-7b8c9d0e1f2a/sync" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | ShopifySyncOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 404 | The resource does not exist, or belongs to another organization (not_found). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"read": 1,
"new_products": 1,
"updated_products": 1,
"orders_read": 1,
"new_orders": 1,
"purchase_orders": 1,
"invoiced": 1,
"paid": 1,
"problems": 1,
"refunds": 1,
"payouts": 1,
"connection": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entity": "string",
"shop_domain": "string",
"shop_name": "string",
"admin_url": "string",
"currency": "string",
"taxes_included": true,
"method": "app",
"status": "active",
"products_read_to": "2026-09-21T09:30:00Z",
"last_sync_at": "2026-09-21T09:30:00Z",
"last_error": "string",
"products": 1,
"orders_read_to": "2026-09-21T09:30:00Z",
"orders": 1,
"orders_open": 1,
"orders_with_problems": 1,
"linked_by": {},
"linked_at": "2026-09-21T09:30:00Z"
}
}
Schemas
ShopifyAuthorize
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | Code (or id) of the company. |
shop_domain |
string | yes | The shop's own address. (pattern ^[a-z0-9][a-z0-9-]{0,59}\.myshopify\.com$) |
client_id |
string | yes | The client id of the company's app. (min length 10; max length 200) |
client_secret |
string | yes | That app's client secret: it travels sealed in state and checks the shop's answer; never shown again. (min length 10; max length 500) |
ShopifyAuthorizeOut
| Field | Type | Required | Description |
|---|---|---|---|
url |
string | yes | The shop's consent page: open it in the browser. |
redirect_uri |
string | yes | Where it sends the browser back. |
ShopifyCallbackInput
| Field | Type | Required | Description |
|---|---|---|---|
query |
string | yes | The query string the shop's consent page sent the browser back with, as it came (its signature covers every parameter). (min length 1; max length 4000) |
ShopifyConnectInput
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | Code (or id) of the company. |
shop_domain |
string | yes | The shop's own address, <name>.myshopify.com (in the shop's admin under Settings, Domains). (pattern ^[a-z0-9][a-z0-9-]{0,59}\.myshopify\.com$) |
client_id |
string or null | The client id of the company's app, made in Shopify's Dev Dashboard and installed on the shop; with client_secret. (min length 10; max length 200) |
|
client_secret |
string or null | That app's client secret. Kept sealed, never shown again. (min length 10; max length 500) | |
access_token |
string or null | Instead of an app's client id and secret: a custom app's Admin API access token (shpat_...). Kept sealed, never shown again. (min length 10; max length 500) |
ShopifyConnection
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the connection. |
entity |
string | yes | The company whose books the shop feeds. |
shop_domain |
string | yes | The shop's own address, <name>.myshopify.com. |
shop_name |
string | yes | The shop's name. |
admin_url |
string | yes | The shop's admin in Shopify. |
currency |
string | yes | The shop's currency. |
taxes_included |
boolean | yes | Whether the shop's prices include the tax; Stabiso's list prices are net, so they are worked out from them. |
method |
"app" | "consent" | "token" |
yes | app: with an app's client id and secret; consent: the app let in on the shop's consent page; token: with a custom app's access token. |
status |
"active" | "reconnect" |
yes | reconnect: the shop stopped accepting the app; connect again. |
products_read_to |
string (date-time) | yes | The products are read up to here (UTC). |
last_sync_at |
string (date-time) or null | yes | When the shop was last read (UTC). |
last_error |
string or null | yes | Why the last round failed, in plain words. |
products |
integer | yes | Variants of the shop sold as products here. |
orders_read_to |
string (date-time) | yes | The orders are read up to here (UTC); orders placed before the shop was connected are not taken. |
orders |
integer | yes | The shop's orders taken into the books. |
orders_open |
integer | yes | Of them, the ones not yet sent, invoiced and paid in full. |
orders_with_problems |
integer | yes | Of them, the ones a step of which was refused (GET .../orders, problem). |
linked_by |
object | yes | Who connected it. |
linked_at |
string (date-time) | yes | When it was connected (UTC). |
ShopifyOrderCreditNote
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the credit note (GET /v1/documents/{document_id}). |
number |
string or null | yes | Its number; null before it is posted. |
status |
string | yes | draft, pending_approval, posted, rejected or void. |
total |
string | yes | Its total, with Stabiso's VAT. |
refunded |
string | yes | What the shop refunded. |
paid |
boolean | yes | Whether the payment out of the payment provider's balance paid it. |
ShopifyOrderInvoice
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the invoice (GET /v1/documents/{document_id}). |
number |
string or null | yes | Its number; null before it is posted. |
status |
string | yes | draft, pending_approval, posted, rejected or void. |
total |
string | yes | Its total, with Stabiso's VAT. |
charged |
string | yes | What the shop charged for the same goods. |
paid |
boolean | yes | Whether the receipt to the payment provider's balance paid it. |
ShopifyOrderOut
| Field | Type | Required | Description |
|---|---|---|---|
order_id |
integer | yes | Shopify's id of the order. |
name |
string | yes | The order's name in the shop, e.g. #1001. |
placed_at |
string (date-time) | yes | When it was placed (UTC). |
customer |
string or null | yes | The buyer's name, or null for a guest; the customer is the sales order's. |
paid |
boolean | yes | Whether the customer paid it in the shop. |
charged |
string | yes | What the shop charged for it, in the shop's currency. |
sales_order_id |
string (uuid) or null | yes | Id of its sales order (GET /v1/sales-documents/{document}); null until made. |
sales_order |
string or null | yes | Number of its sales order. |
state |
"cancelled" | "done" | "open" |
yes | open: not yet sent, invoiced and paid in full; done; cancelled in the shop before anything was invoiced. |
problem |
string or null | yes | Why the last step on it was refused, in plain words; it is done again in the next round. |
invoices |
array of ShopifyOrderInvoice |
yes | The invoice of each fulfilment, the oldest first. |
purchase_orders |
array of ShopifyOrderPurchase |
yes | The purchase orders of its drop-shipped goods, one per supplier. |
credit_notes |
array of ShopifyOrderCreditNote |
yes | The credit note of each refund of goods sent, the oldest first; paid once paid out of the payment provider's balance. |
ShopifyOrderPurchase
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (uuid) | yes | Id of the purchase order (GET /v1/purchase-orders/{id}). |
number |
string | yes | Its number. |
vendor |
string | yes | Code of the supplier who ships the goods. |
status |
string | yes | Its status, e.g. pending_approval, open, closed. |
ShopifyPayoutOut
| Field | Type | Required | Description |
|---|---|---|---|
payout_id |
integer | yes | Shopify's id of the payout. |
issued_at |
string (date-time) | yes | When Shopify issued it (UTC). |
status |
string | yes | Its status in Shopify: SCHEDULED, IN_TRANSIT, PAID, FAILED or CANCELED. |
currency |
string | yes | The currency it is paid out in. |
net |
string | yes | What reaches the bank. |
fees |
string | yes | The fees Shopify kept of it. |
adjustments |
string | yes | What chargebacks and other adjustments took from the balance (negative) or gave back. |
state |
"booked" | "open" |
yes | booked once it is on its way to the bank and what it kept is booked against the payment provider's balance; else open. |
journal_entry_id |
string (uuid) or null | yes | Id of the entry that booked its fees and adjustments (GET /v1/journal-entries/{entry_id}); null when it kept nothing or is open. |
journal_entry |
string or null | yes | Number of that entry once posted. |
in_bank |
boolean | yes | Whether a bank statement line was posted as this payout to the balance. |
problem |
string or null | yes | Why it was not booked, in plain words; it is tried again in the next round. |
ShopifySetup
| Field | Type | Required | Description |
|---|---|---|---|
available |
boolean | yes | Whether a shop can be connected on this system (false: secrets could not be kept sealed). |
scopes |
array of string | yes | What the shop's app must be allowed (its Admin API access scopes). |
redirect_uri |
string or null | yes | Where the shop's consent page sends the browser back: add it to the app's allowed redirection URLs to connect on the consent page. |
connections |
array of ShopifyConnection |
yes | The companies connected. |
ShopifySupplierInput
| Field | Type | Required | Description |
|---|---|---|---|
shop_vendor |
string | yes | The vendor the shop's products name. (min length 1; max length 255) |
vendor |
string or null | yes | Code (or id) of the company's active vendor who ships them to the customers; null when the company ships them itself. |
ShopifySupplierOut
| Field | Type | Required | Description |
|---|---|---|---|
shop_vendor |
string | yes | A vendor the shop's products name. |
variants |
integer | yes | The variants of its products Stabiso sells. |
without_cost |
integer | yes | Those of them without a cost per item in the shop: a drop-shipped one raises no purchase order until it has one. |
vendor |
string or null | yes | Code of the company's vendor who ships its products (drop shipping); null when the company ships them itself. |
vendor_name |
string or null | yes | That vendor's name. |
ShopifySyncOut
| Field | Type | Required | Description |
|---|---|---|---|
read |
integer | yes | Variants read from the shop. |
new_products |
integer | yes | Products made of them, or found by their SKU. |
updated_products |
integer | yes | Products whose name, price or status changed. |
orders_read |
integer | yes | Orders read from the shop. |
new_orders |
integer | yes | Orders taken into the books. |
purchase_orders |
integer | yes | Purchase orders raised with drop shippers. |
invoiced |
integer | yes | Fulfilments invoiced. |
paid |
integer | yes | Invoices paid to the payment provider's balance. |
problems |
integer | yes | Orders a step of which was refused. |
refunds |
integer | yes | Refunds settled: credit notes of the goods refunded, paid out of the payment provider's balance. |
payouts |
integer | yes | Payouts of Shopify Payments booked: their fees and chargebacks against the payment provider's balance. |
connection |
ShopifyConnection |
yes | The connection after the round. |
ShopifyVariantOut
| Field | Type | Required | Description |
|---|---|---|---|
variant_id |
integer | yes | Shopify's id of the variant. |
product_id |
string (uuid) | yes | Id of the product it is sold as here. |
product |
string | yes | Code of that product. |
sku |
string or null | yes | The variant's SKU in Shopify. |
name |
string | yes | The product's title, and the variant's when it has options. |
price |
string | yes | Its price in the shop's currency, as the shop shows it. |
status |
string | yes | The Shopify product's status: ACTIVE, DRAFT, ARCHIVED. |
vendor |
string or null | yes | The product's vendor in the shop. |
cost |
string or null | yes | The variant's cost per item in the shop's currency; null when it has none. |
read_at |
string (date-time) | yes | When it was last read (UTC). |