SelleySelley DocsAPI v1
Go to Selley
IntroductionQuick startAPI referenceAuthenticationMultiple shopsProduct typesFulfillment & deliveryPaginationRate limitsErrors
OverviewBefore purchaseCommitment hashHow rolls workVerify outcomesBuyer checklist
GETList productsGETGet a productPOSTCreate a productPUTUpdate a productDELETEDelete a product
GETList ordersGETGet an orderPUTUpdate order
GETList couponsGETGet a couponPOSTCreate a couponPUTUpdate a couponDELETEDelete a coupon
GETList blacklistsGETGet a blacklistPOSTCreate blacklistPUTUpdate blacklistDELETEDelete blacklist
GETList categoriesGETGet a categoryPOSTCreate categoryPUTUpdate categoryDELETEDelete category
GETList groupsGETGet a groupPOSTCreate a groupPUTUpdate a groupDELETEDelete a group
GETList customersGETGet a customer
GETList subscriptionsGETGet a subscription
GETList themesGETGet themePUTUpdate theme
OverviewEvent typesRequest formatPayload structureOrder dataSubscription dataProduct dataMystery box dataDynamic fulfillmentQuery & feedbackLogs & simulator
OverviewUsage
Base URLhttps://selley.io/api/v1
Get Started

Selley Developer Docs

Build integrations, automation, and custom tooling on top of your Selley store. The REST API covers products, orders, customers, coupons, themes, webhooks, and more — all JSON over HTTPS with Bearer API key authentication.

Create API keys in Dashboard → Settings → Security. Use https://selley.io/api/v1 (or your custom domain with /api/v1).

Quick start

First API call in under a minute.

API reference

Scan every endpoint, then drill into examples.

Product types

Digital, Serial, Mystery Box, Dynamic, and more.

Provably fair

How mystery box odds and verification work.

Webhooks

Real-time order and fulfillment events.

Themes API

Pull and push storefront themes via CLI.

API Root URL

https://selley.io/api/v1

Quick example

bash
curl https://selley.io/api/v1/products \
  -H "Authorization: Bearer YOUR_API_KEY"
Get Started

Quick start

Get from zero to your first API call in four steps. You need a Selley account and an API key with access to your shop.

  1. 1Create an API key in Dashboard → Settings → Security. Copy the secret once — it is shown only at creation time.
  2. 2Set your base URL to https://selley.io/api/v1 (or your custom domain with /api/v1).
  3. 3Send a test request — list your live products to confirm authentication works.
  4. 4Subscribe to webhooks in Developers → Webhooks so your backend reacts to orders and fulfillment in real time.
bash
curl "https://selley.io/api/v1/products?limit=5" \
  -H "Authorization: Bearer sk_live_YOUR_KEY"

Multi-shop accounts

If you operate more than one storefront, add X-Selley-Shop: your-shop-slug to every request so data is scoped to the correct shop.
API reference

Endpoints at a glance

All routes are relative to https://selley.io/api/v1. Click a row to jump to full parameters, examples, and response shapes.

MethodPathDescriptionGroup
GET/productsList productsProducts
GET/products/:idGet productProducts
POST/productsCreate productProducts
PUT/products/:idUpdate productProducts
DELETE/products/:idDelete productProducts
GET/ordersList ordersOrders
GET/orders/:idGet orderOrders
PUT/orders/:idUpdate orderOrders
GET/couponsList couponsCoupons
POST/couponsCreate couponCoupons
GET/blacklistsList blacklistsBlacklists
GET/categoriesList categoriesCategories
GET/groupsList groupsGroups
GET/customersList customersCustomers
GET/subscriptionsList subscriptionsSubscriptions
GET/themesList themesThemes
PUT/themes/:themeIdUpdate themeThemes

Full CRUD docs for each resource appear below — including coupons, blacklists, categories, groups, and webhooks.

Authentication

Every request must include your API secret key as a Bearer token in the Authorization header. Generate and manage keys in your dashboard under Settings → Security. All requests must be made over HTTPS.

Setup Authentication Header

curl https://selley.io/api/v1/products \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx"

Multiple Shops

If your account has more than one shop, pass the X-Selley-Shopheader with the shop's ID or slug on each request. Without this header the first (oldest) shop is used automatically.

bash
curl https://selley.io/api/v1/orders \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "X-Selley-Shop: my-shop-slug"
Get Started

Product types

Every product has a type that controls checkout behavior, fulfillment, and what appears on the customer invoice. Choose the type when creating a product in the dashboard or via POST /products.

TypeBest forDelivery
ServiceManual fulfillment, coaching, access you grant yourselfInvoice message + optional file links
DigitalDownloadable files (PDF, ZIP, etc.)Secure download links on invoice
PhysicalShipped goodsTracking info when you add it
SerialLicense keys, game codes, account credentialsOne unique code per quantity from your stock pool
SubscriptionRecurring billing via StripeOngoing access; renewals fire subscription webhooks
InfoCardDisplay-only product (no checkout)N/A — informational storefront card
DynamicCustom API-driven fulfillmentPOST to your Dynamic Webhook URL per line item
MysteryBoxLoot boxes / case openings with published oddsProvably fair roll + reveal on invoice with verification proof

Mystery box (provably fair)

Mystery box products expose weighted outcomes and odds on the product page before purchase. After payment, Selley rolls using a committed server seed and HMAC-SHA256; customers see their outcome on the invoice with expandable proof they can verify. Buyers can read how provably fair works on the product page before buying. Configure outcomes and weights in the dashboard under Products → Mystery Box. Customers may optionally supply a client seed at checkout via custom field __mysteryClientSeed.

Fulfillment guide

How each product type delivers after payment.

Provably fair guide

Full explanation for buyers and merchants.

Create via API

POST /products with type and status fields.

Get Started

Fulfillment & delivery

When an order is marked paid, Selley runs fulfillment automatically based on each line item's product type. Delivered content appears on the customer invoice at /shop/[store]/invoice/[orderNumber] and in order API responses where applicable.

Serial & Digital

Codes or files are pulled from inventory at payment time. Stock decrements per unit. Bulk copy/download on the invoice exports raw codes only (one per line, no product name prefixes).

Dynamic

Selley POSTs a rich payload to the product's Dynamic Webhook URL (INVOICE.ITEM.DELIVER-DYNAMIC). Your server responds with delivery content (text, links, or JSON) shown on the invoice.

Mystery box

Outcomes are rolled once per quantity using committed randomness. The invoice shows the revealed item, odds snapshot, commitment hash, roll index, and verification steps. Pool stock for each outcome decrements when that outcome is won.

Service & Physical

Use the delivery message configured on the product. For physical goods, add tracking in the dashboard or via order updates; customers see tracking on the invoice when present.

Idempotency

Fulfillment is idempotent — re-processing a paid order does not double-deliver serials, mystery outcomes, or dynamic payloads for the same line item.
Provably Fair

Mystery box fairness

Selley mystery boxes (case openings, loot boxes) use a provably fair system. Buyers see exact odds and a cryptographic commitment before paying. After purchase, they can verify that Selley — not the shop owner — rolled the outcome using the committed randomness.

Share this guide with customers from your product page via the How we prove it's fair link on every mystery box listing.

Before purchase

What buyers can audit before paying.

Roll algorithm

HMAC-SHA256 and weight selection.

Webhook event

product:mystery:revealed payload.

Fulfillment

How outcomes are stored on invoices.

Provably Fair

What buyers see before purchase

Every live mystery box product page on Selley shows:

  • Drop table — Each outcome, weight, percentage chance, and remaining stock.
  • Fairness commitment — SHA-256(server seed) published before checkout. The seller cannot change the seed without changing this hash.
  • Pool version — Increments when odds or stock change, with a new commitment. Buyers should note version + hash at purchase time.
  • Optional client seed — Buyers may set __mysteryClientSeed so part of the randomness comes from them.

For merchants

Configure outcomes and weights in Dashboard → Products when creating or editing a Mystery Box product. Odds update on the storefront immediately; pool version bumps when weights change.
Provably Fair

Why the commitment hash matters

Before payment, Selley stores a random server seedand shows only its hash on the product page. SHA-256 is one-way — you cannot reverse the hash to pick a favorable seed after seeing the buyer's order.

After payment, Selley reveals the server seed on the invoice. Verification is simple: recompute the hash and confirm it matches what was shown before purchase.

Verification

text
shown_before_purchase === SHA-256(revealed_server_seed)

Pool changes

If you edit weights, labels, or stock after publishing a product, Selley generates a new server seed, new commitment hash, and higher pool version. Buyers who saved an old hash should compare against the values shown at the moment they clicked buy.
Provably Fair

How rolls work

Selley runs fulfillment automatically when an order is paid. For each mystery box quantity:

  1. Resolve client seed from __mysteryClientSeed or generate one.
  2. Assign a unique nonce per open (per quantity index).
  3. Compute proofHash = HMAC-SHA256(serverSeed, clientSeed + ":" + nonce).
  4. Derive roll index: parseInt(proofHash[0..12], 16) mod totalWeight where totalWeight is the sum of weights for in-stock outcomes.
  5. Walk outcomes in order; the roll selects exactly one item matching published weights.
  6. A second HMAC pass picks which deliverable code from that outcome's stock.
javascript
const crypto = require("crypto");

function rollHash(serverSeed, clientSeed, nonce) {
  return crypto.createHmac("sha256", serverSeed)
    .update(`${clientSeed}:${nonce}`)
    .digest("hex");
}

function rollIndex(proofHash, totalWeight) {
  return parseInt(proofHash.slice(0, 13), 16) % totalWeight;
}
The shop owner never executes rolls manually. Selley uses the committed seed and a snapshot of pool weights at purchase time.
Provably Fair

Verify after opening

After payment, buyers open their invoice and expand Show fairness proof on each case result. Selley marks outcomes Verified fair when all checks pass:

  • Revealed server seed hashes to the pre-purchase commitment.
  • Proof hash matches HMAC-SHA256(server seed, client seed + nonce).
  • Roll index maps to the won item using the weight snapshot stored on the order line.
  • Delivered code matches the second HMAC selection for that outcome's stock.

Buyers can copy the full proof bundle from the invoice. Merchants can fetch order details via GET /orders/:id or listen for the product:mystery:revealed webhook.

Provably Fair

Pre-purchase checklist

  • ✓Review drop table percentages and remaining stock
  • ✓Copy the commitment hash and pool version (use Copy audit record on the product page)
  • ✓Optionally set a personal client seed
  • ✓After payment, verify SHA-256(revealed seed) equals your saved hash

Selley guarantees that stated odds were followed fairly — not that a specific rare item will drop. Low-chance items are rare because their weight is low, not because of hidden manipulation.

Pagination

List endpoints support cursor-based pagination. Pass limit to control page size and cursor from the previous response to fetch the next page. When nextCursor is null there are no more results.

NameTypeDescription
limitintegerItems per page. Default 100, max 250.
cursorstringOpaque cursor returned as nextCursor in the previous response.
bash
# First page
curl "https://selley.io/api/v1/products?limit=10" \
  -H "Authorization: Bearer YOUR_API_KEY"

# Next page using cursor
curl "https://selley.io/api/v1/products?limit=10&cursor=clxabc123" \
  -H "Authorization: Bearer YOUR_API_KEY"

Rate limits

API requests may be rate-limited per API key. If you exceed the limit you will receive 429 Too Many Requests. Retry after the suggested delay. Webhook delivery has a 15-second timeout per request; failed deliveries are logged and can be retried from the dashboard.

Errors

Errors always return JSON with an error string. Validation errors may include an errors array with field-level details. Use the HTTP status code to determine the type of error.

StatusMeaning
400Bad Request – invalid parameters or missing required fields.
401Unauthorized – missing or invalid API key.
403Forbidden – the request is not allowed.
404Not Found – the resource does not exist.
409Conflict – e.g. duplicate coupon code.
429Too Many Requests – rate limit exceeded.
500Internal Server Error – retry later.
json
{
  "error": "Unauthorized"
}

// Validation error
{
  "error": "code: String must contain at least 1 character(s)"
}
Products

Endpoints: /api/v1/products

GET
/products

GET https://selley.io/api/v1/products

Returns a paginated list of your Live products, sorted by creation date descending.

Query Parameters

NameTypeDescription
limitintegerMax items per page (default 100, max 250).
cursorstringPagination cursor from previous response.

Response

json
{
  "products": [
    {
      "id": "clxabc123",
      "productId": "rec_abc123",
      "name": "Pro License",
      "description": "One-year software license",
      "image": "https://cdn.selley.io/img.jpg",
      "type": "Serial",
      "price": 29.99,
      "currency": "USD",
      "stock": 50,
      "status": "Live",
      "sortOrder": 0,
      "unlisted": false,
      "createdAt": "2024-06-01T10:00:00.000Z",
      "updatedAt": "2024-06-01T10:00:00.000Z"
    }
  ],
  "nextCursor": "clxdef456"
}

Request Example

curl "https://selley.io/api/v1/products?limit=10" \
  -H "Authorization: Bearer YOUR_API_KEY"
GET
/products/:id

GET https://selley.io/api/v1/products/:id

Retrieve a single product by its internal ID or the shorter productId (e.g. rec_abc123).

URL Parameters

NameTypeDescription
idrequiredstringInternal ID or productId of the product.

Request Example

curl "https://selley.io/api/v1/products/rec_abc123" \
  -H "Authorization: Bearer YOUR_API_KEY"
POST
/products

POST https://selley.io/api/v1/products

Create a new product. The product is created in Draft status by default. Set status to Live to make it visible on your storefront.

Request Body

NameTypeDescription
namerequiredstringProduct title.
pricerequirednumberPrice in the specified currency.
typerequiredstringService | Digital | Physical | Serial | Subscription | InfoCard | Dynamic | MysteryBox
descriptionstringProduct description.
currencystring3-letter currency code (default USD).
stockinteger | nullStock count. null = unlimited.
statusstringLive | Draft | Archived. Default: Draft.
unlistedbooleanHide from storefront but accessible via direct link.

Request Example

curl -X POST "https://selley.io/api/v1/products" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Pro License",
    "price": 29.99,
    "type": "Serial",
    "currency": "USD",
    "status": "Live"
  }'
PUT
/products/:id

PUT https://selley.io/api/v1/products/:id

Update any fields of an existing product. Only the fields you include will be changed.

Updatable Fields

NameTypeDescription
namestringNew product title.
pricenumberNew price.
statusstringLive | Draft | Archived
stockinteger | nullNew stock count.
descriptionstringNew description.
unlistedbooleanHide/show on storefront.

Request Example

curl -X PUT "https://selley.io/api/v1/products/rec_abc123" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"price": 39.99, "status": "Live"}'
DELETE
/products/:id

DELETE https://selley.io/api/v1/products/:id

Permanently delete a product. This cannot be undone. Returns success: true on success.

json
{ "success": true }

Request Example

curl -X DELETE "https://selley.io/api/v1/products/rec_abc123" \
  -H "Authorization: Bearer YOUR_API_KEY"
Orders

Endpoints: /api/v1/orders

GET
/orders

GET https://selley.io/api/v1/orders

Returns orders for your shop(s), most recent first. Includes order items. Scoped by shop (use X-Selley-Shop for multi-shop).

Query Parameters

NameTypeDescription
limitintegerMax items (default 50, max 200).

Response

json
{
  "orders": [
    {
      "id": "clxabc123",
      "orderNumber": "ORD-12345",
      "status": "completed",
      "paymentStatus": "paid",
      "total": 29.99,
      "currency": "USD",
      "customerEmail": "[email protected]",
      "customerName": "Jane Smith",
      "paymentMethod": "Stripe",
      "createdAt": "2024-06-01T10:00:00.000Z",
      "paidAt": "2024-06-01T10:01:30.000Z",
      "items": [
        {
          "id": "item_123",
          "productId": "clx_prod",
          "productName": "Pro License",
          "quantity": 1,
          "price": 29.99,
          "total": 29.99
        }
      ]
    }
  ]
}

Request Example

curl "https://selley.io/api/v1/orders?limit=20" \
  -H "Authorization: Bearer YOUR_API_KEY"
GET
/orders/:id

GET https://selley.io/api/v1/orders/:id

Retrieve a single order by its internal ID or the display orderNumber (e.g. ORD-12345). Includes full item list.

NameTypeDescription
idrequiredstringInternal ID or orderNumber.
curl "https://selley.io/api/v1/orders/ORD-12345" \
  -H "Authorization: Bearer YOUR_API_KEY"
PUT
/orders/:id

PUT https://selley.io/api/v1/orders/:id

Update the status of an order. Useful for manually marking orders as completed or cancelled.

NameTypeDescription
statusstringpending | processing | completed | cancelled | refunded
curl -X PUT "https://selley.io/api/v1/orders/ORD-12345" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "completed"}'
Coupons

Endpoints: /api/v1/coupons

GET
/coupons

GET https://selley.io/api/v1/coupons

Returns all discount coupons for your account. Filter to only active coupons with active=true.

Query Parameters

NameTypeDescription
activebooleanPass true to only return active coupons.

Response

json
{
  "coupons": [
    {
      "id": "clxabc123",
      "code": "SAVE20",
      "type": "percentage",
      "value": 20,
      "usageLimit": 100,
      "usedCount": 3,
      "minAmount": 10,
      "expiresAt": "2025-12-31T23:59:59.000Z",
      "active": true,
      "createdAt": "2024-06-01T10:00:00.000Z",
      "updatedAt": "2024-06-01T10:00:00.000Z"
    }
  ]
}
curl "https://selley.io/api/v1/coupons?active=true" \
  -H "Authorization: Bearer YOUR_API_KEY"
GET
/coupons/:id

GET https://selley.io/api/v1/coupons/:id

Retrieve a single coupon by its ID.

NameTypeDescription
idrequiredstringCoupon ID.
curl "https://selley.io/api/v1/coupons/clxabc123" \
  -H "Authorization: Bearer YOUR_API_KEY"
POST
/coupons

POST https://selley.io/api/v1/coupons

Create a new coupon code. Supports percentage or fixed discounts, usage limits, and optional expiry dates.

Request Body

NameTypeDescription
coderequiredstringCoupon code (e.g. SAVE20). Automatically uppercased.
typerequiredstringpercentage | fixed
valuerequirednumberDiscount amount (% or currency unit).
usageLimitinteger | nullMax redemptions. null = unlimited.
minAmountnumber | nullMinimum order total to apply coupon.
expiresAtstring | nullISO 8601 expiry date. null = no expiry.
activebooleanWhether the coupon is active. Default true.
curl -X POST "https://selley.io/api/v1/coupons" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "code": "SAVE20",
    "type": "percentage",
    "value": 20,
    "usageLimit": 100,
    "minAmount": 10,
    "expiresAt": "2025-12-31T23:59:59.000Z"
  }'
PUT
/coupons/:id

PUT https://selley.io/api/v1/coupons/:id

Update an existing coupon. Only the fields you provide will be modified.

NameTypeDescription
codestringNew coupon code.
typestringpercentage | fixed
valuenumberNew discount value.
usageLimitinteger | nullNew max redemptions.
activebooleanEnable or disable the coupon.
expiresAtstring | nullNew expiry date or null to remove.
curl -X PUT "https://selley.io/api/v1/coupons/clxabc123" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"active": false}'
DELETE
/coupons/:id

DELETE https://selley.io/api/v1/coupons/:id

Permanently delete a coupon. Returns success: true.

json
{ "success": true }
curl -X DELETE "https://selley.io/api/v1/coupons/clxabc123" \
  -H "Authorization: Bearer YOUR_API_KEY"
Blacklists

Endpoints: /api/v1/blacklists — shop-scoped

GET
/blacklists

GET https://selley.io/api/v1/blacklists

List all blacklist entries (IP, country, email) for your shop. Blocked visitors are prevented from viewing your storefront.

json
{
  "blacklists": [
    {
      "id": "clxabc123",
      "type": "IP",
      "value": "192.168.1.1",
      "reason": "Fraud attempt",
      "createdAt": "2024-06-01T10:00:00.000Z"
    },
    {
      "id": "clxdef456",
      "type": "COUNTRY",
      "value": "RU",
      "reason": null,
      "createdAt": "2024-06-02T09:00:00.000Z"
    }
  ]
}
curl "https://selley.io/api/v1/blacklists" \
  -H "Authorization: Bearer YOUR_API_KEY"
GET
/blacklists/:id

GET https://selley.io/api/v1/blacklists/:id

Retrieve a single blacklist entry by ID.

NameTypeDescription
idrequiredstringBlacklist entry ID.
curl "https://selley.io/api/v1/blacklists/clxabc123" \
  -H "Authorization: Bearer YOUR_API_KEY"
POST
/blacklists

POST https://selley.io/api/v1/blacklists

Add a new entry to your shop's blacklist. type must be one of IP, COUNTRY, or EMAIL.

NameTypeDescription
typerequiredstringIP | COUNTRY | EMAIL
valuerequiredstringThe IP address, 2-letter country code, or email.
reasonstringOptional internal note.
curl -X POST "https://selley.io/api/v1/blacklists" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "IP",
    "value": "1.2.3.4",
    "reason": "Chargeback fraud"
  }'
PUT
/blacklists/:id

PUT https://selley.io/api/v1/blacklists/:id

Update an existing blacklist entry's type, value, or reason.

NameTypeDescription
typestringIP | COUNTRY | EMAIL
valuestringNew blocked value.
reasonstringNew reason note.
curl -X PUT "https://selley.io/api/v1/blacklists/clxabc123" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"reason": "Updated reason"}'
DELETE
/blacklists/:id

DELETE https://selley.io/api/v1/blacklists/:id

Remove an entry from your blacklist. The visitor will no longer be blocked.

json
{ "success": true }
curl -X DELETE "https://selley.io/api/v1/blacklists/clxabc123" \
  -H "Authorization: Bearer YOUR_API_KEY"
Categories

Endpoints: /api/v1/categories

GET
/categories

GET https://selley.io/api/v1/categories

List all categories for your account, sorted by sortOrder then creation date. Includes bound products and groups.

json
{
  "categories": [
    {
      "id": "clxabc123",
      "name": "Software",
      "sortOrder": 1,
      "unlisted": false,
      "products": [
        { "id": "...", "name": "Pro License", "productId": "rec_123" }
      ],
      "groups": [
        { "id": "...", "name": "Starter Bundle" }
      ],
      "createdAt": "2024-06-01T10:00:00.000Z"
    }
  ]
}
curl "https://selley.io/api/v1/categories" \
  -H "Authorization: Bearer YOUR_API_KEY"
GET
/categories/:id

GET https://selley.io/api/v1/categories/:id

Retrieve a single category with its bound products and groups.

NameTypeDescription
idrequiredstringCategory ID.
bash
curl "https://selley.io/api/v1/categories/clxabc123" \
  -H "Authorization: Bearer YOUR_API_KEY"
POST
/categories

POST https://selley.io/api/v1/categories

Create a new category and optionally bind products and/or groups to it.

NameTypeDescription
namerequiredstringCategory title.
sortOrderintegerDisplay order (lower = first). Default 0.
unlistedbooleanHide from storefront. Default false.
productIdsstring[]Array of product IDs to bind.
groupIdsstring[]Array of group IDs to bind.
curl -X POST "https://selley.io/api/v1/categories" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Software",
    "sortOrder": 1,
    "productIds": ["clxprod1", "clxprod2"]
  }'
PUT
/categories/:id

PUT https://selley.io/api/v1/categories/:id

Update category fields. Passing productIds or groupIds replaces the full bound list.

NameTypeDescription
namestringNew category name.
sortOrderintegerNew sort position.
unlistedbooleanShow or hide the category.
productIdsstring[]Replaces all bound products.
groupIdsstring[]Replaces all bound groups.
bash
curl -X PUT "https://selley.io/api/v1/categories/clxabc123" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Digital Goods", "sortOrder": 0}'
DELETE
/categories/:id

DELETE https://selley.io/api/v1/categories/:id

Delete a category. Products and groups in the category are not deleted.

json
{ "success": true }
bash
curl -X DELETE "https://selley.io/api/v1/categories/clxabc123" \
  -H "Authorization: Bearer YOUR_API_KEY"
Groups

Endpoints: /api/v1/groups

GET
/groups

GET https://selley.io/api/v1/groups

List all product groups (bundles) for your account, including the products inside each group.

json
{
  "groups": [
    {
      "id": "clxabc123",
      "name": "Starter Bundle",
      "description": "Best value pack",
      "discountPercent": 15,
      "sortOrder": 0,
      "unlisted": false,
      "products": [
        { "id": "...", "name": "...", "productId": "rec_1", "price": 9.99 }
      ],
      "createdAt": "2024-06-01T10:00:00.000Z",
      "updatedAt": "2024-06-01T10:00:00.000Z"
    }
  ]
}
curl "https://selley.io/api/v1/groups" \
  -H "Authorization: Bearer YOUR_API_KEY"
GET
/groups/:id

GET https://selley.io/api/v1/groups/:id

Retrieve a single group by ID, including its products.

NameTypeDescription
idrequiredstringGroup ID.
bash
curl "https://selley.io/api/v1/groups/clxabc123" \
  -H "Authorization: Bearer YOUR_API_KEY"
POST
/groups

POST https://selley.io/api/v1/groups

Create a new product group with an optional bundle discount. Bind products by passing productIds.

NameTypeDescription
namerequiredstringGroup name.
descriptionstringOptional description.
discountPercentnumberBundle discount percentage 0–100.
imagestringGroup image URL.
sortOrderintegerDisplay order. Default 0.
unlistedbooleanHide from storefront.
productIdsstring[]Product IDs to include in the group.
curl -X POST "https://selley.io/api/v1/groups" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Starter Bundle",
    "discountPercent": 15,
    "productIds": ["clxprod1", "clxprod2"]
  }'
PUT
/groups/:id

PUT https://selley.io/api/v1/groups/:id

Update group fields. Passing productIds replaces the group's product list.

NameTypeDescription
namestringNew name.
discountPercentnumberNew discount percentage.
sortOrderintegerNew display order.
unlistedbooleanShow/hide.
productIdsstring[]Replaces the full product list.
bash
curl -X PUT "https://selley.io/api/v1/groups/clxabc123" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"discountPercent": 20}'
DELETE
/groups/:id

DELETE https://selley.io/api/v1/groups/:id

Delete a group. Products in the group are not deleted.

json
{ "success": true }
bash
curl -X DELETE "https://selley.io/api/v1/groups/clxabc123" \
  -H "Authorization: Bearer YOUR_API_KEY"
Customers

Endpoints: /api/v1/customers — shop-scoped, read-only

GET
/customers

GET https://selley.io/api/v1/customers

List all customers who have placed orders in your shop. Sorted by creation date descending.

json
{
  "customers": [
    {
      "id": "clxabc123",
      "email": "[email protected]",
      "name": "Jane Smith",
      "phone": "+1234567890",
      "country": "US",
      "city": "New York",
      "totalOrders": 5,
      "totalSpent": 149.95,
      "lastOrderAt": "2024-06-10T15:00:00.000Z",
      "createdAt": "2024-01-05T08:00:00.000Z",
      "updatedAt": "2024-06-10T15:00:00.000Z"
    }
  ]
}
curl "https://selley.io/api/v1/customers?limit=50" \
  -H "Authorization: Bearer YOUR_API_KEY"
GET
/customers/:id

GET https://selley.io/api/v1/customers/:id

Retrieve a single customer by their ID including order stats and notes.

NameTypeDescription
idrequiredstringCustomer ID.
curl "https://selley.io/api/v1/customers/clxabc123" \
  -H "Authorization: Bearer YOUR_API_KEY"
Subscriptions

Endpoints: /api/v1/subscriptions — shop-scoped, read-only

GET
/subscriptions

GET https://selley.io/api/v1/subscriptions

List all recurring subscriptions for your shop. Includes product and customer details.

json
{
  "subscriptions": [
    {
      "id": "clxabc123",
      "productId": "clx_prod",
      "productName": "Pro Plan",
      "productProductId": "rec_pro",
      "customerId": "clx_cust",
      "customerEmail": "[email protected]",
      "status": "ACTIVE",
      "stripeSubscriptionId": "sub_xxxxxxxxxx",
      "stripeCustomerId": "cus_xxxxxxxxxx",
      "currentPeriodEnd": "2024-07-01T00:00:00.000Z",
      "createdAt": "2024-06-01T10:00:00.000Z",
      "canceledAt": null
    }
  ]
}
curl "https://selley.io/api/v1/subscriptions" \
  -H "Authorization: Bearer YOUR_API_KEY"
GET
/subscriptions/:id

GET https://selley.io/api/v1/subscriptions/:id

Retrieve a single subscription by ID including Stripe IDs and period end date.

NameTypeDescription
idrequiredstringSubscription ID.
curl "https://selley.io/api/v1/subscriptions/clxabc123" \
  -H "Authorization: Bearer YOUR_API_KEY"
Themes

Endpoints: /api/v1/themes

CLI & AI workflows

The Themes API lets you pull and push your active storefront theme as theme.json and custom.css — ideal for local development, CI, or AI-assisted theme editing.
GET
/themes

GET https://selley.io/api/v1/themes

Returns the active theme summary for your authenticated shop, including metadata for each theme slot in the bundle.

Response

json
{
  "activeThemeId": "default",
  "themes": [
    {
      "id": "default",
      "name": "My Theme",
      "owner": "merchant",
      "version": "1.0.0",
      "isActive": true,
      "shopSlug": "my-shop",
      "updatedAt": "2024-06-15T12:00:00.000Z"
    }
  ]
}

Request

curl "https://selley.io/api/v1/themes" \
  -H "Authorization: Bearer YOUR_API_KEY"
GET
/themes/:themeId

GET https://selley.io/api/v1/themes/:themeId

Download the full theme bundle: normalized config plus file representation (theme.json and custom.css).

NameTypeDescription
themeIdrequiredstringTheme identifier (typically default).
curl "https://selley.io/api/v1/themes/default" \
  -H "Authorization: Bearer YOUR_API_KEY"

Response fields

NameTypeDescription
id, name, owner, versionstringTheme metadata.
isActivebooleanWhether this theme is live on the storefront.
shopSlugstringYour shop slug for storefront URLs.
configobjectNormalized theme configuration (colors, layout, components).
filesobjectCLI file bundle with theme.json and custom.css strings.
updatedAtstringISO 8601 last update time.
PUT
/themes/:themeId

PUT https://selley.io/api/v1/themes/:themeId

Push an updated theme. Send either a full config object or a files bundle — not both required, but at least one must be present.

Request body

NameTypeDescription
configobjectFull theme config object (normalized server-side).
filesobjectPartial file bundle: { "theme.json": {...}, "custom.css": "..." }

Response

Returns the updated theme object (same shape as GET).

curl -X PUT "https://selley.io/api/v1/themes/default" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "files": {
      "custom.css": ":root { --accent: #4de500; }"
    }
  }'

Cache

After a successful PUT, the storefront cache for your shop slug is cleared so changes appear on the live shop within seconds.
Webhooks

Receive HTTP POST requests when events happen in your store

Overview

Webhooks send a POST request to a URL you provide whenever certain events occur (e.g. order paid, product updated, subscription renewed). Configure webhooks in the dashboard under Developers → Webhooks: add your endpoint URL and select which events to subscribe to. At least one event must be selected per webhook.

Each request includes a JSON body with event, data (detailed payload), timestamp, and source. Your endpoint should respond with 2xx to acknowledge receipt. Non-2xx responses are logged and may be retried.

Configure webhooks Webhook Logs & Simulator

Event types

Events are grouped by category. Select the events you need when creating a webhook. The data object structure depends on the event category (see Payload structure below).

Order

  • order:created · order:updated · order:partial · order:paid · order:paid:product
  • order:cancelled · order:disputed · order:cancelled:product · order:disputed:product
  • order:created:product · order:partial:product · order:updated:product

Query

  • query:created · query:replied

Feedback

  • feedback:received

Product

  • product:created · product:edited · product:stock · product:dynamic · product:mystery:revealed

Subscription

  • subscription:trial:started · subscription:trial:started:product · subscription:created · subscription:created:product
  • subscription:renewed · subscription:renewed:product · subscription:updated · subscription:updated:product
  • subscription:cancelled · subscription:cancelled:product · subscription:trial:ended · subscription:trial:ended:product
  • subscription:upcoming · subscription:upcoming:product

Request format

Every webhook is sent as POST to your URL with the following headers and body.

Headers

HeaderValue
Content-Typeapplication/json
User-AgentSelley-Webhook/1.0

Top-level body fields

NameTypeDescription
eventrequiredstringEvent identifier (e.g. order:paid, subscription:renewed).
datarequiredobjectEvent-specific payload. Structure described in Payload structure sections below.
timestampstringISO 8601 time when the webhook was sent.
sourcestringAlways "selley".

Your endpoint should respond with HTTP 200–299 to acknowledge receipt. Timeout is 15 seconds. Failed or non-2xx deliveries are logged in Developers → Webhook Logs where you can retry or view the payload.

Payload structure

The data object in each webhook is detailed and depends on the event category. Below are the full structures for Order, Subscription, Product, Query, and Feedback. All monetary values use the order/product currency; timestamps are Unix seconds unless noted.

json
{
  "event": "order:paid",
  "data": { /* see Order data below */ },
  "timestamp": "2024-06-15T14:32:00.000Z",
  "source": "selley"
}

Order data

For all order:* events, data includes order details, customer, product, gateway, fees, IP/location, status history, delivery info, and nested product block.

Identifiers & type: id, uniqid, type (PRODUCT), subtype, origin (e.g. EMBED).

Amounts: total, total_display, currency, exchange_rate, discount, fee_percentage, fee_breakdown, discount_breakdown.

Shop & customer: shop_id, name (shop name), customer_email, customer_id.

Product: product_id, product_title, product_type, quantity, subscription_id (null for one-time), subscription_time.

Metadata: custom_fields (object containing merchant-defined fields and values).

Gateway: gateway (STRIPE, PAYPAL, etc.), gateway_data, stripe_id / paypal_order_id etc.

Location & IP: country, location, ip, is_vpn_or_proxy, ip_info (ISP, city, region, device, browser, proxy/vpn/tor flags).

Status: status (e.g. COMPLETED), status_history, status_history_raw, created_at, updated_at.

Nested: product (full product object), products (array of line items), delivery_info, delivery, total_conversions (multi-currency/crypto).

json
{
  "id": 238987,
  "uniqid": "593fe6-bd6c461fa4-f713f6",
  "type": "PRODUCT",
  "origin": "EMBED",
  "total": 45.77,
  "total_display": "33.99",
  "currency": "USD",
  "shop_id": 5436,
  "customer_email": "[email protected]",
  "customer_id": "cst_xxx",
  "product_id": "6894accbf05b6",
  "product_title": "Semi-Annual Alpha Package",
  "product_type": "SERVICE",
  "quantity": 1,
  "custom_fields": {
    "Discord ID": "User#1234",
    "Account Name": "AlphaUser"
  },
  "subscription_id": null,
  "gateway": "STRIPE",
  "status": "COMPLETED",
  "discount": 0,
  "fee_percentage": 4,
  "country": "GB",
  "ip": "2a00:23c7:...",
  "ip_info": { "country_code": "GB", "city": "London", "ISP": "...", "vpn": 0, "proxy": 0 },
  "created_at": 1772131826,
  "updated_at": 1772131943,
  "status_history": [...],
  "gateway_data": { "type": "STRIPE", "label": "Stripe", "payment_reference": "pi_xxx" },
  "product": { "uniqid": "...", "title": "...", "price_display": "...", "currency": "...", ... },
  "products": [{ "uniqid": "...", "title": "...", "quantity": 1, "price": 45.77, "currency": "USD" }],
  "delivery_info": { "downloads": [], "license_keys": [], "external_urls": [...], "tracking_codes": [] },
  "total_conversions": { "USD": 45.77, "EUR": "39.38", "crypto": { "BTC": "...", "ETH": "..." } }
}

Subscription data

For subscription:* events, data includes which product, amount, billing interval, subscription and Stripe IDs, period dates, trial info, and customer.

NameTypeDescription
id / uniqidstringSubscription identifier.
shop_id, shop_namenumber / stringShop context.
product_id, product_uniqid, product_title, product_typestringProduct the subscription is for.
customer_id, customer_emailstringSubscriber.
subscription_id, stripe_subscription_id, stripe_price_idstringSubscription and Stripe references.
subscription_statusstringACTIVE, TRIALING, CANCELLED, etc.
amount, amount_display, currency, quantitynumber / stringRecurring amount and quantity.
billing_interval, recurring_interval_countstring / numbere.g. MONTH, 1.
current_period_start, current_period_endnumberUnix timestamps for current period.
trial_start, trial_endnumber | nullTrial period (if applicable).
gateway, country, ip, ip_infostring / objectPayment gateway and location.
created_at, updated_at, canceled_atnumber | nullTimestamps.
productobjectFull product block.

Product data

For product:created, product:edited, product:deleted, product:stock, product:stock:low, product:dynamic, data includes product details, pricing, stock, gateways, and metadata.

NameTypeDescription
id, uniqidstringProduct identifier.
shop_id, title, description, product_typestringShop and product info.
price, price_display, currencynumber / stringPricing.
quantity_min, quantity_max, stock, stock_warningnumberQuantity and stock levels.
gatewaysstring[]Enabled gateways (e.g. STRIPE, PAYPAL).
image_name, image_storage, unlisted, sort_prioritystring / number / booleanMedia and visibility.
volume_discountsarraye.g. [{ type: PERCENTAGE, value: 5, quantity: 10 }].
created_at, updated_atnumberUnix timestamps.

Mystery box data

For product:mystery:revealed, Selley fires after provably fair rolls complete for a mystery box line item. The webhook data identifies the order and product; full outcome details (rolled item, commitment hash, server seed, roll proof) are stored on the order item and returned on the customer invoice and order APIs.

NameTypeDescription
orderIdrequiredstringInternal order ID.
orderNumberrequiredstringCustomer-facing order number (e.g. ORD-12345).
productIdrequiredstringMystery box product ID.
productNamerequiredstringProduct title at time of purchase.

Full reveal data

Fetch GET /orders/:id or load the invoice page to access per-quantity outcomes with verification fields. Customers verify fairness from the invoice reveal panel without needing API access.
json
{
  "event": "product:mystery:revealed",
  "data": {
    "orderId": "clxord123",
    "orderNumber": "ORD-48291",
    "productId": "rec_mystery01",
    "productName": "Premium Case"
  },
  "timestamp": "2024-06-15T14:32:00.000Z",
  "source": "selley"
}

Dynamic fulfillment data

The INVOICE.ITEM.DELIVER-DYNAMIC event is sent to the Dynamic Webhook URL configured on individual dynamic products. This payload is extremely rich and provides full context for the order, the customer (including custom fields), and the specific line item.

Identifiers: id (Order ID), order_number, unique_id, shop_customer_id.

Customer: Linked customer record including email, name, and extracted discord_id / discord_username if available from custom fields.

Coupon: Applied coupon details with code, discount value, and type.

Item: The specific product instance with price, quantity, and its own custom_fields block.

json
{
  "event": "INVOICE.ITEM.DELIVER-DYNAMIC",
  "id": "ord_238987",
  "order_number": "ORD-SELLEY-1234",
  "coupon_id": "cpn_555",
  "coupon_discount": "5.00",
  "gateway_fee": "0.45",
  "shop_customer_id": "cst_888",
  "created_at": "2024-06-15T14:32:00.000Z",
  "updated_at": "2024-06-15T14:32:10.000Z",
  "gateway": "STRIPE",
  "email": "[email protected]",
  "status": "completed",
  "price": "45.00",
  "currency": "USD",
  "amount": 1,
  "custom_fields": {
    "Discord ID": "User#0001",
    "Special Instructions": "Please deliver via API"
  },
  "customer": {
    "id": "cst_888",
    "email": "[email protected]",
    "name": "John Doe",
    "discord_id": "000000000000000000",
    "discord_username": "john_doe"
  },
  "coupon": {
    "id": "cpn_555",
    "code": "SAVE5",
    "discount": "5.00",
    "type": "fixed"
  },
  "item": {
    "id": "itm_999",
    "invoice_id": "ord_238987",
    "product_id": "prod_777",
    "status": "completed",
    "price": "45.00",
    "quantity": 1,
    "total_price": "45.00",
    "custom_fields": { ... },
    "product": {
      "id": "prod_777",
      "name": "Dynamic Service X"
    }
  }
}

Query & Feedback data

Query (query:created, query:replied): data includes id, ticket_id, shop_id, customer_email, customer_id, subject, message, status, reply_count, created_at, updated_at.

Feedback (feedback:received): data includes id, shop_id, product_id, product_title, order_id, customer_email, rating, comment, created_at.

Logs & Simulator

In the dashboard, Developers → Webhook Logs shows every delivery attempt: URL, event, response status, retry count, and created time. You can Send again to retry a failed delivery or open Payload to view the exact JSON that was sent.

Use the Simulator to send a test webhook to a configured URL: choose a webhook and an event, then trigger a delivery. The payload will match the structure documented above (with sample data). This helps verify your endpoint and payload handling without creating real orders or subscriptions.

Open Webhook Logs & Simulator
Embed

Add buy buttons and checkout to any site

Overview

The Embed lets you place a product or bundle (group) checkout on any external website. Customers see your product, choose quantity, and complete payment without leaving the host site. The flow is a modal that opens from your embed script; checkout and payment happen inside the modal.

Generate your embed code in the dashboard under Developers → Embed: select a product or group, optionally set button text and styles, then copy the script and HTML snippet. Add them to your site where you want the button to appear.

Open Embed generator

Usage

Include the Selley embed script on your page, then add a button or link with data-selley-store, and for a single product data-selley-product (product ID from your store), or for a bundle data-selley-group (group ID). When the user clicks, a modal opens with the product/group details and a gateway selection that leads to full checkout. No redirect to Selley is required; the flow stays in the modal.

Example: single product

html
<script src="https://your-domain.com/embed.js" async></script>
<button data-selley-store="your-store" data-selley-product="rec_abc123">
  Buy now
</button>

Example: bundle (group)

html
<button data-selley-store="your-store" data-selley-group="group_xyz">
  Buy bundle
</button>

Your store subdomain (e.g. your-store.myselley.com) or custom domain is used as the base. The embed script and CSS URL are shown in the dashboard Embed page.

On this page

IntroductionQuick startAPI referenceAuthenticationMultiple shopsProduct typesFulfillment & deliveryPaginationRate limitsErrorsOverviewBefore purchaseCommitment hashHow rolls workVerify outcomesBuyer checklistList productsGet a productCreate a productUpdate a productDelete a productList ordersGet an orderUpdate orderOverviewEvent typesRequest formatPayload structureOrder dataSubscription dataProduct dataMystery box dataDynamic fulfillmentQuery & feedbackLogs & simulator
Selley API v1 · Need help? Developer settings