Ir al contenido

Dropshipping API

Esta página aún no está disponible en tu idioma.

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


Every request carries your API key in this header — X-API-Key

Terminal window
X-API-Key: YOUR_API_KEY

Create 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.


  • 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

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.

MethodRoutePermissionWhat it does
GET/:storeId/cataloguesuppliers.viewThe suppliers this store can connect, with what each can do.
GET/:storeIdsuppliers.viewThe store’s supplier connections, with health and automation. Credentials are never returned.
POST/:storeIdsuppliers.manageConnect a supplier with its API key. The key is checked with the supplier before it is stored.
POST/:storeId/oauth/startsuppliers.manageStart connecting a supplier that uses sign-in, such as AliExpress. Answers the address to send the merchant to.
PUT/:storeId/:idsuppliers.manageReplace the connection’s credentials.
POST/:storeId/:id/testsuppliers.manageCheck the connection now and record its health.
GET/:storeId/:id/balancesuppliers.viewThe account balance at the supplier, where the supplier reports one.
GET/:storeId/:id/impactsuppliers.viewWhat disconnecting would affect — linked products and open supplier orders.
PUT/:storeId/:id/settingssuppliers.manageChange the supplier’s own settings.
PUT/:storeId/:id/automationsuppliers.manageChange how orders are sent, and the limits.
PUT/:storeId/:id/enabledsuppliers.manageSwitch the connection on or off.
DELETE/:storeId/:idsuppliers.manageDisconnect. Credentials are deleted, and imported products stay as the store’s own.
MethodRoutePermissionWhat it does
GET/:storeId/:id/productssuppliers.importBrowse or search the supplier’s catalogue.
POST/:storeId/:id/products/resolve-urlsuppliers.importTurn a pasted product link into the supplier’s product id.
GET/:storeId/:id/products/:supplierProductIdsuppliers.importOne supplier product with its variants, costs and stock.
POST/:storeId/:id/import/previewsuppliers.importPrice the chosen products with the price rule and name any duplicates. Nothing is charged.
POST/:storeId/:id/importsuppliers.importImport up to 50 products. A large import answers a job id to poll.
GET/:storeId/imports/:jobIdsuppliers.importThe progress of a background import.
GET/:storeId/linkssuppliers.viewThe store’s product links.
POST/:storeId/linkssuppliers.importLink a product the store already has to a supplier product.
PUT/:storeId/links/:linkIdsuppliers.importChange a link’s variants, price rule or sync switches.
DELETE/:storeId/links/:linkIdsuppliers.importUnlink. The product stays as the store’s own.
POST/:storeId/links/:linkId/syncsuppliers.importSync one link now.
MethodRoutePermissionWhat it does
GET/:storeId/orderssuppliers.viewSupplier orders, newest first, filtered and paged by cursor. The query fields are listed below the table.
GET/:storeId/orders/:supplierOrderIdsuppliers.viewOne supplier order with its history.
POST/:storeId/orders/:orderId/groups/:groupKey/submitsuppliers.orders.manageSend one part of an order to its supplier now.
POST/:storeId/orders/:supplierOrderId/retrysuppliers.orders.manageTry a queued, paused or failed supplier order again.
POST/:storeId/orders/:supplierOrderId/paysuppliers.orders.managePay a supplier order that was created but not paid.
POST/:storeId/orders/:supplierOrderId/cancelsuppliers.orders.manageWithdraw the order at the supplier, where the supplier still allows it.
POST/:storeId/orders/:orderId/groups/:groupKey/fulfil-manuallysuppliers.orders.manageTake 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.
  • 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.

  • 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.
  • 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.
  • 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

  • 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.

An order moves as its slowest part. It becomes shipped only when every part has shipped, and delivered when every part is delivered.

ValueMeaningWhat a program may do
pendingNot started yet.Nothing — wait.
processingBeing prepared, by the store or the supplier.Nothing — wait.
shippedHanded to a carrier.Read the tracking from the part’s shipment.
deliveredDelivered. Final.Nothing — wait.
cancelledCancelled. Final.Nothing — wait.
ValueMeaningWhat a program may do
queuedWaiting to be sent.Nothing — wait.
needsReviewStopped, with a reason. Needs a person or a changed input.Show it to a person. Retry only after the cause has changed.
submittedCreated at the supplier, not yet accepted and paid.Pay it, if the store pays by hand.
confirmedAccepted and paid at the supplier.Nothing — wait.
processingBeing prepared by the supplier.Nothing — wait.
shippedShipped by the supplier, with tracking.Nothing — wait.
deliveredDelivered. Final.Nothing — wait.
cancelledCancelled. Final.Nothing — wait.
failedA temporary error, such as a timeout. The job retries it by itself.Retry it once, if the automatic retries have stopped.

A paused supplier order carries exactly one of these. The second column is the value the api sets for each.

ValueretryableMeaningWhat a program may do
insufficientBalancefalseThe store’s balance at the supplier cannot cover the order.Tell a person. Retry after the balance is topped up.
variantUnavailablefalseThe supplier no longer offers a variant.Nothing without a person.
destinationUnsupportedfalseThe supplier does not deliver there, or the store excluded the country.Nothing without a person.
costAboveLimitfalseThe cost is above a limit the store set.Retry only on a person’s decision.
costChangedfalseThe supplier’s price moved since import.Retry, accepting the new cost, only on a person’s decision.
customerPaymentPendingfalseThe shopper has not paid online.Submit it when the store decides to.
customerPaymentNotSettledfalseThe payment is authorised but not settled.Retry after the payment settles.
connectionUnhealthytrueThe supplier did not answer, or the connection is paused.Retry later.
supplierRefusedtrueThe supplier rejected the order.Read the message. Retry once the cause is fixed.
connectionMissingfalseThe connection is off, removed, or the supplier is switched off by Posty5.Nothing without a person.
supplierAlreadyShippedfalseA cancel came after the supplier shipped.Nothing — handle it as a return.
testModefalseA test connection to a supplier without a sandbox. No order was placed.Nothing until the connection is live.
customerCaptureFailedfalsePromise to sell — the supplier was paid, charging the shopper failed.Tell a person at once.

POST /api/store-suppliers/webhook/:supplierKey/:integrationId

Suppliers 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.


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.
  • 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.

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.


List the supplier orders waiting for review —

Terminal window
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.hasMore

And 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.

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/store 4.3.0 — store.suppliers
  • NuGet — Posty5.Store 3.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.