Seller API
Connect your inventory system, bot or scraper to Lootmarket: upload and sync listings in bulk, top up the auto-delivery stock, change prices, receive paid deals via webhooks and deliver goods automatically.
https://45-88-14-102.sslip.io/api/v1Quick start
- Create a key in your account → API and webhooks. The key is shown only once.
- Find a category for your goods with
GET /gamesand its fields withGET /games/{slug}. - Upload listings in one
POST /listings/bulkrequest with yourexternal_idvalues and repeat it on every sync. - For auto-delivery, top up the stock with
POST /listings/{id}/stock; for manual delivery, subscribe to thedeal.paidwebhook.
curl https://45-88-14-102.sslip.io/api/v1/me \
-H "Authorization: Bearer mk_your_key"Authentication
Every request is signed with an API key of the form mk_…. Pass it in the Authorization: Bearer <key> or X-Api-Key: <key> header. We store only a hash of the key; your account shows its prefix, creation date and last use. Revoke a compromised key in your account — it stops working immediately.
A key has permissions (scopes). A request without the required scope gets 403.
| Scope | What it allows |
|---|---|
listings:read | Читання оголошень і складу |
listings:write | Створення та зміна оголошень, цін і складу |
deals:read | Читання угод і чатів |
deals:write | Видача товару та повідомлення в чатах угод |
GET /me, GET /games and GET /games/{slug} are available to any valid key.
Responses and errors
All responses are UTF-8 JSON. A successful response contains data and, for lists and batch operations, meta. Fields are in snake_case, dates are ISO 8601 in UTC.
{
"data": { "id": "cm1…", "title": "1000 V-Bucks", "price_kopecks": 49900, "price_rub": "499.00" },
"meta": { "limit": 50, "has_more": false, "next_cursor": null }
}{
"error": {
"code": "validation_error",
"message": "price_kopecks: Минимальная цена — 1 ₽",
"details": [{ "path": "price_kopecks", "message": "Минимальная цена — 1 ₽" }]
}
}The message texts are human-readable and currently in Russian. Branch your code on code and details[].path, not on the message text.
| HTTP | code | When |
|---|---|---|
| 400 | bad_request, invalid_json | Invalid parameters or the request body is not JSON |
| 401 | unauthorized | No key, or the key is invalid or revoked |
| 403 | forbidden | The key lacks the required scope, or the action is forbidden |
| 404 | not_found | The object does not exist or belongs to another user |
| 409 | conflict | external_id is already used by another listing |
| 422 | validation_error, unprocessable | Validation failed, or the action is not allowed in the current status |
| 429 | rate_limited | Rate limit exceeded |
| 500 | internal_error | Error on our side — retry later |
Limits and pagination
No more than 120 requests per minute per key. Every response carries the X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (unix time when the window resets) headers. Over the limit you get 429 and Retry-After in seconds. Use the batch endpoints for bulk work: one bulk call with 500 listings counts as a single request.
Lists are paginated: limit from 1 to 100 (default 50) and cursor. Request the next page with cursor=meta.next_cursor until meta.has_more becomes false.
curl "https://45-88-14-102.sslip.io/api/v1/listings?status=ACTIVE&limit=100&cursor=cm1abc…" -H "Authorization: Bearer $KEY"| Operation | Per-request limit |
|---|---|
| POST /listings/bulk | 500 listings |
| PATCH /listings/prices | 1000 items |
| POST /listings/{id}/stock | 5000 lines, up to 2000 characters each |
| POST /api/upload | file up to 8 MB, 30 uploads per minute |
Money
All amounts are in Russian rubles, as integer kopecks (1 ruble = 100 kopecks) in *_kopecks fields. For convenience a ruble string *_rub with two decimal places is duplicated next to it. When writing you may pass either: price_kopecks: 49900 or price_rub: "499.00" (also "499,00" and the number 499). If both are passed, price_kopecks wins.
Profile and catalog
Profile and balance
/api/v1/me{
"data": {
"id": "cm0…", "username": "shop_best", "rating": 4.9, "sales_count": 1520,
"balance_kopecks": 1250000, "balance_rub": "12500.00",
"frozen_balance_kopecks": 34900, "frozen_balance_rub": "349.00",
"active_listings": 87, "open_sales": 3,
"api_key": { "prefix": "mk_AbC12dE", "scopes": ["listings:read", "listings:write", "deals:read", "deals:write"] }
}
}Game list
/api/v1/games?type=GAME&q=fortnitetype is GAME, MOBILE_GAME or APPLICATION; q searches by name. Each game in the response has a list of categories with an id — you need it to create listings.
Game with category fields
/api/v1/games/{slug}Returns categories and their fields (fields). Field values go into the listing's attributes object under the field's key. For SELECT the value must match one of the options, for NUMBER it is a number, and fields with required: true are mandatory.
{
"data": {
"id": "cm0…", "slug": "fortnite", "name": "Fortnite", "type": "GAME",
"categories": [{
"id": "cm0cat…", "slug": "v-bucks", "name": "В-баксы",
"fields": [
{ "key": "platform", "label": "Платформа", "type": "SELECT", "options": ["PC", "PlayStation", "Xbox"], "required": true },
{ "key": "amount", "label": "Количество", "type": "NUMBER", "options": [], "required": false }
]
}]
}
}Listings
A listing belongs to the key owner; other sellers' listings do not exist for the API (404). If the marketplace has moderation enabled, a new listing gets the PENDING status, and changing the price, title or category of an active listing sends it back for review.
| Field | Type | Description |
|---|---|---|
category_id | string | Category from /games — required on create |
title | string | 3–120 characters |
description | string | up to 5000 characters |
price_kopecks | price_rub | int | string | Price per unit, from 1 ₽ to 1,000,000 ₽ |
old_price_kopecks | int | null | Strikethrough price, must be higher than the current one |
quantity | int | Quantity for MANUAL; for AUTO it is counted from the stock |
delivery_type | MANUAL | AUTO | Manual delivery in chat or auto-delivery from the stock |
delivery_info | string | Instructions for the buyer after payment |
attributes | object | Category field values {key: value} |
images | string[] | Up to 10 URLs: from POST /api/upload or external https |
external_id | string | Your item id, unique per seller |
stock | string[] | AUTO only: lines to add to the stock |
buyer_fields | object[] | null | Up to 5 fields the buyer fills in at checkout (player ID, server): {key, label, type: text|number|email|select, required, options, placeholder, help}. Omitted — unchanged; null or [] — no fields |
variants | object[] | null | Up to 30 product variants with their own price and quantity (see “Product variants”). Omitted — unchanged; null or [] — no variants |
rental_unit | HOUR | DAY | null | Account rental — only in categories with rental_allowed: the price is per hour or per day, quantity is how many accounts are rented out at once; MANUAL only, no variants. null — a regular sale |
rental_min_units | rental_max_units | int | null | Rental: order term in hours or days (at most 720 hours or 90 days). Omitted — unchanged |
List listings
/api/v1/listings?status=ACTIVE,PAUSED&categoryId=&external_id=&cursor=&limit=listings:readstatus and external_id accept several comma-separated values.
Create a listing
/api/v1/listingslistings:writecurl -X POST https://45-88-14-102.sslip.io/api/v1/listings \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{
"external_id": "vbucks-1000-pc",
"category_id": "cm0cat…",
"title": "1000 V-Bucks to your account",
"description": "Top-up via gift code, 5 minutes.",
"price_rub": "499.00",
"quantity": 50,
"delivery_type": "MANUAL",
"attributes": { "platform": "PC" },
"images": ["/uploads/listings/3f9c….webp"]
}'{
"data": {
"id": "cm1…", "short_id": "6992fc5806ba", "external_id": "vbucks-1000-pc",
"url": "https://45-88-14-102.sslip.io/products/6992fc5806ba-1000-v-bucks-to-your-account",
"status": "ACTIVE", "price_kopecks": 49900, "price_rub": "499.00",
"old_price_kopecks": null, "old_price_rub": null, "quantity": 50, "delivery_type": "MANUAL", …
}
}Get, update, delete
/api/v1/listings/{id}listings:read/api/v1/listings/{id}listings:write/api/v1/listings/{id}listings:writeYou can pass short_id instead of id. PATCH changes only the fields you pass. DELETE removes the listing; if it already has deals, it is archived instead (archived: true), and with deals still in progress it returns 422.
curl -X PATCH https://45-88-14-102.sslip.io/api/v1/listings/cm1… -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" -d '{"price_kopecks": 45900, "old_price_kopecks": 49900}'Change status
/api/v1/listings/{id}/statuslistings:write{"status": "PAUSED"} hides the listing from the storefront, ACTIVE brings it back, ARCHIVED archives it. Only an active listing can be paused; a listing under moderation or a rejected one cannot be activated (422).
Bulk upload and sync (bulk upsert)
/api/v1/listings/bulklistings:writeUp to 500 listings per request. external_id is required — it is the idempotency key: if you already have a listing with that external_id, it is updated (the fields you pass override the current ones); otherwise a new one is created. Repeating the same request creates no duplicates, so you can run the sync on a schedule. An error in one item does not cancel the others — you get a result for each. The optional status field (ACTIVE | PAUSED | ARCHIVED) lets you take items you no longer have off sale in the same call.
curl -X POST https://45-88-14-102.sslip.io/api/v1/listings/bulk \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{
"items": [
{ "external_id": "sku-1001", "category_id": "cm0cat…", "title": "Account with 50 skins",
"price_kopecks": 150000, "delivery_type": "AUTO", "attributes": { "platform": "PC" },
"stock": ["login1:pass1", "login2:pass2"] },
{ "external_id": "sku-1002", "category_id": "cm0cat…", "title": "2800 V-Bucks",
"price_rub": 1290, "quantity": 20, "attributes": { "platform": "PC" } },
{ "external_id": "sku-0999", "status": "ARCHIVED" }
]
}'{
"data": [
{ "index": 0, "external_id": "sku-1001", "result": "created", "id": "cm1…", "short_id": "a1b2c3d4e5f6", "status": "ACTIVE" },
{ "index": 1, "external_id": "sku-1002", "result": "updated", "id": "cm1…", "short_id": "0f9e8d7c6b5a", "status": "PENDING",
"quality": [{ "code": "contacts", "level": "block", "field": "description", "message": "В описании есть ссылка. Контакты и ссылки запрещены: общение и оплата — только через сделку на площадке" }] },
{ "index": 2, "external_id": "sku-0999", "result": "error",
"error": { "code": "unprocessable", "message": "Выберите категорию" } }
],
"meta": { "total": 3, "created": 1, "updated": 1, "errors": 1 }
}To update an existing listing, external_id and the changed fields are enough. To only archive a listing, use POST /listings/{id}/status or pass its full data together with status. On update, lines from stock are added to the stock (duplicates are skipped) rather than replacing it.
Bulk price update
/api/v1/listings/priceslistings:writeUp to 1000 items. A listing is identified by id or external_id. If the new price is not lower than the old strikethrough price and old_price_kopecks is not passed, the strikethrough price is removed.
curl -X PATCH https://45-88-14-102.sslip.io/api/v1/listings/prices -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"items": [
{"external_id": "sku-1001", "price_kopecks": 139000},
{"id": "cm1…", "price_rub": "99.90", "old_price_kopecks": 14900}
]}'{
"data": [
{ "index": 0, "id": "cm1…", "external_id": "sku-1001", "result": "updated", "price_kopecks": 139000, "price_rub": "1390.00", "status": "ACTIVE" },
{ "index": 1, "id": "cm1…", "external_id": null, "result": "updated", "price_kopecks": 9990, "price_rub": "99.90", "status": "ACTIVE" }
],
"meta": { "total": 2, "updated": 2, "errors": 0 }
}Product variants
One product — several variants with their own price and quantity: “Region: RU / EU / Global”, “80 / 400 / 800 Robux”. The buyer picks a variant on the product page, and the deal uses the variant's price and stock. The price of a listing with variants (price_kopecks, has_variants: true) is the cheapest active variant, shown as “from X ₽”; quantity is the sum of active variants' stock. Variant parameters (attributes) are category field keys from GET /games/{slug} (the value must be one of their options) or your own names, up to 3 parameters. For AUTO listings each variant has its own code stock: stock in the variant or POST …/stock with variant_id.
curl -X POST https://45-88-14-102.sslip.io/api/v1/listings -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"category_id": "cm0…", "title": "Робуксы, быстрая выдача", "delivery_type": "MANUAL",
"variants": [
{"attributes": {"Количество": "400"}, "price_rub": "399", "quantity": 50},
{"attributes": {"Количество": "800"}, "price_rub": "749", "quantity": 20},
{"title": "1700 робуксов", "price_kopecks": 149000, "old_price_kopecks": 169000, "quantity": 5}
]}'In POST/PATCH /listings and bulk the variants list is complete: a variant with an id is updated, one without an id is created, a missing one is deleted (archived if it has deals). A variant's quantity changes only when passed; stock adds codes. For single changes use:
/api/v1/listings/{id}/variantslistings:read/api/v1/listings/{id}/variantslistings:write/api/v1/listings/{id}/variants/{variantId}listings:write/api/v1/listings/{id}/variants/{variantId}listings:write{
"data": {
"id": "cm3…", "title": "400", "attributes": { "Количество": "400" },
"price_kopecks": 39900, "price_rub": "399.00", "old_price_kopecks": null, "old_price_rub": null,
"quantity": 50, "sort_order": 0, "is_active": true
}
}A variant's price can also be changed in PATCH /listings/prices with a variant_id field. In deals (GET /deals, webhooks) the variant comes in variant / variantId, and the deal's unit_price is the variant's price.
Quality check: the quality field
Every created or updated listing goes through an automatic check: contacts and links that take the deal off the platform, banned words, an all-caps or spammy title, a price far from the market, duplicates, an account without a picture, auto-delivery without stock. If the check finds problems, the responses of POST /listings, GET and PATCH /listings/{id}, POST …/status, POST …/stock, and every item of bulk and prices carry a quality array. No problems — no field.
| Field | Type | Description |
|---|---|---|
code | string | Rule code: contacts, banned_words, price_low, duplicate… |
level | block | warn | block — the listing is not published; warn — it is published, but a moderator will review it |
message | string | Reason for the seller (in Russian) |
field | string? | Listing field: title, description, price, images, category, stock |
With a block issue the listing gets status: "PENDING", and reject_reason holds the reason prefixed with «Автопроверка: ». Fix the listing and send it again (PATCH or bulk): once no problems remain, it goes back on sale by itself. The exception is auto-delivery without stock: such a listing gets SOLD and becomes active after a stock top-up. warn issues do not stop publication.
{
"data": {
"id": "cm1…", "status": "PENDING",
"reject_reason": "Автопроверка: В описании есть ссылка. Контакты и ссылки запрещены: общение и оплата — только через сделку на площадке",
…,
"quality": [
{ "code": "contacts", "level": "block", "field": "description", "message": "В описании есть ссылка. Контакты и ссылки запрещены: общение и оплата — только через сделку на площадке" },
{ "code": "caps_title", "level": "warn", "field": "title", "message": "Название набрано заглавными буквами" }
]
}
}Auto-delivery stock
For listings with delivery_type: "AUTO" the goods are delivered to the buyer right after payment — one stock line per unit (a code, login:password, a link). The quantity of such a listing always equals the number of unsold lines. Empty lines and duplicates (in the request and in the stock) are skipped. If the listing was sold out, topping up the stock makes it active again.
/api/v1/listings/{id}/stocklistings:writecurl -X POST https://45-88-14-102.sslip.io/api/v1/listings/cm1…/stock \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"items": ["XXXX-YYYY-ZZZZ-0001", "XXXX-YYYY-ZZZZ-0002", "XXXX-YYYY-ZZZZ-0003"]}'{ "data": { "added": 3, "skipped": 0, "in_stock": 42, "status": "ACTIVE" } }/api/v1/listings/{id}/stock?sold=false&limit=500&offset=0listings:read{
"data": [
{ "id": "cm2…", "content": "XXXX-YYYY-ZZZZ-0001", "is_sold": false, "deal_number": null, "created_at": "2026-09-27T10:00:00.000Z" },
{ "id": "cm2…", "content": "XXXX-YYYY-ZZZZ-0000", "is_sold": true, "deal_number": 10452, "created_at": "2026-09-26T08:12:00.000Z" }
],
"meta": { "total": 43, "unsold": 42, "sold": 1, "limit": 500, "offset": 0 }
}/api/v1/listings/{id}/stocklistings:writeDeletes unsold lines: {"ids": ["cm2…", "cm2…"]} or the whole unsold stock {"all": true}. Sold lines cannot be deleted — they remain as the delivery history.
Image upload
POST /api/upload (outside /v1) accepts multipart/form-data with the file and kind fields (listings, avatars, shop — the shop banner, or chat). JPEG, PNG, WEBP and GIF up to 8 MB are allowed — the format is checked by the file contents. Works with an API key that has the listings:write scope. Pass the returned url to the listing's images or to chat attachments.
curl -X POST https://45-88-14-102.sslip.io/api/upload -H "Authorization: Bearer $KEY" \
-F "kind=listings" -F "file=@cover.webp"
{ "url": "/uploads/listings/9b1f0c2e4a7d6e3f1a2b3c4d.webp" }Errors from this endpoint look like {"error": "text", "code": "invalid_file"}.
Deals
Deal list and deal card
/api/v1/deals?role=seller&status=PAID,DELIVERED&cursor=&limit=deals:read/api/v1/deals/{number}deals:readrole is seller (default, your sales) or buyer. A deal is identified by its number, the same as on the website.
buyer_input is what the buyer entered at checkout for the listing's buyer_fields (player ID, server): an array of {key, label, value}, empty if there were no fields.
{
"data": {
"id": "cm3…", "number": 10452, "role": "seller", "status": "PAID",
"listing": { "id": "cm1…", "short_id": "6992fc5806ba", "external_id": "sku-1002", "title": "2800 V-Bucks" },
"buyer": { "id": "cm0…", "username": "player42" },
"quantity": 2,
"unit_price_kopecks": 129000, "unit_price_rub": "1290.00",
"amount_kopecks": 258000, "amount_rub": "2580.00",
"total_kopecks": 270900, "total_rub": "2709.00",
"seller_fee_kopecks": 25800, "seller_fee_rub": "258.00",
"seller_payout_kopecks": 232200, "seller_payout_rub": "2322.00",
"with_guarantee": false,
"buyer_input": [{ "key": "player_id", "label": "ID игрока", "value": "812345678" }],
"delivered_content": null,
"paid_at": "2026-09-27T10:05:00.000Z", "auto_complete_at": null
}
}Deliver goods
/api/v1/deals/{number}/deliverdeals:writeFor manual delivery: pass the data for the buyer in content (up to 5000 characters, optional) — it is saved in the deal, and the deal moves to DELIVERED. Works only for a paid deal where you are the seller.
curl -X POST https://45-88-14-102.sslip.io/api/v1/deals/10452/deliver -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" -d '{"content": "Code: AAAA-BBBB-CCCC"}'Deal chat
/api/v1/deals/{number}/messages?after=2026-09-27T10:00:00Z&limit=100deals:read/api/v1/deals/{number}/messagesdeals:writeafter returns only messages newer than the given time (handy for polling), mark_read=true marks them as read. Marketplace system messages have is_system: true and sender_id: null.
curl -X POST https://45-88-14-102.sslip.io/api/v1/deals/10452/messages -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" -d '{"text": "Hello! Delivering your order within 5 minutes."}'Deal statuses
| Status | Meaning | What the seller does |
|---|---|---|
PENDING_PAYMENT | Created, awaiting payment | Nothing — the item is reserved |
PAID | Paid, the marketplace holds the money | Deliver the goods and call /deliver (AUTO delivers itself) |
DELIVERED | Delivered, awaiting confirmation | Wait; the deal closes automatically on a timer |
COMPLETED | Completed | Payout goes to hold, then to the balance |
DISPUTED | Dispute opened | Reply in the chat; a moderator decides |
REFUNDED | Money refunded to the buyer | — |
CANCELLED | Cancelled before payment | — |
Webhooks
Add an endpoint in your account and choose the events. When an event happens we send a POST with a JSON body. Only https:// URLs are accepted. Reply with a 2xx code within 5 seconds; otherwise there will be 3 more attempts (after 2, 10 and 30 seconds). A 4xx response other than 408 and 429 is treated as a final rejection. Delivery order is not guaranteed and repeats are possible — check data.status and store the X-Delivery-Id values you have processed.
| Event | When |
|---|---|
deal.paid | Угоду оплачено — потрібно видати товар |
deal.delivered | Товар позначено виданим |
deal.completed | Угоду завершено, виплату нараховано |
deal.disputed | Покупець відкрив спір |
deal.refunded | Спір вирішено поверненням покупцю |
deal.cancelled | Угоду скасовано до оплати |
webhook.test | The “Send test” button in your account |
| Header | Value |
|---|---|
X-Event | Event name |
X-Signature | sha256=<hex HMAC-SHA256 of the raw body with your secret> |
X-Delivery-Id | Delivery id, the same across retries of one event |
X-Delivery-Attempt | Attempt number: 1–4 (a manual retry from the log continues the count) |
X-Webhook-Id | Webhook id in your account |
{
"event": "deal.paid",
"created_at": "2026-09-27T10:05:01.123Z",
"data": {
"dealId": "cm3…", "number": 10452, "listingId": "cm1…", "buyerId": "cm0…",
"quantity": 2, "amount": 258000, "sellerPayout": 232200, "status": "PAID",
"buyerInput": [{ "key": "player_id", "label": "ID игрока", "value": "812345678" }]
}
}Amounts in data are in kopecks. buyerInput holds the buyer's checkout data (player ID, server), [] if there were no fields. Get the full deal card with GET /deals/{number}. Verify the signature against the raw request body before parsing the JSON, and compare in constant time.
import express from "express";
import crypto from "crypto";
const SECRET = process.env.WEBHOOK_SECRET; // whsec_...
const app = express();
app.post("/hooks/market", express.raw({ type: "application/json" }), (req, res) => {
const expected = "sha256=" + crypto.createHmac("sha256", SECRET).update(req.body).digest("hex");
const got = req.get("X-Signature") || "";
const ok = got.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected));
if (!ok) return res.status(401).end();
const { event, data } = JSON.parse(req.body.toString("utf8"));
if (event === "deal.paid") {
// deliver the goods: POST /api/v1/deals/{data.number}/deliver
}
res.status(200).end();
});
app.listen(3001);import hmac, hashlib, os
from flask import Flask, request, abort
SECRET = os.environ["WEBHOOK_SECRET"].encode()
app = Flask(__name__)
@app.post("/hooks/market")
def hook():
body = request.get_data() # raw bytes
expected = "sha256=" + hmac.new(SECRET, body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, request.headers.get("X-Signature", "")):
abort(401)
event = request.get_json()
if event["event"] == "deal.paid":
pass # deliver the goods: POST /api/v1/deals/<number>/deliver
return "", 200SDKs and examples
Ready-made API clients for JavaScript/TypeScript and Python: types generated from OpenAPI, retries after 429 that honour Retry-After, iterators over all pages, batches of any size (split to the API limits) and webhook signature verification. The archives contain the sources, a ready build, examples and tests.
| Feature | JavaScript / TypeScript | Python |
|---|---|---|
| Dependencies | none (fetch, WebCrypto) | requests |
| Runtime | Node 18+, Deno, Bun, Workers | Python 3.9+ |
| Types | from OpenAPI (openapi-typescript) | TypedDict from OpenAPI, py.typed |
| Rate limit 429 | waits Retry-After and retries | waits Retry-After and retries |
| Pagination | for await … iterate() | for … in iter() |
| Webhook signature | constructWebhookEvent() | construct_webhook_event() |
JavaScript / TypeScript
npm install ./marketplace-sdk-jsimport { MarketplaceClient, constructWebhookEvent } from "marketplace-seller-sdk";
const client = new MarketplaceClient({ apiKey: process.env.MARKET_API_KEY, baseUrl: "https://45-88-14-102.sslip.io" });
// bulk upload by external_id: new ones are created, known ones updated
const { meta } = await client.listings.bulkUpsert(items); // any number — batches of 500
await client.listings.updatePrices([{ external_id: "sku-1", price_rub: "449.00" }]);
for await (const deal of client.deals.iterate({ status: "PAID" })) {
await client.deals.deliver(deal.number, "KEY-XXXX");
}
// webhook: raw body, request headers and the secret
const { event, deliveryId } = await constructWebhookEvent(rawBody, req.headers, process.env.WEBHOOK_SECRET);Python
pip install ./marketplace-sdk-pythonfrom marketplace_sdk import MarketplaceClient, construct_webhook_event
client = MarketplaceClient(os.environ["MARKET_API_KEY"], "https://45-88-14-102.sslip.io")
results, meta = client.listings.bulk_upsert(items) # batches of 500
client.listings.update_prices([{"external_id": "sku-1", "price_rub": "449.00"}])
for deal in client.deals.iter(status="PAID"):
client.deals.deliver(deal["number"], "KEY-XXXX")
hook = construct_webhook_event(request.get_data(), request.headers, WEBHOOK_SECRET) # FlaskExamples in the archives
| Example | What it does |
|---|---|
import-csv · import_csv.py | Bulk catalog upload from CSV by external_id (template catalog.example.csv: prices, quantity, stock codes separated by “|”, category fields attr:<key>). Safe to re-run; rows with errors go to a separate CSV. |
sync-prices-stock · sync_prices_stock.py | Price and stock sync from your feed: sends only the changes, adds new auto-delivery codes, and with --pause-missing pauses what is missing from the feed. Meant to run on a schedule. |
webhook-autodelivery · webhook_autodelivery.py | Webhook receiver: signature check, duplicate protection by X-Delivery-Id, and on deal.paid a code from your pool plus delivery. Test events from the tester are acknowledged but not processed. |
You can test your handler without real purchases in your account: the “Webhook tester” sends a sample of any event to your URL and shows the response code and time, and the “Delivery log” shows every attempt for 7 days — a failed one can be sent again with the “Retry” button (same X-Delivery-Id).
Postman / Insomnia
Import postman_collection.json (Postman: Import; Insomnia: Import → From File — Postman v2.1 format, or openapi.json itself). Set the collection variables baseUrl = https://45-88-14-102.sslip.io/api/v1, siteUrl = https://45-88-14-102.sslip.io and apiKey = your key. Requests are grouped by section, and the ones that change data come with an example body.
OpenAPI
The machine-readable OpenAPI 3.1 specification is at /api/v1/openapi.json. You can import it into Postman or Insomnia, or generate a client from it. Its descriptions are in Russian.