API Documentation
Integrate your applications with the marketplace: read your account and items, pull your sales for custom dashboards, and verify buyer purchase codes from your own license server.
curl https://sellmycode.net/api
Authentication
Every request needs a personal access token. Create named, scoped tokens in your workspace under Settings → API Key — each app gets its own token with only the permissions it needs, and you can revoke any of them at any time.
curl https://sellmycode.net/api/account/details \ -H "Authorization: Bearer YOUR_API_KEY"
curl https://sellmycode.net/api/account/details \ -H "X-Api-Key: YOUR_API_KEY"
Errors & Limits
| Code | Meaning |
|---|---|
| 200 | success |
| 400 | Validation error — a required parameter is missing or malformed |
| 401 | Invalid or missing API key |
| 404 | Resource not found (also returned for an invalid purchase code) |
| 429 | Rate limit exceeded — wait and retry (60 requests per minute) |
{
"status": "error",
"msg": "Invalid request"
}
GETAccount Details
Returns the profile of the account that owns the API key.
curl https://sellmycode.net/api/account/details \ -H "Authorization: Bearer YOUR_API_KEY"
GETAll Items
All of your approved items, newest first. Authors only.
curl https://sellmycode.net/api/items/all \ -H "Authorization: Bearer YOUR_API_KEY"
GETSingle Item
One of your approved items by its numeric ID.
| Parameter | Type | Description |
|---|---|---|
| item_id Required | integer | The item ID (shown in your workspace item list) |
curl "https://sellmycode.net/api/items/item?item_id=123" \ -H "Authorization: Bearer YOUR_API_KEY"
GETSales
Your sales, newest first — build revenue dashboards or sync orders into your own tools. Paginated.
| Parameter | Type | Description |
|---|---|---|
| from optional | date | Y-m-d — only sales on or after this date |
| to optional | date | Y-m-d — only sales on or before this date |
| item_id optional | integer | filter by one of your items |
| status optional | string | active | refunded | cancelled | held |
| per_page optional | integer | default 25, max 50 |
| page optional | integer | page number |
curl "https://sellmycode.net/api/sales?from=2026-01-01&status=active&per_page=25" \ -H "Authorization: Bearer YOUR_API_KEY"
{
"status": "success",
"pagination": { "page": 1, "per_page": 25, "total": 132, "last_page": 6 },
"sales": [
{
"id": 981,
"purchase_code": "8f14e45f-ce95-41d8-a2b6-72e5f13d81a7",
"item": { "id": 123, "name": "My Theme" },
"buyer": "johndoe",
"license_type": "Regular",
"price": 29.0,
"fee": 5.8,
"earning": 23.2,
"currency": "USD",
"sale_status": "active",
"cleared": true,
"date": "2026-07-01T09:30:00+00:00"
}
]
}
GETMy Purchases
Your own purchases as a buyer, newest first — check licenses and download windows from your tooling. Paginated.
| Parameter | Type | Description |
|---|---|---|
| status optional | string | active | refunded | cancelled | held |
| per_page optional | integer | default 25, max 50 |
| page optional | integer | page number |
curl "https://sellmycode.net/api/purchases?status=active" \ -H "Authorization: Bearer YOUR_API_KEY"
{
"status": "success",
"pagination": { "page": 1, "per_page": 25, "total": 3, "last_page": 1 },
"purchases": [
{
"purchase_code": "8f14e45f-ce95-41d8-a2b6-72e5f13d81a7",
"item": { "id": 123, "name": "My Theme", "url": "https://sellmycode.net/item/my-theme/123" },
"license_type": "Regular",
"price": 29.0,
"currency": "USD",
"purchase_status": "active",
"download_expiry_at": "2027-01-01T00:00:00+00:00",
"download_expired": false,
"date": "2026-07-01T09:30:00+00:00"
}
]
}
GETBalance
A read-only snapshot of your wallets: withdrawable earnings, earnings still in the clearing period, and Store Credit. Optionally include your statement history.
| Parameter | Type | Description |
|---|---|---|
| statements optional | boolean | 1 to include recent statements |
| wallet optional | string | balance | store_credit — filter statements by wallet |
| per_page optional | integer | default 25, max 50 |
curl "https://sellmycode.net/api/balance?statements=1&wallet=balance" \ -H "Authorization: Bearer YOUR_API_KEY"
{
"status": "success",
"currency": "USD",
"wallets": {
"balance": 1250.75,
"pending_balance": 89.4,
"store_credit": 12.5
},
"pagination": { "page": 1, "per_page": 25, "total": 57, "last_page": 3 },
"statements": [
{
"id": 4021,
"title": "[Sale] #981 (My Theme)",
"wallet": "balance",
"type": "credit",
"amount": 29.0,
"total": 23.2,
"date": "2026-07-01T09:30:05+00:00"
}
]
}
POSTPurchase Validation
Verify a purchase code a buyer gives you — the core of any license system. Returns the purchase details when the code belongs to one of YOUR items and is still active; otherwise 404.
| Parameter | Type | Description |
|---|---|---|
| purchase_code Required | string | The code from the buyer (shown on their Purchases page and license certificate) |
curl -X POST https://sellmycode.net/api/purchases/validation \ -H "Authorization: Bearer YOUR_API_KEY" \ -d "purchase_code=BUYER_PURCHASE_CODE"
// license check from your app / plugin $ch = curl_init('https://sellmycode.net/api/purchases/validation'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Authorization: Bearer YOUR_API_KEY'], CURLOPT_POSTFIELDS => http_build_query(['purchase_code' => $code]), ]); $res = json_decode(curl_exec($ch), true); $valid = ($res['status'] ?? '') === 'success';
// Node.js 18+ (built-in fetch) const res = await fetch('https://sellmycode.net/api/purchases/validation', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ purchase_code: code }), }); const data = await res.json(); const valid = res.ok && data.status === 'success';
# Python 3 + requests import requests res = requests.post( 'https://sellmycode.net/api/purchases/validation', headers={'Authorization': 'Bearer YOUR_API_KEY'}, data={'purchase_code': code}, timeout=10, ) valid = res.status_code == 200 and res.json().get('status') == 'success'
{
"status": "success",
"buyer": "johndoe",
"item": {
"purchase_code": "8f14e45f-ce95-41d8-a2b6-72e5f13d81a7",
"license_type": "Regular",
"price": 29.0,
"currency": "USD",
"item": { "…item fields…" },
"supported_until": "2026-12-01T00:00:00+00:00",
"download_expiry": "2026-08-01T00:00:00+00:00",
"downloaded": true,
"date": "2026-07-01T09:30:00+00:00"
}
}
GETPublic Catalog
Browse and search the whole approved catalog — open to EVERY account, buyer or author. Build price trackers, portfolio showcases or category dashboards. Returns storefront data only: buyer-facing prices (discounts included), ratings and sales counts — never files, download links or buyer identities.
| Parameter | Type | Description |
|---|---|---|
| search optional | string | full-text search across item names and tags |
| category optional | string | a category slug (see the categories endpoint) |
| sort optional | string | latest (default) | popular | rating |
| per_page optional | integer | default 25, max 50 |
| page optional | integer | page number |
curl "https://sellmycode.net/api/catalog/items?search=woocommerce&sort=popular" \ -H "Authorization: Bearer YOUR_API_KEY"
{
"status": "success",
"pagination": { "page": 1, "per_page": 25, "total": 64, "last_page": 3 },
"items": [
{
"id": 123,
"name": "My Theme",
"url": "https://sellmycode.net/item/my-theme/123",
"thumbnail": "https://sellmycode.net/files/thumbnails/…",
"category": { "id": 4, "name": "WordPress", "slug": "wordpress" },
"author": "janedoe",
"price": { "regular": 29.0, "extended": 145.0 },
"currency": "USD",
"rating": { "average": 4.8, "count": 36 },
"version": "2.1.0",
"updated_at": "2026-07-10T08:00:00+00:00",
"published_at": "2026-01-05T12:00:00+00:00"
}
]
}
Full public detail of one approved item — the list fields plus description, media previews, included files list and framework.
curl https://sellmycode.net/api/catalog/items/123 \ -H "Authorization: Bearer YOUR_API_KEY"
All categories with their approved-item counts — use the slug as the category filter above.
{
"status": "success",
"categories": [
{ "id": 4, "name": "WordPress", "slug": "wordpress", "url": "https://sellmycode.net/categories/wordpress", "items_count": 128 }
]
}
Licence Activation
For software that phones home — PHP scripts, desktop or mobile apps. Your installation sends the purchase code and its domain; we confirm the licence and count how many sites it runs on, so you do not have to host a licensing server yourself.
| Parameter | Type | Description |
|---|---|---|
| purchase_code | string | the code the buyer received |
| domain | string | the site running the software; scheme, www and path are stripped automatically |
curl "https://sellmycode.net/api/license/activate" \ -H "Content-Type: application/json" \ -d '{"purchase_code":"d71c21a3-...","domain":"customer-site.com"}'
{
"status": "success",
"code": "activated",
"domain": "customer-site.com",
"license_type": "Regular",
"activation_limit": 2,
"activations_used": 1,
"updates": { "active": true, "until": "2027-01-30T10:00:00+00:00" }
}
| Error code | HTTP | Meaning |
|---|---|---|
| invalid_code | 403 | unknown, refunded or cancelled purchase code |
| activation_limit_reached | 403 | every allowed site is in use — deactivate one first |
| not_activated | 403 | this domain has never been activated (or was released) |
| invalid_domain | 400 | the domain could not be parsed |
| rate_limited | 429 | too many calls from this IP |
Item Updates (Toolkit)
Let a WordPress site keep the themes and plugins it bought here up to date. Tell us which packages are installed, get back the ones with a newer version, then pull the installable ZIP. This is what the SellMyCode Toolkit plugin uses — you only need these endpoints if you are building your own updater.
On WordPress? Skip the code
The SellMyCode Toolkit plugin already wires these endpoints into the normal Themes and Plugins update screens. Install it, paste a token, and purchased items update like anything else in WordPress.
POSTCheck for updates
| Parameter | Type | Description |
|---|---|---|
| packages | array | installed packages, up to 100 per call |
| packages[].slug | string | the theme or plugin folder name |
| packages[].type | string | theme | plugin |
| packages[].version optional | string | the version currently installed |
curl "https://sellmycode.net/api/updates/check" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"packages":[{"slug":"my-theme","type":"theme","version":"1.4.0"}]}'
{
"status": "success",
"checked": 1,
"updates": [
{
"item_id": 123,
"slug": "my-theme",
"type": "theme",
"name": "My Theme",
"installed_version": "1.4.0",
"new_version": "1.6.2",
"changelog": { "version": "1.6.2", "body": "Fixed ..." },
"can_download": true,
"reason": null,
"update_window_ends_at": "2027-01-30T10:00:00+00:00",
"download_url": "https://sellmycode.net/api/updates/download/123",
"renew_url": null
}
]
}
GETDownload the installable package
Responds with a 302 redirect to a short-lived storage URL (valid a few minutes). Follow redirects and save the ZIP — it contains only the theme or plugin, ready to install.
curl -L -o update.zip "https://sellmycode.net/api/updates/download/123" \ -H "Authorization: Bearer YOUR_API_KEY"
| Error code | HTTP | Meaning |
|---|---|---|
| not_purchased | 403 | this account did not buy the item |
| update_window_expired | 403 | update support has ended — renew_url is included in the response |
| no_installable_file | 404 | the author has not provided an installable package for this item |
| item_removed | 410 | the item is no longer available |
Sale Webhooks
Instead of polling the sales endpoint, register an HTTPS URL and we POST a signed JSON event to it the moment something happens. Manage endpoints in your workspace under Settings → API Key → Webhooks (up to 5, each with its own signing secret shown once at creation).
| Event | Sent when |
|---|---|
| sale.created | One of your items, bundles or courses is sold |
| sale.refunded | One of your sales is refunded or cancelled — revoke the license on your side |
Delivery
Each delivery is an HTTP POST with a JSON body. Respond with any 2xx status within 10 seconds; do heavy work asynchronously. Failed deliveries are retried 3 times (after 1 minute, 15 minutes and 1 hour). After 10 consecutive failures — or a 410 Gone response — the endpoint is disabled automatically and you are notified.
| Header | Description |
|---|---|
| X-SMC-Event | The event name, e.g. sale.created |
| X-SMC-Delivery | Unique delivery ID (UUID) — deduplicate retries with it |
| X-SMC-Signature | t=<unix>,v1=<hex> — HMAC-SHA256 signature (see below) |
{
"id": "1c2f6a7e-9d31-4c3e-8f5b-2d9f8f4f1a10",
"event": "sale.created",
"created_at": "2026-07-18T10:30:00+00:00",
"data": {
"sale_id": 981,
"purchase_code": "8f14e45f-ce95-41d8-a2b6-72e5f13d81a7",
"type": "item",
"item": { "id": 123, "name": "My Theme" },
"buyer": "johndoe",
"license_type": "Regular",
"price": 29.0,
"earning": 23.2,
"currency": "USD",
"date": "2026-07-18T10:30:00+00:00"
}
}
Verifying the signature
Compute HMAC-SHA256 over the string "timestamp.rawBody" with your signing secret and compare it (constant-time) to the v1 value. Reject deliveries whose timestamp is older than ~5 minutes to prevent replays.
$secret = 'whsec_...'; // from Settings → API Key → Webhooks $body = file_get_contents('php://input'); $header = $_SERVER['HTTP_X_SMC_SIGNATURE'] ?? ''; parse_str(str_replace(',', '&', $header), $sig); // ['t' => ..., 'v1' => ...] $expected = hash_hmac('sha256', $sig['t'] . '.' . $body, $secret); if (!hash_equals($expected, $sig['v1'] ?? '') || abs(time() - (int) $sig['t']) > 300) { http_response_code(400); exit; } http_response_code(200); // ack fast, process async
// Express: use express.raw() so the RAW body is available for the HMAC const crypto = require('crypto'); const SECRET = 'whsec_...'; // from Settings → API Key → Webhooks app.post('/webhooks/sellmycode', express.raw({ type: 'application/json' }), (req, res) => { const header = req.get('X-SMC-Signature') || ''; const sig = Object.fromEntries(header.split(',').map(p => p.split('='))); const expected = crypto.createHmac('sha256', SECRET) .update(sig.t + '.' + req.body.toString('utf8')).digest('hex'); const fresh = Math.abs(Date.now() / 1000 - Number(sig.t)) < 300; const valid = sig.v1 && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig.v1)); if (!valid || !fresh) return res.sendStatus(400); const event = JSON.parse(req.body); // { id, event, created_at, data } res.sendStatus(200); // ack fast, process async });
# Flask — request.get_data() is the RAW body needed for the HMAC import hmac, hashlib, time from flask import Flask, request SECRET = 'whsec_...' # from Settings → API Key → Webhooks @app.route('/webhooks/sellmycode', methods=['POST']) def webhook(): header = request.headers.get('X-SMC-Signature', '') sig = dict(p.split('=', 1) for p in header.split(',') if '=' in p) body = request.get_data() # bytes, unparsed expected = hmac.new( SECRET.encode(), (sig.get('t', '') + '.').encode() + body, hashlib.sha256 ).hexdigest() fresh = abs(time.time() - float(sig.get('t', 0))) < 300 if not (hmac.compare_digest(expected, sig.get('v1', '')) and fresh): return '', 400 event = request.get_json() # { id, event, created_at, data } return '', 200 # ack fast, process async
# Rails / Rack — request.body.read is the RAW body needed for the HMAC SECRET = 'whsec_...' # from Settings → API Key → Webhooks def webhook header = request.headers['X-SMC-Signature'].to_s sig = header.split(',').to_h { |p| p.split('=', 2) } body = request.body.read expected = OpenSSL::HMAC.hexdigest('SHA256', SECRET, "#{sig['t']}.#{body}") fresh = (Time.now.to_i - sig['t'].to_i).abs < 300 valid = sig['v1'] && ActiveSupport::SecurityUtils.secure_compare(expected, sig['v1']) return head :bad_request unless valid && fresh event = JSON.parse(body) # { id, event, created_at, data } head :ok # ack fast, process async end