Skip to main content

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.paid when an order is paid
  • message.created when 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() -> dict
  • me_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) -> dict
  • list_lots_model(page=1, limit=20) -> pydantic model | dict
  • create_lot(lot: dict) -> dict
  • get_lot(lot_id: str) -> dict
  • update_lot(lot_id: str, patch: dict) -> dict
  • delete_lot(lot_id: str) -> dict

Auto-delivery

  • get_auto_delivery_stats(lot_id: str) -> dict
  • import_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) -> dict
  • list_orders_model(...) -> pydantic model | dict
  • get_order(order_id: str) -> dict
  • check_payment(order_id: str) -> dict
  • cancel_order(order_id: str) -> dict
  • deliver_invoice(order_id: str) -> dict (service invoices only)

Messages

  • list_order_messages(order_id: str) -> list
  • send_order_message(order_id: str, content: str, attachments: list | None = None) -> dict

Service chats

  • list_service_chats(page=1, limit=20, include_closed=False) -> dict
  • get_service_chat(chat_id: str) -> dict
  • create_service_invoice(chat_id: str, price_usd: int, title: str|None=None, description: str|None=None) -> dict
  • close_service_chat(chat_id: str) -> dict

Webhooks

  • webhook_events() -> list
  • list_webhooks() -> list
  • create_webhook(url: str, events: list|None=None, secret: str|None=None) -> dict
  • update_webhook(webhook_id: str, url: str|None=None, is_active: bool|None=None, events: list|None=None) -> dict
  • delete_webhook(webhook_id: str) -> dict

Low-level

  • request(method: str, path: str, json_body: dict | None = None) -> dict | str

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

solpay_seller-1.0.1.tar.gz (14.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

solpay_seller-1.0.1-py3-none-any.whl (11.7 kB view details)

Uploaded Python 3

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

Hashes for solpay_seller-1.0.1.tar.gz
Algorithm Hash digest
SHA256 777ffab48d6e28e40ed09680d0b44de99de00f34fa043007d65bf0d1761ba629
MD5 0bcd7f7cca73f5ab8d1f36fa38e27f1a
BLAKE2b-256 068d3feeeec485079e9e3ee36de92507f5b91173738171d1139c6835304330c0

See more details on using hashes here.

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

Hashes for solpay_seller-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 5674df8e68ca9b1a33b4362dcc0f279e139bad15b811bd741078b8d53672f4c0
MD5 477c08ca5b77be29e166bf5810efcac1
BLAKE2b-256 efa21b732e2c880f6e1ba871b0b9b5a8de4de0eeffdeb2339a6a117284560c43

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page