Skip to main content

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_key and secret from 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:

StatusMost 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_map keys 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"}.

Pricing more than a handful of SKUs

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 quantity field on an ask listing. One call creates one unit. For multiple units, create multiple listings, or use a pre-order with a quota.
  • A price below 50 THB is silently clamped to 50, not rejected. You get a normal 201 and a live listing at a price you did not set. Validate first.
  • 201 does not always mean created. If your account is in vacation mode you get 201 with {"status":"user is in vacation mode"} and no order_id. Always check for order_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" }
Send this exactly once

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 label contains buyer personal data

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​