Python SDK for SolPay Seller API
Project description
solpay-seller — Python SDK for Seller API
solpay-seller is a batteries-included Python client for the public Seller API (auth via X-Api-Key: sp_sk_...) used to:
- create/update lots (products & services)
- manage auto-delivery stock for product lots
- read sales orders
- send messages to buyers (order chat)
- manage webhooks (receive events like
order.paid,message.created) - upload images for lots (multipart)
If you only read this README on PyPI, you should be able to write a full bot: create lot → wait for purchase webhook → send random 1..100 → reply ping/pong.
Installation
pip install solpay-seller
Optional extras:
# Pydantic models for typed parsing (optional)
pip install "solpay-seller[models]"
# Docs toolchain (mkdocs) (optional)
pip install "solpay-seller[docs]"
Getting an API key
Create a Seller API key in the dashboard:
- Dashboard → Integrations → API Keys
Key format:
sp_sk_...(secret, shown only once)
You must send it as:
X-Api-Key: sp_sk_...
Quickstart
from solpay_seller import SellerApiClient
client = SellerApiClient(
api_key="sp_sk_...",
base_url="https://solpay.one/api", # IMPORTANT: include /api
)
print(client.me()) # { "id": "..." }
print(client.list_lots(page=1, limit=20)["total"])
Creating a lot (and finding categoryId)
To create a lot you need a valid categoryId. Categories are public:
cats = client.get_categories()
print(cats[0]["id"], cats[0]["name"])
category_id = cats[0]["id"]
Create a product lot (prices are in cents):
lot = client.create_lot({
"type": "PRODUCT",
"title": "Steam key",
"description": "<p>Instant delivery</p>",
"priceUsd": 499,
"categoryId": category_id,
"quantity": 0, # ignored if autoDeliveryEnabled=true
"acceptToken": False,
"images": [],
"tags": ["steam", "key"],
"isActive": True,
# Optional: automatic first message after payment
"autoFirstMessageEnabled": True,
"autoFirstMessageText": "Thanks! If you need help, reply here. (ping -> pong)",
# Optional: auto-delivery settings (unique items pool)
"autoDeliveryEnabled": True,
"autoDeliveryEmptyAction": "BLOCK_PURCHASE", # or "ALLOW_MANUAL"
"autoDeliveryDelimiter": "\\n",
"autoDeliveryNotifyOnEmpty": True,
})
lot_id = lot["id"]
print("lot_id:", lot_id)
Uploading images (for lots)
If you want lot images, upload first, then pass returned URLs in images.
uploaded = client.upload_image("./image.png")
image_url = uploaded["url"] # "/uploads/images/....png"
lot = client.create_lot({
"type": "PRODUCT",
"title": "With image",
"description": "<p>...</p>",
"priceUsd": 199,
"categoryId": category_id,
"quantity": 0,
"acceptToken": False,
"images": [image_url],
"tags": [],
"isActive": True,
})
Multiple images:
res = client.upload_images(["./a.png", "./b.png"])
urls = res["urls"]
Auto-delivery stock (import items)
Auto-delivery stock is a pool of unique items. Each paid order consumes N items where N = order.quantity.
Import “one item per line” using delimiter \\n:
client.import_auto_delivery_items(
lot_id,
text="KEY1\nKEY2\nKEY3",
delimiter="\\n",
)
print(client.get_auto_delivery_stats(lot_id))
Multi-line items are supported via a custom delimiter:
client.import_auto_delivery_items(
lot_id,
delimiter="---",
text="USER:pass\nnote line 2---USER2:pass2\nnote2",
)
Edit / update a lot
updated = client.update_lot(lot_id, {"priceUsd": 599, "title": "Steam key (v2)"})
print(updated["priceUsd"], updated["title"])
Webhooks: receive purchases + messages (ping → pong)
You should use webhooks to “wait” for events:
order.paidwhen an order is paidmessage.createdwhen a message appears in an order chat
1) Register a webhook endpoint
webhook = client.create_webhook(
url="https://YOUR_PUBLIC_DOMAIN/webhooks/solpay",
events=["order.paid", "message.created"],
secret="your-webhook-secret",
)
print("webhook_id:", webhook["id"])
2) Receive and verify webhooks (Flask example)
Install:
pip install flask
Run this file as a bot:
import json
import random
import threading
from flask import Flask, request, abort
from solpay_seller import SellerApiClient
from solpay_seller.webhooks import verify_signature
API_KEY = "sp_sk_..."
BASE_URL = "https://solpay.one/api"
WEBHOOK_SECRET = "your-webhook-secret"
WEBHOOK_PUBLIC_URL = "https://YOUR_PUBLIC_DOMAIN/webhooks/solpay"
client = SellerApiClient(api_key=API_KEY, base_url=BASE_URL)
state = {
"seller_id": None,
"lot_id": None,
}
app = Flask(__name__)
@app.post("/webhooks/solpay")
def hook():
raw = request.get_data(cache=False)
sig = request.headers.get("X-SolPay-Signature", "")
if not verify_signature(raw_body=raw, signature_header=sig, secret=WEBHOOK_SECRET):
abort(401, "bad signature")
payload = json.loads(raw.decode("utf-8"))
event = (payload.get("type") or "").lower()
data = payload.get("data") or {}
# A) On purchase of OUR lot -> send random 1..100
if event == "order.paid":
order_id = data.get("id")
lot = data.get("lot") or {}
if order_id and lot.get("id") == state["lot_id"]:
client.send_order_message(order_id, f"Your random number: {random.randint(1, 100)}")
# B) If someone wrote "ping" -> reply "pong"
if event == "message.created":
order_id = data.get("orderId")
msg = (data.get("message") or {})
sender_id = msg.get("senderId")
content = (msg.get("content") or "").strip().lower()
# ignore our own messages
if order_id and content == "ping" and sender_id and sender_id != state["seller_id"]:
client.send_order_message(order_id, "pong")
return {"ok": True}
def bootstrap():
# Identify ourselves (used to ignore own messages)
me = client.me()
state["seller_id"] = me["id"]
# Create lot (you need a real categoryId)
category_id = client.get_categories()[0]["id"]
# Optional: upload an image and attach it to the lot
image_url = None
try:
uploaded = client.upload_image("./image.png")
image_url = uploaded.get("url")
if image_url:
print("Uploaded image:", image_url)
except Exception as e:
print("Image upload skipped:", e)
lot = client.create_lot({
"type": "PRODUCT",
"title": "Demo lot (python bot)",
"description": "<p>Auto delivery + ping/pong demo</p>",
"priceUsd": 199,
"categoryId": category_id,
"quantity": 0,
"acceptToken": False,
"images": [image_url] if image_url else [],
"tags": ["demo"],
"isActive": True,
"autoDeliveryEnabled": True,
"autoDeliveryEmptyAction": "BLOCK_PURCHASE",
"autoDeliveryDelimiter": "\\n",
"autoDeliveryNotifyOnEmpty": True,
})
state["lot_id"] = lot["id"]
# Add auto-delivery items
client.import_auto_delivery_items(state["lot_id"], "ITEM1\nITEM2\nITEM3", delimiter="\\n")
# Edit lot
client.update_lot(state["lot_id"], {"title": "Demo lot (python bot) v2", "priceUsd": 249})
# Register webhook endpoint (use your real public URL)
#
# NOTE: Creating a webhook always creates a new endpoint. If you restart this script multiple times,
# you may end up with multiple active webhooks pointing to the same URL (and possibly different secrets),
# which can cause duplicate deliveries or signature mismatches (401). Clean up old ones by URL first:
try:
for wh in client.list_webhooks():
if (wh.get("url") or "").strip() == WEBHOOK_PUBLIC_URL:
client.delete_webhook(wh["id"])
except Exception as e:
print("Webhook cleanup skipped:", e)
client.create_webhook(
url=WEBHOOK_PUBLIC_URL,
events=["order.paid", "message.created"],
secret=WEBHOOK_SECRET,
)
print("Ready. Waiting for webhooks...")
if __name__ == "__main__":
# Tip: for local testing use ngrok:
# ngrok http 8000
threading.Thread(target=bootstrap, daemon=True).start()
app.run(host="0.0.0.0", port=8000)
Orders & messages (manual polling)
Webhooks are best, but you can also poll:
orders = client.list_orders(page=1, limit=20, status="PAID")["orders"]
for o in orders:
print(o["id"], o["status"])
msgs = client.list_order_messages(order_id="ord_...")
print(msgs[-1]["content"])
Send a message:
client.send_order_message("ord_...", "Hello! Thanks for your purchase.")
Service chats (SERVICE lots)
chats = client.list_service_chats(page=1, limit=20, include_closed=False)
chat_id = chats["orders"][0]["id"]
invoice = client.create_service_invoice(chat_id, price_usd=2500, title="Stage 1")
client.close_service_chat(chat_id)
Pydantic models (optional)
If installed (pip install "solpay-seller[models]") you can use:
me_model()list_lots_model()list_orders_model()
They return pydantic objects; otherwise they fall back to raw JSON dict.
Error handling
All non-2xx responses raise SellerApiError:
from solpay_seller import SellerApiClient, SellerApiError
client = SellerApiClient(api_key="sp_sk_...", base_url="https://solpay.one/api")
try:
client.get_lot("missing_id")
except SellerApiError as e:
print("status:", e.status_code)
print("message:", e.message)
print("details:", e.details)
Full API reference (all client methods)
Public helpers
get_categories() -> list
Auth / identity
me() -> dictme_model() -> pydantic model | dict
Uploads
upload_image(file_path: str) -> dict→{ "url": "/uploads/images/..." }upload_images(file_paths: list[str]) -> dict→{ "urls": [...] }
Lots
list_lots(page=1, limit=20) -> dictlist_lots_model(page=1, limit=20) -> pydantic model | dictcreate_lot(lot: dict) -> dictget_lot(lot_id: str) -> dictupdate_lot(lot_id: str, patch: dict) -> dictdelete_lot(lot_id: str) -> dict
Auto-delivery
get_auto_delivery_stats(lot_id: str) -> dictimport_auto_delivery_items(lot_id: str, text: str, delimiter: str | None = None) -> dict
Orders
list_orders(page=1, limit=20, status=None, kind=None, from_iso=None, to_iso=None, lot_id=None, buyer_id=None) -> dictlist_orders_model(...) -> pydantic model | dictget_order(order_id: str) -> dictcheck_payment(order_id: str) -> dictcancel_order(order_id: str) -> dictdeliver_invoice(order_id: str) -> dict(service invoices only)
Messages
list_order_messages(order_id: str) -> listsend_order_message(order_id: str, content: str, attachments: list | None = None) -> dict
Service chats
list_service_chats(page=1, limit=20, include_closed=False) -> dictget_service_chat(chat_id: str) -> dictcreate_service_invoice(chat_id: str, price_usd: int, title: str|None=None, description: str|None=None) -> dictclose_service_chat(chat_id: str) -> dict
Webhooks
webhook_events() -> listlist_webhooks() -> listcreate_webhook(url: str, events: list|None=None, secret: str|None=None) -> dictupdate_webhook(webhook_id: str, url: str|None=None, is_active: bool|None=None, events: list|None=None) -> dictdelete_webhook(webhook_id: str) -> dict
Low-level
request(method: str, path: str, json_body: dict | None = None) -> dict | str
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file solpay_seller-1.0.1.tar.gz.
File metadata
- Download URL: solpay_seller-1.0.1.tar.gz
- Upload date:
- Size: 14.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
777ffab48d6e28e40ed09680d0b44de99de00f34fa043007d65bf0d1761ba629
|
|
| MD5 |
0bcd7f7cca73f5ab8d1f36fa38e27f1a
|
|
| BLAKE2b-256 |
068d3feeeec485079e9e3ee36de92507f5b91173738171d1139c6835304330c0
|
File details
Details for the file solpay_seller-1.0.1-py3-none-any.whl.
File metadata
- Download URL: solpay_seller-1.0.1-py3-none-any.whl
- Upload date:
- Size: 11.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5674df8e68ca9b1a33b4362dcc0f279e139bad15b811bd741078b8d53672f4c0
|
|
| MD5 |
477c08ca5b77be29e166bf5810efcac1
|
|
| BLAKE2b-256 |
efa21b732e2c880f6e1ba871b0b9b5a8de4de0eeffdeb2339a6a117284560c43
|