Partner API · v1 early access

Build on ShopZen.

Connect your dropshipping platform, inventory system or marketplace to your users' ShopZen stores. Read orders, update deliveries and publish products — always with the store owner's permission.

API keys come only from ShopZen

There is no page where anyone can make their own key. A ShopZen administrator reviews every partner and issues the client ID and secret by hand — sandbox first, production after a check. Store owners never hand you a password or key: they approve your app on ShopZen's own Connect screen, and can disconnect it at any time.

  • Connect stores

    Your users sign in or sign up on ShopZen and approve your app. Nobody shares a password.

  • Orders and customers

    New orders, their items and customers inside your app. Update the status and add courier tracking.

  • “Add to my website”

    Publish a product with photos, sizes, prices and stock in one call, or up to 500 at once.

  • Webhooks

    Hear the moment an order is placed, a product changes or stock runs low. Signed, retried for 3 days.

Store design, pages, domains, discount codes, team and billing stay in the ShopZen dashboard. There's no fee for partners.

On this page
  1. Getting a key
  2. Connecting a store
  3. Making requests
  4. Permissions
  5. Endpoints
  6. Webhooks
  7. Errors
  8. Going live
  9. Questions

Getting a key

Keys are issued by the ShopZen team, one partner at a time.

  1. 1

    Apply by email

    Write to [email protected] with your company name, website and logo, the app name your users will see, your exact HTTPS redirect URLs, the permissions you need (and why), and a technical contact.

  2. 2

    ShopZen reviews it

    A ShopZen administrator checks your company and trims the permissions to what your app actually uses.

  3. 3

    Sandbox key

    You get a sandbox client ID and secret. Build and test the whole flow — test stores, test orders, no real money.

  4. 4

    Production key

    Show us the go-live checklist working (a short screen recording is fine) and we issue your production key.

Keep the client secret on your server — never in a mobile app, browser code or a public repository. If it leaks, email us and we rotate it straight away.

Connecting a store

“Connect ShopZen” uses OAuth 2.0 with PKCE — the same flow as “Sign in with Google”. Each connection is one of your users plus one of their stores.

  1. 1Your server makes a random state and code verifier, then sends the user to ShopZen's Connect page.
  2. 2They sign in — or create a ShopZen store right there — pick a store, see what you asked for and press Allow.
  3. 3ShopZen sends them back to your redirect URL with a one-time code (valid 10 minutes). Check state first.
  4. 4Your server swaps the code for tokens. Save the store ID, both tokens and the granted scopes.
1 · Send the user to Connect
https://shopzen.bd/connect
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https%3A%2F%2Fapp.example.com%2Fshopzen%2Fcallback
  &scope=store.read%20products.write%20orders.read
  &state=RANDOM_STATE
  &code_challenge=BASE64URL_SHA256_OF_VERIFIER
  &code_challenge_method=S256
4 · Swap the code for tokens (server only)
curl https://api.shopzen.bd/oauth/token \
  -d grant_type=authorization_code \
  -d code=AUTH_CODE \
  -d redirect_uri=https://app.example.com/shopzen/callback \
  -d client_id=YOUR_CLIENT_ID \
  -d client_secret=YOUR_CLIENT_SECRET \
  -d code_verifier=CODE_VERIFIER
Response
{
  "access_token": "szp_at_7f3c…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "szp_rt_91ab…",
  "scope": "store.read products.write orders.read",
  "store": { "id": "cmtss9wvd0008fuhdzv49z27m", "name": "Rahim Fashion", "url": "https://rahim.shopzen.bd" }
}

Access tokens last 1 hour. Refresh them with grant_type=refresh_token; each refresh returns a new refresh token and the old one stops working. Using an old one again ends the connection, as it looks stolen. Refresh tokens expire after 90 days unused.

Users can disconnect from your app (call POST /oauth/revoke) or from ShopZen, under Settings → Connected apps. Either way the tokens stop working at once.

Making requests

Sandbox and production are fully separate: separate keys, stores, tokens and webhooks.

Connect page

Sandbox · https://sandbox.shopzen.bd/connect

Live · https://shopzen.bd/connect

Token endpoint

Sandbox · https://api.sandbox.shopzen.bd/oauth/token

Live · https://api.shopzen.bd/oauth/token

API base URL

Sandbox · https://api.sandbox.shopzen.bd/partner/v1

Live · https://api.shopzen.bd/partner/v1

Every call sends the access token
curl https://api.shopzen.bd/partner/v1/orders?status=PENDING \
  -H "Authorization: Bearer szp_at_7f3c…"
Format
JSON in and out. Results are under data; lists add next_cursor.
Money
Whole numbers in paisa: 149000 is ৳1,490. Never send decimals.
Times
ISO 8601 in UTC, like 2026-09-25T08:30:00.000Z.
Your IDs
Products, variants, orders and customers take an external_id — your own ID — so you never have to store ours.
Pages
Up to 200 items per page (50 by default). Pass next_cursor back as ?cursor= until it is null.
Retries
Send an Idempotency-Key on every POST. A retry with the same key within 24 hours returns the first result instead of doing the work twice.
Rate limit
120 requests a minute per connected store, with short bursts. Over it, you get 429 and a Retry-After header.

Permissions

Ask only for what you use — users approve a short list more readily. The user may grant fewer than you asked for.

store.read

The store's name, address, currency and status.

products.read

Products, variants, categories and stock.

products.write

Create, update and archive products; set stock and prices.

orders.read

Orders and their items.

orders.write

Change order status and add tracking.

customers.read

Customers — name, phone, email, addresses — and their orders.

analytics.read

The sales summary.

products.write includes products.read, and orders.write includes orders.read. Calling an endpoint without its permission returns 403 insufficient_scope.

Endpoints

All paths are under /partner/v1, and every call only ever sees the store the token was issued for.

Store

GET/store

The connected store and its status

Products

PUT/products/by-external-id/{external_id}

Create or update one product by your ID

POST/products/bulk

Up to 500 products in one background job

GET/products

List, with status, category and search filters

GET/products/{id}

One product

PATCH/products/{id}

Change some fields, like the status

DELETE/products/{id}

Archive — hidden from the store, kept on past orders

GET/categories

All categories

POST/inventory

Set stock for up to 1,000 variants

Orders

GET/orders

List, filtered by status and dates

GET/orders/{id}

One order with items, delivery and payment

POST/orders/{id}/status

Move it along: paid, fulfilled, canceled

POST/orders/{id}/tracking

Add the courier and tracking number

PATCH/orders/{id}

Link it to the order in your system

Customers and sales

GET/customers

List and search customers

GET/customers/{id}

One customer with addresses and totals

GET/customers/{id}/orders

Their orders, newest first

GET/analytics/summary

Sales, orders and top products for a date range

Webhooks

POST/webhooks

Register an endpoint (up to 5 per store)

GET/webhooks

Your endpoints

DELETE/webhooks/{id}

Remove one

“Add to my website” — create or update by your ID
curl -X PUT https://api.shopzen.bd/partner/v1/products/by-external-id/8812 \
  -H "Authorization: Bearer szp_at_7f3c…" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Navy cotton panjabi",
    "category": "Men / Panjabi",
    "images": ["https://cdn.example.com/8812-front.jpg"],
    "variants": [
      { "external_id": "8812-M", "sku": "PNJ-NVY-M", "options": { "Size": "M" }, "price": 185000, "stock": 12 },
      { "external_id": "8812-L", "sku": "PNJ-NVY-L", "options": { "Size": "L" }, "price": 185000, "stock": 8 }
    ]
  }'

ShopZen copies product photos to its own storage, so they keep working if your links change later.

Webhooks

ShopZen posts to your HTTPS endpoint the moment something happens. Answer with any 2xx within 10 seconds and do the work afterwards.

order.created

A shopper places an order

order.updated

Status, tracking or notes change

order.canceled

An order is canceled

product.updated

A product changes, or its photos finish copying

product.deleted

A product is archived

inventory.low

A variant reaches its low-stock level

job.completed

A bulk job finishes

store.suspended

The store closes to shoppers, e.g. an unpaid plan

store.reactivated

The store opens again

connection.revoked

The owner disconnects your app — always sent

customer.redact

A shopper asks for their data to be deleted

Every delivery is signed with your webhook secret. Check the signature before trusting anything in it — anyone can post to your URL.

Verify a webhook (Node.js)
import crypto from "node:crypto";

// Use the raw body, before any JSON parsing.
function isFromShopZen(rawBody, headers, secret) {
  const timestamp = headers["shopzen-timestamp"];
  const expected = crypto.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
  const signatures = (headers["shopzen-signature"] ?? "").split(",").map((s) => s.trim().replace(/^v1=/, ""));
  const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
  return fresh && signatures.some((s) => s.length === expected.length && crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected)));
}
  • Failed deliveries are retried for up to 3 days, with growing gaps.
  • The same event can arrive twice, and out of order. Skip duplicates by id and compare updated_at.
  • Webhooks are a signal, not a guarantee: sync with updated_since every hour as a backup.

Errors

Match on the error code, never the message. Include request_id when you contact us.

Every error looks like this
{
  "error": {
    "code": "validation_failed",
    "message": "variants[0].price must be a positive integer",
    "details": { "field": "variants[0].price" },
    "request_id": "req_01J8ZA1…"
  }
}

400validation_failed

Fix the request — details names the field.

401token_expired

Refresh the access token and retry.

401invalid_token

Revoked or wrong. If refreshing fails too, ask the user to connect again.

403insufficient_scope

The connection lacks a permission. Ask the user to reconnect with it.

403store_suspended

Reads still work. Retry writes after store.reactivated.

403plan_limit_reached

The store's plan is full. Tell the user; they can upgrade in ShopZen.

409conflict

A SKU or external_id is already used by another record.

429rate_limited

Wait Retry-After seconds, then retry.

5xxserver_error

Our side. Retry with backoff and the same Idempotency-Key.

Retry 429 and 5xx with exponential backoff (1 s, 2 s, 4 s… up to a minute). Don't retry other 4xx errors without fixing the request.

Going live

Before ShopZen issues a production key, your integration shows us that it:

  • The client secret and webhook secret live only on your server
  • The callback checks state, and the connect flow uses PKCE
  • The new refresh token is saved after every refresh, one refresh at a time per store
  • Every webhook signature and timestamp is checked, and duplicates are skipped
  • 401, 403 and 429 errors show the user a clear message
  • Every POST sends an Idempotency-Key
  • Users can disconnect from your app, and connection.revoked is handled
  • A store's customer data is deleted on disconnect or customer.redact
  • Your users have a support contact they can reach

Technical help: [email protected]. Security issues: security@shopzen.bd — please don't post them publicly.

Questions

Can I create an API key myself?

No. There is no self-serve key page. Every key is created by a ShopZen administrator after reviewing your application, and ShopZen can rotate or turn off a key at any time. Store owners can't create keys either — they approve your app on ShopZen's own Connect screen.

Is there a fee for partners?

No. Each connected store pays its normal ShopZen plan, and that's all.

What if my client secret leaks?

Email [email protected] straight away and we rotate it. Nobody from ShopZen will ever ask you for your secret.

Can the API change a store's design, domain or billing?

No. Store design, pages, domains, discount codes, team and billing stay in the ShopZen dashboard, where only the store's own people can change them.

Will v1 change under me?

Only in ways that don't break integrations: new endpoints, new optional fields and new events. A breaking change means /partner/v2, and v1 keeps working for at least 12 months after.

Want to connect your platform?

Tell us what you're building. We reply within two working days.