Soukbase API
Base URL: https://soukbase.com. Machine description: /openapi.json (catalog at /.well-known/api-catalog). Reading needs no authentication. Sellers sign in with a wallet signature: see /.well-known/auth.md. There are no API keys. Checkout is not on this domain; it runs at the seller's APIbase Integrator shop. Read-only MCP: POST /mcp (tools: search_listings, get_listing, get_seller, list_categories, get_checkout, trust_get; same limits as the read group).
All bodies are JSON. All prices are strings with two decimals in USDC, for example "12.50". Every error has the same shape: {"error": "...", "code": "...", "field": "...", "next_action": "..."}. Match on code; the wording of error can change.
Routes
<!-- generated:routes -->
| Route | Auth | What it does |
|---|---|---|
| POST /api/v1/auth/nonce | none | Get a single-use nonce for a sign-in message |
| POST /api/v1/auth/siwe | wallet signature in body | Exchange a signed session message for a bearer token |
| GET /api/v1/handles/{handle} | none | Check if a seller handle is free |
| POST /api/v1/sellers | wallet signature in body | Create a seller from a signed wallet message |
| GET /api/v1/sellers/me | session token | Read your own seller record: profile, shop connection and your listings |
| PATCH /api/v1/sellers/me | session token | Change your profile; connect, change or disconnect your Integrator shop (checked against the shop at once; not reserved until verified) |
| GET /api/v1/sellers/{handle} | none | Read a seller's public record |
| POST /api/v1/listings | session token | Create a listing and send it to review |
| PATCH /api/v1/listings/{id} | session token | Change a listing you own |
| GET /api/v1/listings | none | Search live listings |
| GET /api/v1/listings/{id} | none (token adds owner view) | Read one listing (JSON plus schema.org JSON-LD) |
| GET /api/v1/listings/{id}/publish-request | session token | Get the Integrator catalog request for an approved listing |
| GET /api/v1/trust/{sellerHandle} | none | trust_get for a seller (fields in /docs/trust.md) |
| GET /api/v1/trust/listing/{id} | none | trust_get for one live listing, with an optional quote check (fields in /docs/trust.md) |
| POST /api/v1/reports | none | Report a listing or an order |
| GET /api/v1/stats | none | Public counts: sellers, listings, paid orders by class (what each means: /docs/status.md) |
| GET /aup.json | none | The prohibited-goods rule list, check ids and its ruleset_hash |
| GET /openapi.json | none | This API as OpenAPI 3.1 |
<!-- /generated:routes -->
POST /hooks/telegram is the operator's review webhook. It accepts only Telegram's calls with the bot's secret header and answers 403 to anyone else; it is not part of the API.
POST /hooks/integrator is the Integrator's order webhook (signed, see /docs/sellers.md). The public listing feed is GET /feed/products.jsonl, GET /feed/products.json (live listings only, licence CC BY 4.0 via soukbase.com, keep source_url and source_hash) and GET /feed/newest.xml (Atom, the 50 newest). All three answer ETag, If-None-Match and If-Modified-Since with 304.
Limits
<!-- generated:limits --> Rate limits. Every answer carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset; a refused request gets HTTP 429 and Retry-After (seconds until the window resets). For reports only accepted reports count toward the daily limit; refused attempts are counted separately, per IP.
| Group | Limit | Counted per | Applies to |
|---|---|---|---|
| read | 120 per 1 minute | IP | every GET under /api/v1 and /openapi.json |
| sellerWrite | 60 per 1 hour | wallet | creating a seller, creating a listing, changing a listing |
| reports | 10 per 24 hours | wallet (IP when no wallet is given) | accepted reports only; a refused report does not count |
| reportsRefused | 100 per 1 hour | IP | refused report attempts (invalid input, unknown listing); a guard against floods, never touches the 10 per day |
| nonce | 30 per 1 minute | IP | getting a nonce and signing in |
Seller limits (until tier T3, then the second row):
| Tier | Price per listing | Open listings | Units per order |
|---|---|---|---|
| T1, T2 | 1.00 to 20.00 USDC | 5 | 1 |
| T3 | 1.00 to 200.00 USDC (the Integrator's new-merchant cap) | not limited | not limited by Soukbase |
An open listing is one that is in review, approved and waiting to publish, live or paused. Rejected and removed listings do not count. Tier T0 cannot list.
Field limits.
- title: 1 to 120 characters; description: 1 to 2000 characters, plain text with paragraphs, lists and
inline code; no HTML and no images. - links: 1 to 20 https URLs, only with
delivery_modeinstant_links, each up to 2048 characters. - refund_window_days: 0 to 30, default 14.
- details (reports): up to 2000 characters. client_ref: up to 64 characters.
- A repeated
client_refwith the same content within 24 hours returns the existing listing (HTTP 200,Idempotent-Replay: true); different content givesidempotency_conflict. - Search pages: 20 items by default, at most 100 (
limit).
<!-- /generated:limits -->
Examples
Sign in and create a seller
Get a nonce, build the EIP-4361 message from the values in the answer, sign it with the wallet (EIP-191 personal_sign), and send both. The exact message and statement are in /.well-known/auth.md.
curl -s https://soukbase.com/api/v1/auth/nonce \
-H 'content-type: application/json' \
-d '{"address":"0xYourWalletAddress","purpose":"seller_bind"}'
{"nonce":"3f9c...","purpose":"seller_bind","expires_at":"2026-11-02T12:10:00.000Z","domain":"soukbase.com","uri_origin":"https://soukbase.com","chain_ids":[8453],"documents":[{"name":"seller-agreement","resource":"https://soukbase.com/legal/seller-agreement.md#sha256=..."}]}
curl -s https://soukbase.com/api/v1/sellers \
-H 'content-type: application/json' \
-d '{"handle":"acme-data","display_name":"Acme Data","contact_email":"owner@acme.example","country":"DE","consents":{"agreement":true,"sanctions":true,"public_receipts":true},"siwe":{"message":"...","signature":"0x..."}}'
{"handle":"acme-data","display_name":"Acme Data","wallet_address":"0x...","url":"https://soukbase.com/s/acme-data","status":"active","tier":"T1","class":"external","integrator_slug":null,"shop":{"slug":null,"storefront_url":null,"status":"not_connected","payout":{...}},"next_action":"Sign in ..."}
integrator_slug is optional: you can register without an Integrator shop and connect it later (below). When you send it, the shop is checked before anything is saved, with the same codes as the PATCH below.
A known wallet that sends the same request again, with the same handle, renews an expired binding and gets 200 with the same body.
Then sign a second message with purpose session and exchange it:
curl -s https://soukbase.com/api/v1/auth/siwe \
-H 'content-type: application/json' \
-d '{"message":"...","signature":"0x..."}'
{"token":"...","token_type":"Bearer","expires_at":"2026-11-03T12:00:00.000Z","address":"0x..."}
Check a handle
curl -s https://soukbase.com/api/v1/handles/acme-data
{"handle":"acme-data","available":false,"reason":"taken"}
reason is invalid_format, reserved or taken.
Create a listing
New listings go to review. What is checked, and what a person decides, is in /docs/moderation.md.
curl -s https://soukbase.com/api/v1/listings \
-H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
-d '{"category":"prompt_set","title":"Prompt pack for code review","description":"Tested prompts for reviewing pull requests.","delivery_mode":"instant_text","delivery_time":"instant","price_usdc":"5.00","refund_window_days":14,"rights_confirmed":true,"client_ref":"run-1"}'
{"listing":{"id":"...","status":"pending_review","version":1,"price_usdc":"5.00", "...":"..."},"idempotent_replay":false,"next_action":"A person reviews every listing. ..."}
Categories: dataset, prompt_set, template, report, ebook_audio, own_api_credits. Delivery modes: instant_text, instant_links, webhook. Delivery times: instant, 1h, 24h, 72h. Soukbase never stores the delivered goods: you paste them into your own Integrator shop.
Change a listing
curl -s -X PATCH https://soukbase.com/api/v1/listings/LISTING_ID \
-H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
-d '{"price_usdc":"6.00"}'
Changing price, stock or refund window keeps a live listing live and the new version is checked again. Changing title, description, category, links, delivery mode or delivery time creates a new version in review and takes the live version down. A rejected listing is submitted again by any edit. stock is only on this route (null means not limited).
Search listings
curl -s 'https://soukbase.com/api/v1/listings?q=prompts&category=prompt_set&price_max=10.00&delivery_mode=instant_text'
{"items":[{"id":"...","title":"...","price_usdc":"5.00","currency":"USDC","links_count":0,"jsonld":{"@type":"Product","...":"..."}}],"next_cursor":null}
Only live listings are listed. Pass next_cursor back as cursor for the next page. Delivery links are not shown publicly: links_count says how many there are.
Read one listing
curl -s https://soukbase.com/api/v1/listings/LISTING_ID
The answer includes jsonld, a schema.org Product with an Offer (priceCurrency USDC). The id can also be the slug. With the owner's token the answer adds status, links, stock, integrator_sku, rejection_category (the category of the latest rejection, otherwise null) and status_text (a short note on the status, otherwise null; today only more information requested, meaning a reviewer asked for more information and the listing stays pending_review); for anyone else a listing that is not live is a 404.
Your seller record and your shop
GET /api/v1/sellers/me (session) returns your private record: profile, wallet_address, binding_expires_at, the shop block (status is not_connected, connected, unverified or mismatch) and your listings in every status but removed. PATCH /api/v1/sellers/me changes display_name, contact_email, country, site_or_repo_url (null clears it) or connects your Integrator shop. integrator_slug: null disconnects the shop (409 shop_in_use while a listing is approved, live or paused):
curl -s -X PATCH https://soukbase.com/api/v1/sellers/me \
-H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
-d '{"integrator_slug":"acme-shop"}'
The slug is checked at that moment; what is checked and what each answer means is in /docs/sellers.md. An Integrator that does not answer gives integrator_unreachable with Retry-After: 60 and nothing is saved.
Get the request for your Integrator shop
When a listing is approved_pending_publish, the owner can fetch the exact catalog request with the approved fields:
curl -s https://soukbase.com/api/v1/listings/LISTING_ID/publish-request -H "authorization: Bearer $TOKEN"
The answer has curl (run it with your own Integrator key in $MK_LIVE_KEY; Soukbase never sees the key), body, a fulfilment block for the delivery mode, a test_item_note (the Integrator's test item your shop needs once) and a test_order_hint. method is PUT. Soukbase makes the listing live after it checks your public shop; the checks and the whole flow are in /docs/sellers.md.
Read a seller
curl -s https://soukbase.com/api/v1/sellers/acme-data
{"handle":"acme-data","tier":"T1","class":"external","counts":{"listings_live":0,"orders":0},"not_measured":["fulfilment_rate","median_delivery_time","refund_rate","dispute_rate"],"...":"..."}
not_measured lists facts Soukbase does not measure yet. A fact that is not measured is never shown as zero.
Report a listing or an order
curl -s https://soukbase.com/api/v1/reports \
-H 'content-type: application/json' \
-d '{"listing_id":"LISTING_ID","reason":"not_as_described","order_id":"ORDER_ID","reporter_wallet":"0xPayerWallet","details":"What went wrong."}'
Reasons: not_delivered, not_as_described, does_not_work, duplicate_charge (these need order_id, and reporter_wallet must be the order's payer), and prohibited_goods, ip_infringement, prompt_injection, malware, fraud_or_scam, other. One report per order and reason. A report does not trigger a refund: see /legal/refunds.md and /legal/report.md.
Public counts
curl -s https://soukbase.com/api/v1/stats
The same numbers as the /status page. What each field counts and what is excluded: /docs/status.md. Zero is a number; a fact that is not measured is named in not_measured.
Error codes
<!-- generated:errors -->
| Code | HTTP | Meaning | Next step |
|---|---|---|---|
validation_failed | 400 | A field is missing, has the wrong type or is out of range. | Fix the field named in field and resend. |
json_invalid | 400 | The body is not valid JSON. | Send application/json. |
address_invalid | 400 | Not an EVM address (0x + 40 hex). | Send the wallet address. |
url_invalid | 400 | Not an https URL. | Use an https URL. |
handle_invalid | 400 | Handle does not match ^[a-z0-9-]{3,40}$. | Pick another handle. |
handle_reserved | 409 | Handle is a reserved word. | Pick another handle; GET /api/v1/handles/{handle} checks one. |
handle_taken | 409 | Handle belongs to another seller. | Pick another handle. |
seller_exists | 409 | This wallet already has a seller. | Sign in with POST /api/v1/auth/nonce (purpose session). To renew an expired binding, POST /api/v1/sellers again with the same handle. |
consent_required | 400 | A consent is not true. | All three consents must be true. |
rights_not_confirmed | 400 | rights_confirmed is not true. | Confirm you have the right to sell the item. |
price_below_min | 422 | Price is under the minimum. | Use a price at or above the minimum. |
price_above_cap | 422 | Price is above the cap for the seller tier. | Lower the price; the cap is in the error text. |
price_format | 400 | Price must be a decimal string with two decimals, e.g. "12.50". | Send the price as a string. |
links_required | 400 | delivery_mode instant_links needs 1 to 20 links. | Add links or change delivery_mode. |
links_not_allowed | 400 | links are only for delivery_mode instant_links. | Remove links or use instant_links. |
content_not_allowed | 400 | Text has HTML, an image or a control character. | Use plain text and the small Markdown set. |
nothing_to_change | 400 | PATCH body has no field. | Send at least one field. |
message_malformed | 400 | The sign-in message is not the exact EIP-4361 text the server accepts. | Build the message from the values in the nonce answer. |
domain_mismatch | 400 | Message domain is not this site. | Use the domain from the nonce answer. |
uri_domain_mismatch | 400 | Message URI host is not this site. | Use an https URL on this domain. |
address_not_checksummed | 400 | Message address is not EIP-55 checksummed. | Use the checksummed address. |
chain_not_allowed | 400 | Chain id is not accepted. | Use a chain id from the nonce answer. |
issued_at_in_future | 400 | Issued At is in the future. | Use the current time. |
issued_at_too_old | 400 | Issued At is too old. | Request a new nonce and sign again. |
expiration_invalid | 400 | Expiration Time is not after Issued At. | Fix the times. |
expiration_too_long | 400 | Expiration Time is too far away. | Use a shorter validity. |
message_expired | 400 | The message has expired. | Request a new nonce and sign again. |
nonce_unknown | 400 | Nonce was not issued by this server. | POST /api/v1/auth/nonce. |
nonce_used | 400 | Nonce was already used. | Request a new nonce. |
nonce_expired | 400 | Nonce expired. | Request a new nonce. |
nonce_purpose_mismatch | 400 | Nonce was issued for another purpose. | Request a nonce with the right purpose. |
signature_malformed | 400 | Signature is not a 65-byte hex string. | Sign with personal_sign (EIP-191). |
contract_wallet_unsupported | 400 | Contract wallet signatures are not accepted. | Sign with a plain wallet (EOA). |
signature_invalid | 401 | Signature does not match the address. | Sign the exact message again. |
statement_mismatch | 400 | Message statement differs from the required text. | Use the statement template in /.well-known/auth.md. |
documents_incomplete | 400 | Message resources must list the three legal documents once each. | Use the documents in the nonce answer. |
doc_hash_mismatch | 400 | A document hash is not the published one. | Use the hashes in the nonce answer. |
integrator_slug_invalid | 400 | Integrator slug is not valid. | Use the slug of your Integrator shop. |
integrator_slug_taken | 409 | Another seller has connected and verified this Integrator shop. | Connect your own shop; a shop belongs to the seller whose bound wallet it pays. |
integrator_shop_not_found | 422 | No shop with this slug at the Integrator. | Register at apibase.pro/integrator/connect with the same wallet, then enter the slug it gives you. |
integrator_wallet_mismatch | 422 | The shop pays to a wallet other than your bound wallet. | Use the wallet you signed with, or register the shop with that wallet. |
integrator_unreachable | 503 | The Integrator did not answer; nothing was saved. | Retry after the Retry-After seconds. |
shop_in_use | 409 | Your listings are published from this shop. | Contact support@soukbase.com to move your listings to another shop. |
address_sanctioned | 403 | This wallet cannot be used. | Use another wallet or write to support@soukbase.com. |
legal_docs_unavailable | 503 | Server cannot read the current legal documents. | Retry later. |
token_missing | 401 | No Authorization: Bearer token. | Sign in: POST /api/v1/auth/nonce then /api/v1/auth/siwe. |
token_malformed | 401 | Token is not valid. | Sign in again. |
token_bad_signature | 401 | Token is not valid. | Sign in again. |
token_expired | 401 | Token expired. | Sign in again. |
session_revoked | 401 | Session was ended. | Sign in again. |
no_active_binding | 403 | The wallet has no active seller binding. | Sign a seller_bind message and POST /api/v1/sellers; a known wallet sends the same handle and the binding is renewed. |
seller_suspended | 403 | The seller is suspended. | Write to support@soukbase.com. |
seller_not_registered | 403 | No seller exists for this wallet. | Create the seller first: sign a seller_bind message. |
seller_not_active | 403 | The seller is not active. | Write to support@soukbase.com. |
tier_too_low | 403 | Tier T0 cannot list. | Complete the wallet signature step. |
listing_limit_reached | 422 | Open listing cap for the seller tier reached. | Remove or wait for a decision on an existing listing. |
idempotency_conflict | 409 | client_ref was used in the last 24 hours with different content. | Use a new client_ref or resend the same body. |
listing_not_found | 404 | No such listing, or it is not public and you are not the owner. | Check the id. |
seller_not_found | 404 | No such seller. | Check the handle. |
order_not_found | 404 | No such order. | Check order_id. |
not_publishable | 409 | Listing is not approved_pending_publish. | Wait for approval; poll GET /api/v1/listings/{id}. |
listing_removed | 409 | The listing was removed. | Create a new listing. |
listing_transition_invalid | 409 | The listing state does not allow this. | Read the status with GET /api/v1/listings/{id}. |
reporter_not_payer | 403 | An order report must come from the order's payer wallet. | Send reporter_wallet = payer. |
order_listing_mismatch | 400 | order_id belongs to a different listing. | Fix listing_id. |
report_duplicate | 409 | This order already has a report with this reason. | No action needed. |
order_required | 400 | This reason needs order_id. | Send the order id. |
intake_paused | 503 | New seller sign-ups are paused for now. | Wait for the Retry-After seconds. Existing sellers can still create and change listings. |
rate_limited | 429 | Rate limit reached. | Wait for the Retry-After seconds. |
not_found | 404 | No such route. | See /openapi.json. |
internal_error | 500 | Unexpected server error. | Retry later. |
<!-- /generated:errors -->