Quick Start Guide
This walks through a real integration end to end: verify credentials, price a SKU, create a listing, then fulfil an order. Every request and response below matches what the API actually returns.
Prerequisites
- Credentials — an
api_keyandsecretfrom your SASOM account manager - A backend to call from — this is a server-to-server API; the secret must never reach a browser or mobile app
- An HTTP client
Put the credentials in environment variables:
export SASOM_API_KEY='...'
export SASOM_API_SECRET='...'
Step 1 — Verify your credentials
Call a read-only endpoint:
curl -sS -w '\nHTTP %{http_code}\n' \
https://partners.sasomapi.com/partner/processing/orders \
-u "$SASOM_API_KEY:$SASOM_API_SECRET"
A working credential returns HTTP 200 and:
{
"type": "processing",
"orders": [],
"nextCursor": null
}
If it does not return 200, the status tells you roughly where to look:
| Status | Most likely cause |
|---|---|
401 {"message":"Unauthorized"} | No Authorization header reached us |
500 {"message":"Internal Server Error"} | Usually a wrong or not-yet-provisioned api_key, or a malformed header — check the credential before reporting an outage |
403 {"message":"Forbidden"} | Wrong secret, or you are over the 200 req/60s rate limit |
The 500 is genuinely counter-intuitive: an unrecognised api_key makes our
authorizer fail rather than deny. It is a known defect and will become a 403, so
do not build logic around it — just check your key first.
Also check you are not sending a newline inside the base64 (use printf, not
echo). See
Authentication.
Step 2 — Check the market price before you list
Find out what the SKU is going for, and which sizes are valid for it:
curl -sS https://partners.sasomapi.com/partner/lowest/FZ5246-100 \
-u "$SASOM_API_KEY:$SASOM_API_SECRET"
{
"message": "SKU found, missing size key means no listing on that size",
"code": "0000",
"size_map": {
"size_type": "US",
"9": { "ask": { "price": 4500 }, "pre_verified": { "price": 4200 } },
"10": { "ask": { "price": 5200 } }
},
"product": {
"name": "Air Jordan 1 Retro High OG",
"model": "FZ5246-100",
"brand": "Nike",
"gender_size": "men",
"size_type": "US",
"category": "sneakers"
}
}
Two things to take from this:
- The
size_mapkeys are the valid sizes for that SKU. Use them — a size the product does not have is rejected with the valid list in the error message. - A missing size key means nobody has listed that size, not that the size is invalid.
An unknown SKU returns 400 with {"message":"SKU not found","code":"0009"}.
Use POST /partner/lowest/bulk — 100 SKUs per request instead of 100 requests. At
200 requests/minute, looping the single-SKU endpoint will throttle you quickly. See
Rate Limiting.
Step 3 — Create an ask listing
curl -sS -X POST https://partners.sasomapi.com/partner/list/ask \
-u "$SASOM_API_KEY:$SASOM_API_SECRET" \
-H 'Content-Type: application/json' \
-d '{
"sku": "FZ5246-100",
"size": "10",
"price": 5500
}'
201 Created:
{ "status": "created", "order_id": "kR3mQp7xY2nB8vLc4dFg" }
Store that order_id. It is how you edit, cancel and track this listing —
there is no way to look a listing up by SKU + size afterwards.
Things to know before you send this in anger:
- There is no
quantityfield on an ask listing. One call creates one unit. For multiple units, create multiple listings, or use a pre-order with aquota. - A price below 50 THB is silently clamped to 50, not rejected. You get a
normal
201and a live listing at a price you did not set. Validate first. 201does not always mean created. If your account is in vacation mode you get201with{"status":"user is in vacation mode"}and noorder_id. Always check fororder_id.- Retrying a call that timed out creates a second listing. There is no idempotency key.
Step 4 — Confirm it went live
curl -sS https://partners.sasomapi.com/partner/live/listings/ask \
-u "$SASOM_API_KEY:$SASOM_API_SECRET"
Valid types are ask, pre-order and r2s. Anything else returns 400 with the
valid list. The response has listings and nextCursor; page size is 500.
Step 5 — Poll for orders
There are no webhooks. Order state is discovered by polling:
curl -sS https://partners.sasomapi.com/partner/processing/orders \
-u "$SASOM_API_KEY:$SASOM_API_SECRET"
{
"type": "processing",
"orders": [
{
"order_id": "kR3mQp7xY2nB8vLc4dFg",
"sku": "FZ5246-100",
"size": "10",
"price": 5500,
"status": "process",
"type": "ask",
"asker": "...",
"asker_email": "...",
"category": "sneakers",
"product": {
"name": "Air Jordan 1 Retro High OG",
"brand": "Nike",
"gender_size": "men",
"size_type": "US"
},
"process_timestamp": "2026-09-08T04:11:23.000Z",
"operation": { "authenticated": "pending" }
}
],
"nextCursor": null
}
This endpoint returns orders in pre_order, process and matched — page size
100. Respect the max-age=60 cache header rather than polling faster.
Step 6 — Confirm the order
An order in process is waiting for you:
curl -sS -X PUT https://partners.sasomapi.com/partner/confirm/order \
-u "$SASOM_API_KEY:$SASOM_API_SECRET" \
-H 'Content-Type: application/json' \
-d '{"order_id": "kR3mQp7xY2nB8vLc4dFg"}'
{ "message": "success - order been confirmed" }
Confirming records a shipping-leg fee and, on seller-shipping arrangements,
deducts it from your payout. The status change is asynchronous, so a retry sent
before the order leaves process deducts the fee a second time. On a timeout,
re-read the order with GET /partner/{order_id} and only resend if the status has
not moved.
Check the payout after confirming:
curl -sS https://partners.sasomapi.com/partner/payout/kR3mQp7xY2nB8vLc4dFg \
-u "$SASOM_API_KEY:$SASOM_API_SECRET"
{ "order_id": "kR3mQp7xY2nB8vLc4dFg", "expected_payout": 5325 }
Step 7 — Set tracking and ship
Once you have a tracking code from your carrier:
curl -sS -X PUT https://partners.sasomapi.com/partner/update/tracking \
-u "$SASOM_API_KEY:$SASOM_API_SECRET" \
-H 'Content-Type: application/json' \
-d '{
"order_ids": ["kR3mQp7xY2nB8vLc4dFg"],
"tracking_code": "TH12345678"
}'
{ "status": "success" }
Note the field names: order_ids (an array) and tracking_code. There is no
order_id, tracking_number or logistics_provider field. Order IDs that do not
exist are skipped silently and still return 200, so reconcile afterwards.
Then download the shipping label:
curl -sS https://partners.sasomapi.com/partner/label/kR3mQp7xY2nB8vLc4dFg \
-u "$SASOM_API_KEY:$SASOM_API_SECRET"
{ "status": "success", "url": "https://sasom-label-bucket-prod.s3..." }
The PDF has the buyer's full name, address and phone number, and the URL grants access to anyone holding it for up to an hour. Do not log it or share it. Download the PDF, use it, and do not retain buyer data beyond your fulfilment needs.
Finally, confirm shipping (order must be matched):
curl -sS -X PUT https://partners.sasomapi.com/partner/update/shipping \
-u "$SASOM_API_KEY:$SASOM_API_SECRET" \
-H 'Content-Type: application/json' \
-d '{"order_id": "kR3mQp7xY2nB8vLc4dFg"}'
{ "message": "success: shipping confirmed" }
A working client
const BASE_URL = 'https://partners.sasomapi.com';
const apiKey = process.env.SASOM_API_KEY;
const apiSecret = process.env.SASOM_API_SECRET;
if (!apiKey || !apiSecret) {
throw new Error('SASOM_API_KEY and SASOM_API_SECRET must be set');
}
const authorization = `Basic ${Buffer.from(`${apiKey}:${apiSecret}`).toString('base64')}`;
class SasomApiError extends Error {
constructor(status, body) {
super(body?.message ?? body?.errorMessage ?? `SASOM API ${status}`);
this.status = status;
this.code = body?.code ?? null;
}
}
async function sasomRequest(method, path, body) {
const response = await fetch(`${BASE_URL}${path}`, {
method,
headers: {
Authorization: authorization,
'Content-Type': 'application/json',
'Accept-Encoding': 'gzip',
},
...(body ? { body: JSON.stringify(body) } : {}),
});
const payload = await response.json().catch(() => null);
// No envelope: on 2xx the body IS the result. Never check `payload.success`.
if (!response.ok) throw new SasomApiError(response.status, payload);
return payload;
}
async function createAskListing({ sku, size, price }) {
// The API clamps a low price instead of rejecting it, so guard here.
if (!Number.isFinite(price) || price < 50) {
throw new Error(`Refusing to list ${sku} at ${price} — minimum is 50 THB`);
}
const created = await sasomRequest('POST', '/partner/list/ask', {
sku,
size,
price,
});
// A 201 with no order_id means nothing was created (e.g. vacation mode).
if (!created.order_id) {
throw new Error(
`Listing not created for ${sku}: ${created.status ?? 'unknown reason'}`,
);
}
return created.order_id;
}
async function fetchProcessingOrders() {
const all = [];
let cursor = null;
do {
const query = cursor ? `?cursor=${encodeURIComponent(cursor)}` : '';
const page = await sasomRequest('GET', `/partner/processing/orders${query}`);
all.push(...page.orders);
cursor = page.nextCursor;
if (cursor) await new Promise((r) => setTimeout(r, 250));
} while (cursor);
return all;
}
Next
- Listing Management — ask vs pre-order vs R2S, repricing, and what locks a price
- Order Management — the full order lifecycle
- Rate Limiting — bulk pricing and the catalog mirror
- Error Handling — real error bodies and the traps