Platforms
Connect a Shopify store to manage its catalog, read its orders, publish blog articles and install ad pixels on the storefront.
Connecting Shopify gives you a store account, which is a different kind of account from a social one. Through it you run the catalog (products, stock, collections, discounts), read orders, publish blog articles and install ad pixels on the storefront. You cannot pick a store as a destination in POST /v1/posts, and it has no inbox or social analytics.
#What you can do
| Job | How | Page |
|---|---|---|
| Manage products, variants, images and stock | Commerce API (/v1/commerce/...) |
Products |
| Read orders with line items, tax, refunds and attribution | Commerce API, read-only | Orders |
| Collections, discounts, pages, menus, redirects, markets, metafields | Commerce API | Commerce overview |
| Keep a Meta product catalog in sync for catalog ads | Catalog syncs | Products |
| Create, schedule and edit blog articles | Blogs API | Blogs |
| Fire Meta, TikTok, Google, LinkedIn, Pinterest, X and ChatGPT pixels | Tracking tag install | Site pixels |
| Property | Value |
|---|---|
| Platform id | shopify |
| Connect | OAuth install (needs the store's myshopify.com domain) or a custom-app Admin token |
| Social posting | No |
| Analytics | No (order data comes from the Commerce API) |
| Ids | Shopify's own numeric ids as strings. Read them from responses, never build them |
#Connect a store
Shopify cannot look up a merchant's stores for you, so you need the store domain first. Ask the merchant for it (acme.myshopify.com, or just acme).
curl "https://api.pozy.ai/v1/connect/shopify?profileId=6710c3a9e4b0d2f1a8c95e21&shop=acme.myshopify.com&redirect_url=https://app.example.com/connected" \
-H "Authorization: Bearer $POZY_API_KEY"const res = await fetch("https://api.pozy.ai/v1/connect/shopify?profileId=6710c3a9e4b0d2f1a8c95e21&shop=acme.myshopify.com&redirect_url=https:%2F%2Fapp.example.com%2Fconnected", {
headers: { Authorization: `Bearer ${process.env.POZY_API_KEY}` },
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
// Redirect the merchant to data.authUrlimport os, requests
resp = requests.get(
"https://api.pozy.ai/v1/connect/shopify",
headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
params={
"profileId": "6710c3a9e4b0d2f1a8c95e21",
"shop": "acme.myshopify.com",
"redirect_url": "https://app.example.com/connected",
},
)
auth_url = resp.json()["authUrl"]Open authUrl in the merchant's browser. When they approve the install, Shopify and Pozy send them to your redirect_url, whose query string holds connected, profileId, accountId and username, the store's domain. Keep the accountId. Rely on the redirect rather than on the domain you sent: the merchant can end up approving another store.
redirect_url has to be a full http(s) URL or a custom app scheme like myapp://connected; a relative path gets 400. If you connect a store again to the profile it is already in, Pozy just renews the existing account's token.
#Connect with an Admin token instead
Without a browser step, the merchant sets up a custom app in the Shopify admin under Settings > Apps and sales channels > Develop apps. They give it read_content, write_content, read_products and write_products, then hand you its Admin API token, which begins with shpat_.
curl -X POST https://api.pozy.ai/v1/connect/shopify/token \
-H "Authorization: Bearer $POZY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"profileId": "6710c3a9e4b0d2f1a8c95e21",
"shop": "acme.myshopify.com",
"accessToken": "shpat_..."
}'Pozy checks the token against the store before it saves anything. Custom-app tokens do not expire. Two things work differently with a token: the store cannot grant optional permissions through Pozy (the merchant adds scopes to the custom app instead), and pixels cannot be installed, because a Shopify web pixel belongs to the app that installs it.
#Permissions
The install asks for the scopes Pozy needs for content and catalog work. Nothing about customers or payments is requested up front.
| Scope | Enables |
|---|---|
read_content, write_content |
Blogs, articles and online store pages |
read_products, write_products |
Products, variants, images, collections and metafields |
write_pixels, read_customer_events |
Installing pixels and letting them receive storefront events |
Stock, sales channels, discounts, navigation, metaobjects, markets, marketing activities, image removal and orders need optional scopes. The merchant approves those later without reinstalling. Call GET /v1/commerce/store: it lists what the store can do now in capabilities, what is missing in missingCapabilities, and returns a grantPermissionsUrl to send the store owner to.
| Capability | Shopify scope behind it |
|---|---|
products.images_remove |
write_files |
inventory.read, inventory.write |
write_inventory and read_locations |
channels.read, channels.write |
write_publications |
discounts.read, discounts.write, discounts.codes |
write_discounts |
navigation.read, navigation.write |
write_online_store_navigation |
metaobjects.read, metaobjects.write |
write_metaobjects |
markets.read, markets.write |
write_markets (writes also use write_products) |
marketing.write |
write_marketing_events |
orders.read |
read_orders and read_all_orders |
Without read_all_orders, Shopify only returns the last 60 days of orders, so a store that granted only read_orders still lists orders.read as missing. Order reads also need Shopify's approval for protected customer data. Where that approval does not cover buyer details, customer, billingAddress and shippingAddress come back as null.
#Shopify-only operations
Most of the Commerce API works on both Shopify and WooCommerce. These operations exist only on Shopify and return 400 platform_not_supported elsewhere:
- Sales channels: list channels, publish or unpublish products and collections
- Markets and price lists, including fixed prices per market
- Metaobjects and their definitions
- Navigation menus and URL redirects
- Collection metafields and reordering products inside a collection
- Adding codes to an existing discount
- Marketing activities: log a post, ad or message you ran for the merchant so it shows in the store's Marketing area with its UTM tags, then add engagement numbers to it each day
curl -X PUT https://api.pozy.ai/v1/commerce/marketing-activities \
-H "Authorization: Bearer $POZY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"accountId": "6710c4b2e4b0d2f1a8c95e3a",
"remoteId": "post_6711a0c3e4b0d2f1a8c96001",
"title": "Autumn drop on Instagram",
"url": "https://acme.com/collections/autumn",
"utm": { "campaign": "autumn-drop", "source": "instagram", "medium": "social" },
"tactic": "post",
"channel": "social",
"status": "active"
}'Set remoteId to an id from your side, such as the Pozy post id. A second call with that remoteId changes the existing activity instead of adding one.
#Limits and behaviour
- Pozy keeps no copy of store data. Every read goes to the store, except the catalog syncs you create.
- Shopify tells Pozy about product changes through its webhooks, so synced catalogs catch up within minutes and you get
commerce.product.*events. - When the merchant uninstalls the app, Shopify revokes the token at once and Pozy marks the account disconnected. There is nothing for you to revoke. Reinstalling runs the normal connect flow and issues a fresh token.
- Blog and article deletes are permanent. Deleting a blog deletes every article in it.
- You cannot create orders, write customers, upload media or publish social posts through a store account.
#Errors
| Error | Cause | Fix |
|---|---|---|
400 on connect |
shop is not a myshopify.com domain, or redirect_url is relative |
Pass acme.myshopify.com (or acme) and an absolute redirect |
403 insufficient_permissions |
The store never granted the needed scope, or Shopify turned the call down | Point the owner to grantPermissionsUrl, or connect the store again |
409 reconnect_required |
A pixel install on a store connected with a token or without pixel access | Send the merchant to details.authUrl. The account keeps its id |
404 product_not_found |
The product was deleted or belongs to another store | List products for a current id |
404 blog_article_not_found |
The article was deleted or is in another blog | List the blog's articles |
429 |
Your Pozy rate limit or Shopify's own limit | Wait for Retry-After, then retry |
#Endpoints
| Endpoint | What it does |
|---|---|
GET /v1/connect/shopify |
Get Shopify OAuth connect URL |
POST /v1/connect/shopify/token |
Connect a Shopify store with a custom-app Admin token |
GET /v1/commerce/store |
Get a store |
The full catalog, order and discount surface is listed on Commerce, and the article endpoints on Blogs.
#Webhook events
| Event | When it fires |
|---|---|
account.connected |
Account connected. |
account.disconnected |
Account disconnected. |
commerce.product.created |
Commerce product created. |
commerce.product.updated |
Commerce product updated. |
commerce.product.deleted |
Commerce product deleted. |
commerce.order.created |
Commerce order created. |
commerce.order.updated |
Commerce order updated. |
commerce.order.refunded |
Commerce order refunded. |