Dropshipping API
This content is not available in your language yet.
Who this is for
Section titled “Who this is for”For developers who automate a store they own or work on, with the REST API, the Posty5 SDKs or n8n. Shoppers and suppliers never call these routes. If you run the store by hand, the merchant guide is the page you want. Open the merchant guide
Authentication
Section titled “Authentication”Every request carries your API key in this header — X-API-Key
X-API-Key: YOUR_API_KEYCreate a key in the Posty5 dashboard, in Account settings, under API keys. API keys are available to Developer accounts.
Every route below works on one store and checks the store permission shown beside it. The key’s owner must own the store or be on its staff with that permission.
When the store owner’s plan does not include dropshipping (Pro or higher), these routes are refused — connecting a supplier (with an API key or by signing in), replacing its credentials, turning a connection on, changing its automation, browsing a supplier’s products, fetching one supplier product, resolving a product URL, previewing or running an import, creating a product link, and sending, retrying or paying a supplier order. The owner’s plan decides, not the plan of the staff member calling. Everything else stays open on any plan, so a store that drops below Pro can still read what it has, turn a connection off, change its settings, test or disconnect it, edit, sync or remove product links, and cancel a supplier order or fulfil it manually.
Base URL
Section titled “Base URL”- The API lives at
https://api.posty5.com - Every supplier route starts with
/api/store-suppliers - The rest of the Posty5 API is described at docs.posty5.com
Routes
Section titled “Routes”Paths below are relative to the supplier prefix. The first parameter is always the store id, and the id after it is the supplier connection.
Connections
Section titled “Connections”| Method | Route | Permission | What it does |
|---|---|---|---|
GET | /:storeId/catalogue | suppliers.view | The suppliers this store can connect, with what each can do. |
GET | /:storeId | suppliers.view | The store’s supplier connections, with health and automation. Credentials are never returned. |
POST | /:storeId | suppliers.manage | Connect a supplier with its API key. The key is checked with the supplier before it is stored. |
POST | /:storeId/oauth/start | suppliers.manage | Start connecting a supplier that uses sign-in, such as AliExpress. Answers the address to send the merchant to. |
PUT | /:storeId/:id | suppliers.manage | Replace the connection’s credentials. |
POST | /:storeId/:id/test | suppliers.manage | Check the connection now and record its health. |
GET | /:storeId/:id/balance | suppliers.view | The account balance at the supplier, where the supplier reports one. |
GET | /:storeId/:id/impact | suppliers.view | What disconnecting would affect — linked products and open supplier orders. |
PUT | /:storeId/:id/settings | suppliers.manage | Change the supplier’s own settings. |
PUT | /:storeId/:id/automation | suppliers.manage | Change how orders are sent, and the limits. |
PUT | /:storeId/:id/enabled | suppliers.manage | Switch the connection on or off. |
DELETE | /:storeId/:id | suppliers.manage | Disconnect. Credentials are deleted, and imported products stay as the store’s own. |
Supplier products, imports and links
Section titled “Supplier products, imports and links”| Method | Route | Permission | What it does |
|---|---|---|---|
GET | /:storeId/:id/products | suppliers.import | Browse or search the supplier’s catalogue. |
POST | /:storeId/:id/products/resolve-url | suppliers.import | Turn a pasted product link into the supplier’s product id. |
GET | /:storeId/:id/products/:supplierProductId | suppliers.import | One supplier product with its variants, costs and stock. |
POST | /:storeId/:id/import/preview | suppliers.import | Price the chosen products with the price rule and name any duplicates. Nothing is charged. |
POST | /:storeId/:id/import | suppliers.import | Import up to 50 products. A large import answers a job id to poll. |
GET | /:storeId/imports/:jobId | suppliers.import | The progress of a background import. |
GET | /:storeId/links | suppliers.view | The store’s product links. |
POST | /:storeId/links | suppliers.import | Link a product the store already has to a supplier product. |
PUT | /:storeId/links/:linkId | suppliers.import | Change a link’s variants, price rule or sync switches. |
DELETE | /:storeId/links/:linkId | suppliers.import | Unlink. The product stays as the store’s own. |
POST | /:storeId/links/:linkId/sync | suppliers.import | Sync one link now. |
Supplier orders
Section titled “Supplier orders”| Method | Route | Permission | What it does |
|---|---|---|---|
GET | /:storeId/orders | suppliers.view | Supplier orders, newest first, filtered and paged by cursor. The query fields are listed below the table. |
GET | /:storeId/orders/:supplierOrderId | suppliers.view | One supplier order with its history. |
POST | /:storeId/orders/:orderId/groups/:groupKey/submit | suppliers.orders.manage | Send one part of an order to its supplier now. |
POST | /:storeId/orders/:supplierOrderId/retry | suppliers.orders.manage | Try a queued, paused or failed supplier order again. |
POST | /:storeId/orders/:supplierOrderId/pay | suppliers.orders.manage | Pay a supplier order that was created but not paid. |
POST | /:storeId/orders/:supplierOrderId/cancel | suppliers.orders.manage | Withdraw the order at the supplier, where the supplier still allows it. |
POST | /:storeId/orders/:orderId/groups/:groupKey/fulfil-manually | suppliers.orders.manage | Take a part over, so the store ships it itself. |
The list accepts these query fields — status, needsReview, integrationId, orderId, contractModel, cursor, pageSize
The list pages by cursor, like the store’s other lists, and takes no page number. A page holds 25 rows unless you ask for another size, and never more than 100. Each answer is one page: the rows, then a pagination block with the next and previous cursors, whether more rows follow, the total count and the page size —
{ "items": [{ "_id": "SUPPLIER_ORDER_ID", "status": "needsReview" }], "pagination": { "nextCursor": "NEXT_CURSOR", "previousCursor": null, "hasMore": true, "totalCount": 42, "pageSize": 25 }}To read the next page, repeat the call with the same filters and the previous page’s next cursor as the cursor. Stop when no more rows follow. Read every page before acting on the rows: a retry or a payment changes the list the cursor walks.
Two actions take an optional body —
submit—{ "payNow": true }— also pay the order as soon as it is created, where the supplier can be paid from a balance. Paying now is the merchant’s own decision, so the check that the shopper has paid is skipped — an unpaid or cash-on-delivery order is paid for too, and the choice is recorded in the supplier order’s history. The destination, stock, cost-limit and balance checks still apply.retry—{ "acceptCost": true }— accept the supplier’s new cost after a price change. It is recorded as the caller’s decision.
Additions to store orders
Section titled “Additions to store orders”GET /api/store-orders/:storeId/:id— The order details add the order’s parts and, for a caller who also holds the suppliers view permission, its supplier orders with their costs.GET /api/store-orders/:storeId?needsAttention=true— Each order in the list carries a summary of its parts, and the list takes a filter for orders with a part that needs attention.
Objects
Section titled “Objects”A supplier connection
Section titled “A supplier connection”supplierKey— Which supplier, as the catalogue names it.mode— test or live.enabled— Whether the connection is switched on.hasCredentials— Whether credentials are stored. The credentials themselves are never returned.automation— How orders are sent and the limits that stop them.health— The result of the last check.isCircuitOpen,circuitOpenUntil— The connection is paused after repeated errors, until that time.lastWebhookAt— When the supplier last sent a notification.webhookUrl— The address the supplier sends its notifications to, on the connection list. Empty when no public address is configured.
A product link
Section titled “A product link”productId,supplierProductId— The store’s product and the supplier’s product it is linked to.variants[]— Each store variant mapped to a supplier variant, with its cost and stock.priceRule— How the store’s price is worked out from the cost.sync— Which fields the hourly sync may change.lastSyncAt,lastSyncError— The last sync and, if it failed, why.
A supplier order
Section titled “A supplier order”status— Where the supplier order is. The table below lists every value.reviewReason,reviewMessage— Why it stopped, as one reason from a closed list, and the supplier’s words with anything sensitive removed.retryable— Whether trying again with nothing changed may succeed.supplierOrderNumber— Our number, sent to the supplier as its order number. It never changes between retries of one attempt.supplierOrderId,supplierStatus— The supplier’s own id and status, as the supplier spells it.costs,payment— What the supplier charges, and whether the store has paid it.shipping— The carrier and tracking number once the supplier ships.attempt,events[]— Which attempt this is, and its history.destination,destinationRedacted— The delivery address, only for a caller allowed to see customer data. Otherwise it is removed and the flag says so.
The payment status is one of these — notRequired, unpaid, paid, refunded
On a store order
Section titled “On a store order”fulfilmentGroups[]— The order’s parts — one for the store’s own items and one per supplier connection — each with its status, items and shipment.fulfilmentSummary— How many parts there are, and how many have shipped or been delivered.supplierOrders[]— The supplier orders behind the parts, for a caller allowed to see suppliers.
Statuses and reasons
Section titled “Statuses and reasons”Order part statuses
Section titled “Order part statuses”An order moves as its slowest part. It becomes shipped only when every part has shipped, and delivered when every part is delivered.
| Value | Meaning | What a program may do |
|---|---|---|
pending | Not started yet. | Nothing — wait. |
processing | Being prepared, by the store or the supplier. | Nothing — wait. |
shipped | Handed to a carrier. | Read the tracking from the part’s shipment. |
delivered | Delivered. Final. | Nothing — wait. |
cancelled | Cancelled. Final. | Nothing — wait. |
Supplier order statuses
Section titled “Supplier order statuses”| Value | Meaning | What a program may do |
|---|---|---|
queued | Waiting to be sent. | Nothing — wait. |
needsReview | Stopped, with a reason. Needs a person or a changed input. | Show it to a person. Retry only after the cause has changed. |
submitted | Created at the supplier, not yet accepted and paid. | Pay it, if the store pays by hand. |
confirmed | Accepted and paid at the supplier. | Nothing — wait. |
processing | Being prepared by the supplier. | Nothing — wait. |
shipped | Shipped by the supplier, with tracking. | Nothing — wait. |
delivered | Delivered. Final. | Nothing — wait. |
cancelled | Cancelled. Final. | Nothing — wait. |
failed | A temporary error, such as a timeout. The job retries it by itself. | Retry it once, if the automatic retries have stopped. |
Review reasons
Section titled “Review reasons”A paused supplier order carries exactly one of these. The second column is the value the api sets for each.
| Value | retryable | Meaning | What a program may do |
|---|---|---|---|
insufficientBalance | false | The store’s balance at the supplier cannot cover the order. | Tell a person. Retry after the balance is topped up. |
variantUnavailable | false | The supplier no longer offers a variant. | Nothing without a person. |
destinationUnsupported | false | The supplier does not deliver there, or the store excluded the country. | Nothing without a person. |
costAboveLimit | false | The cost is above a limit the store set. | Retry only on a person’s decision. |
costChanged | false | The supplier’s price moved since import. | Retry, accepting the new cost, only on a person’s decision. |
customerPaymentPending | false | The shopper has not paid online. | Submit it when the store decides to. |
customerPaymentNotSettled | false | The payment is authorised but not settled. | Retry after the payment settles. |
connectionUnhealthy | true | The supplier did not answer, or the connection is paused. | Retry later. |
supplierRefused | true | The supplier rejected the order. | Read the message. Retry once the cause is fixed. |
connectionMissing | false | The connection is off, removed, or the supplier is switched off by Posty5. | Nothing without a person. |
supplierAlreadyShipped | false | A cancel came after the supplier shipped. | Nothing — handle it as a return. |
testMode | false | A test connection to a supplier without a sandbox. No order was placed. | Nothing until the connection is live. |
customerCaptureFailed | false | Promise to sell — the supplier was paid, charging the shopper failed. | Tell a person at once. |
The supplier notification address
Section titled “The supplier notification address”POST /api/store-suppliers/webhook/:supplierKey/:integrationIdSuppliers that send notifications call this address to report order status, tracking and stock.
Posty5 finds the connection from the address and checks the message with that connection’s own credentials. Every refusal gets the same 400 answer, and 503 when Posty5 cannot decide, so the supplier sends again. A notification only prompts Posty5 to read the order back from the supplier. It never moves money, stock or a status by itself.
Idempotency and retries
Section titled “Idempotency and retries”A supplier order is protected against duplicates three ways.
- One job per order part, whatever triggers it —
supplier-order:<orderId>:<groupKey> - A unique key per attempt, sent to the supplier as its order number. Before creating, Posty5 asks the supplier whether that number already exists, so a create that timed out after succeeding is found, not repeated.
- Before paying, Posty5 reads the supplier’s own status. An order already paid there is recorded, not paid again.
What that means for your program
Section titled “What that means for your program”- Calling submit twice gives one supplier order.
- Retry is accepted for a queued, paused or failed order. Retry automatically only when this field is true —
retryable - A paused order does not clear by itself. It needs a person, or an input that changed.
- To follow an order, read the supplier order again. Do not call submit in a loop.
When orders are sent
Section titled “When orders are sent”With an automatic level, a supplier part is sent once, when the order becomes confirmed — by a staff member or by an online payment.
Automatic payment to the supplier needs the shopper’s payment to be paid, as confirmed by the payment provider. An order with no online payment waits unless the connection allows unpaid orders.
Under promise to sell, sending starts once the shopper’s payment is authorised, and the shopper is charged when the supplier accepts.
Examples
Section titled “Examples”List the supplier orders waiting for review —
curl "https://api.posty5.com/api/store-suppliers/STORE_ID/orders?needsReview=true" \ -H "X-API-Key: YOUR_API_KEY"The answer is one page of supplier orders. A row stopped for a low balance looks like this —
{ "_id": "SUPPLIER_ORDER_ID", "orderNumber": "1042", "fulfilmentGroupKey": "GROUP_KEY", "supplierKey": "cjdropshipping", "attempt": 1, "status": "needsReview", "reviewReason": "insufficientBalance", "reviewMessage": "Balance is insufficient", "retryable": false, "supplierOrderNumber": "SUPPLIER_ORDER_NUMBER", "payment": { "status": "unpaid" }}The same call from TypeScript —
const response = await fetch( `https://api.posty5.com/api/store-suppliers/${storeId}/orders?needsReview=true`, { headers: { "X-API-Key": process.env.POSTY5_API_KEY! } },);const { result } = await response.json();// result.items: the supplier orders on this page// result.pagination.nextCursor: send it back as `cursor` while result.pagination.hasMoreAnd from C# —
using var http = new HttpClient { BaseAddress = new Uri("https://api.posty5.com") };http.DefaultRequestHeaders.Add("X-API-Key", Environment.GetEnvironmentVariable("POSTY5_API_KEY"));var json = await http.GetStringAsync($"/api/store-suppliers/{storeId}/orders?needsReview=true");// { "result": { "items": [...], "pagination": { "nextCursor": "...", "hasMore": true, ... } } }// Send pagination.nextCursor back as &cursor= while pagination.hasMore is true.SDKs and n8n
Section titled “SDKs and n8n”The JavaScript and .NET store packages each have a suppliers client covering every group above — catalogue, connections, supplier products, imports, links and supplier orders. When a supplier order is paused for review, submit, retry and pay throw an error instead of returning, so read the supplier order again to see why.
- npm —
@posty5/store4.3.0—store.suppliers - NuGet —
Posty5.Store3.2.0—store.Suppliers
Neither of these versions is published yet. Until they are, call the routes above directly.
From TypeScript, retry every supplier order paused because the supplier’s price changed, accepting the new cost —
import { HttpClient } from "@posty5/core";import { IStoreSupplierOrder, StoreClient } from "@posty5/store";
const store = new StoreClient(new HttpClient({ apiKey: process.env.POSTY5_API_KEY }));
// Read every page first: a retry changes the list the cursor walks.const paused: IStoreSupplierOrder[] = [];let cursor: string | undefined;do { const page = await store.suppliers.listSupplierOrders(storeId, { needsReview: true, cursor, pageSize: 100 }); paused.push(...page.items); cursor = page.pagination.hasMore ? page.pagination.nextCursor ?? undefined : undefined;} while (cursor);
for (const supplierOrder of paused) { if (supplierOrder.reviewReason === "costChanged") { await store.suppliers.retry(storeId, supplierOrder._id, { acceptCost: true }); }}And from C# —
using Posty5.Core.Configuration;using Posty5.Core.Http;using Posty5.Core.Models;using Posty5.Store;using Posty5.Store.Models;
var http = new Posty5HttpClient(new Posty5Options { ApiKey = Environment.GetEnvironmentVariable("POSTY5_API_KEY") });var store = new StoreClient(http);
// Read every page first: a retry changes the list the cursor walks.var paused = new List<StoreSupplierOrder>();string? cursor = null;do{ var page = await store.Suppliers.ListSupplierOrdersAsync(storeId, new SupplierOrderSearchParams { NeedsReview = true }, new PaginationParams { Cursor = cursor, PageSize = 100 }); if (page == null) break; paused.AddRange(page.Items); cursor = page.Pagination.HasMore ? page.Pagination.NextCursor : null;} while (cursor != null);
foreach (var supplierOrder in paused){ if (supplierOrder.ReviewReason == "costChanged") await store.Suppliers.RetryAsync(storeId, supplierOrder.Id!, acceptCost: true);}The Posty5 Store node for n8n (version 4.4.0 of the n8n package) covers suppliers, supplier products, product links, supplier orders, order parts and orders. It is not published yet either.