Seller reporting without dashboard login
Sellers can consult deposits, period volumes, transaction status, and disputes with a Pro+ app API key
against https://api.licensechain.app and Pay consult endpoints on
https://pay.licensechain.app. You do not need a browser session on the Dashboard.
Related guides: Transaction details, Pay merchant webhooks, Pay Mirror API, Products API.
Seller Core API: Period volume, analytics, available balance, NET 15 settlements, withdrawals, refunds, and CSV export are available with a Pro+ app API key — no Dashboard session required. See sections 2–6 below and the detailed API index.
Admin analytics: GET /v1/analytics/* remains admin-role only and returns
platform-wide metrics — not seller-scoped. Do not use it for merchant reconciliation.
1. Authentication
Create an app in the Dashboard and use its API key (Bearer). Integrations commonly store it as
LICENSECHAIN_API_TOKEN with base URL LICENSECHAIN_API_BASE_URL=https://api.licensechain.app
(same pattern as Laravel sellers that also sync product prices).
Authorization: Bearer $LICENSECHAIN_API_TOKEN
Content-Type: application/json - API access requires Pro, Business, or Enterprise tier (Free keys receive
403). - Seller-scoped routes resolve ownership from the app owner (
userId), not from a Dashboard cookie. - Never commit API keys or product
callbackSecretvalues; keep them in env vars only.
2. Period volume and deposits (primary)
GET /v1/orders is the seller-facing list for paid and pending checkouts bound to your products.
Filter by calendar period with dateFrom / dateTo (ISO date or datetime strings).
curl -sS -G "https://api.licensechain.app/v1/orders" \
-H "Authorization: Bearer $LICENSECHAIN_API_TOKEN" \
--data-urlencode "dateFrom=2026-07-01" \
--data-urlencode "dateTo=2026-07-15" \
--data-urlencode "limit=100" \
--data-urlencode "offset=0" Query parameters:
dateFrom,dateTo— period window (inclusive bounds as parsed by the API)productId— single product filterprovider—stripe|nowpaymentsstatus— license/order status filter (normalized seller statuses)search— email / payment id search (max 200 chars)limit(1–100, default 20),offset— pagination; server fetches up to 1000 then pagesformat=csvorAccept: text/csv— CSV export (up to 10k rows; ignores pagination)
curl -sS -G "https://api.licensechain.app/v1/orders" \
-H "Authorization: Bearer $LICENSECHAIN_API_TOKEN" \
-H "Accept: text/csv" \
--data-urlencode "dateFrom=2026-07-01" \
--data-urlencode "dateTo=2026-07-15" \
-o orders-h1.csv {
"orders": [
{
"id": "lic_abc123",
"licenseKey": "LC-XXXXXX-XXXXXX-XXXXXX",
"email": "buyer@example.com",
"status": "active",
"transactionStatus": "paid",
"refunded": false,
"price": 29.9,
"currency": "EUR",
"provider": "stripe",
"stripePaymentIntentId": "pi_3RxExample123",
"nowpaymentsPaymentId": null,
"product": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Plan STARTER",
"currency": "EUR"
},
"createdAt": "2026-07-03T14:22:01.000Z",
"updatedAt": "2026-07-03T14:22:05.000Z"
}
],
"total": 1,
"limit": 100,
"offset": 0,
"hasMore": false
} How to reconcile volume for a period: page until hasMore is false, sum
price for rows with transactionStatus === "paid", group by currency
and optionally product.id. That is the API equivalent of a deposits/volume report for the window.
3. Single order / irregular movement consult
GET /v1/orders/:id accepts license id, license key, Stripe PaymentIntent id, or NOWPayments id.
Stripe rows may include live stripeStatus and an attached dispute summary.
curl -sS "https://api.licensechain.app/v1/orders/pi_3RxExample123" \
-H "Authorization: Bearer $LICENSECHAIN_API_TOKEN" {
"order": {
"id": "lic_abc123",
"transactionStatus": "paid",
"stripeStatus": "succeeded",
"stripePaymentIntentId": "pi_3RxExample123",
"dispute": null
}
} 4. Transaction status (compat batch)
Prefer /v1/orders for period reporting. Use these when you already store payment intent ids
(support tooling, post-webhook checks):
POST /v1/transaction/statusbody{ "transaction": "pi_…" }-
POST /v1/transactionsbody{ "transactionsId": ["pi_…"], "limits": "week" }—limitsisall|today|week|month(relative windows only; max 200 rows). Optionalemailfilter.
Full examples: Transaction details.
5. Disputes (chargebacks)
curl -sS -G "https://api.licensechain.app/v1/disputes" \
-H "Authorization: Bearer $LICENSECHAIN_API_TOKEN" \
--data-urlencode "status=open" \
--data-urlencode "limit=50" status—open|closed|allsearch— dispute id, PaymentIntent, or customer emailGET /v1/disputes/:id— detail including evidence deadline and deduction flags
{
"disputes": [
{
"id": "disp_001",
"stripeDisputeId": "dp_1AbCdEf",
"stripePaymentIntentId": "pi_3RxExample123",
"amount": 29.9,
"currency": "EUR",
"disputeFee": 15,
"status": "needs_response",
"reason": "fraudulent",
"evidenceDueBy": "2026-07-20T08:00:00.000Z",
"balanceDeducted": false,
"deductedAmount": 0,
"customerEmail": "buyer@example.com",
"productName": "Plan STARTER",
"openedAt": "2026-07-12T08:00:00.000Z",
"closedAt": null,
"isOpen": true
}
],
"total": 1,
"limit": 50,
"offset": 0
} 6. Seller analytics aggregate
GET /v1/seller/analytics mirrors Dashboard seller analytics (revenue by currency, MoM change,
daily series, payment-method / billing / country / referral breakdowns). Seller-scoped via API key.
curl -sS -G "https://api.licensechain.app/v1/seller/analytics" \
-H "Authorization: Bearer $LICENSECHAIN_API_TOKEN" \
--data-urlencode "dateFrom=2026-07-01" \
--data-urlencode "dateTo=2026-07-15" dateFrom,dateTo,productId— optional filtersformat=csvorAccept: text/csv— flattened metrics CSV
7. Balance and NET 15 settlements
GET /v1/seller/balance returns available payout balance per currency (revenue − withdrawals −
dispute deductions ± admin adjustments − locked arbitrage) plus NET 15 H1/H2 settlement periods
(matured vs hold) with generation windows and due dates.
curl -sS "https://api.licensechain.app/v1/seller/balance" \
-H "Authorization: Bearer $LICENSECHAIN_API_TOKEN" - H1 (days 1–15 UTC) settles on the 30th of the same month (or last day if shorter)
- H2 (days 16–end) settles on the 15th of the next month
- Period status
maturedmeans the settlement due date has passed;holdmeans not yet due - Optional
?period=2026-07-H1filters to one half-month; also available asGET /v1/seller/settlements
8. Withdrawals (payout ledger)
GET /v1/seller/withdrawals returns the seller payout ledger (withdrawals, plan balance payments,
dispute deductions, admin adjustments, arbitrage holds) — same shape as Dashboard.
POST /v1/seller/withdrawals requests a payout against
GET /v1/seller/balance available math (fee percent, min amount, daily limit, KYC gate, AML).
curl -sS "https://api.licensechain.app/v1/seller/withdrawals?limit=50" \
-H "Authorization: Bearer $LICENSECHAIN_API_TOKEN"
curl -sS -X POST "https://api.licensechain.app/v1/seller/withdrawals" \
-H "Authorization: Bearer $LICENSECHAIN_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "amount":150,"currency":"USD","cryptoCurrency":"USDC","network":"BASE" }' - Writes real
seller_withdrawalsrows; pending/processing/completed amounts reduceavailable - Locked arbitrage is already excluded from available balance — you cannot withdraw it
- First withdrawal requires approved business verification (
KYC_REQUIRED) - Optional
?status=pendinglists only withdrawal rows with that status
9. Refunds
GET /v1/refunds prefers real payment_transactions rows with provider refund status
(source: "payment_transaction"). When no ledger refund exists for a payment, it falls back to
license-revoke metadata from Pay fraud/dispute flows (source: "license_revoke" —
revoked:fraud_refund|dispute|early_fraud_warning).
Order list/detail refunded uses both sources.
curl -sS -G "https://api.licensechain.app/v1/refunds" \
-H "Authorization: Bearer $LICENSECHAIN_API_TOKEN" \
--data-urlencode "dateFrom=2026-07-01" \
--data-urlencode "limit=50" 10. License counts and revenue
GET /v1/licenses remains app-scoped inventory. GET /v1/licenses/stats returns
app-scoped counts plus split revenue: appRevenue, sellerRevenue,
revenueScope (seller|app), and preferred revenue
(seller product revenue when the caller owns products). For period analytics prefer
/v1/orders or /v1/seller/analytics.
11. Pay: live deposit confirmation (no Dashboard)
Mirror checkout sellers (Next.js and Laravel integrations in the field) confirm purchases server-to-server:
curl -sS -X POST "https://pay.licensechain.app/api/checkout/mirror/validate" \
-H "Content-Type: application/json" \
-d '{
"license": "$LCG_FROM_CALLBACK_OR_WEBHOOK",
"email": "buyer@example.com"
}'
Response when verified includes verified: true and purchase metadata
(transactionId, productId, timestamps). Guide:
Pay Mirror API.
Merchant webhooks (push deposits / failures):
- Receiver path convention:
POST {origin}/api/graphexia/webhooks/paymentderived from product success URL - Headers:
x-lc-pay-signature,x-lc-pay-timestamp,x-lc-pay-event-id,x-lc-pay-idempotency-key,x-lc-pay-event-type - Event types:
payment.confirmed,payment.pending,payment.failed,payment.canceled - Signature message:
{timestamp}.{rawJsonBody}HMAC-SHA256 with product callback secret
After payment.confirmed, production sellers re-validate with mirror validate, then mark their
local order paid (idempotent). See
Pay merchant webhooks and
signing models.
12. Product price sync (checkout hygiene)
Before redirecting to Pay, some sellers align catalog price with the order amount:
curl -sS -X PATCH "https://api.licensechain.app/v1/products/$MIRROR_PRODUCT_ID" \
-H "Authorization: Bearer $LICENSECHAIN_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "price": 29.90, "currency": "EUR" }' 13. Integration patterns observed
| Capability | Typical Mirror seller (Next.js) | Typical Mirror seller (Laravel) | LicenseChain surface |
|---|---|---|---|
| Checkout | Build mirror payload → pay…/checkout/{productId}?payload= | Same URL builder + per-tier product ids | Pay hosted checkout |
| Confirm deposit | Webhook + mirror validate + local order PATCH | Webhook + mirror validate + mark order validated | Pay webhooks + /api/checkout/mirror/validate |
| Core API usage today | Often none for reporting | PATCH /v1/products/:id price sync | Products + reporting routes below |
| Period stats / refunds / NET 15 | Local DB optional | GET /v1/seller/analytics, /v1/seller/balance, /v1/seller/withdrawals, /v1/refunds | |
14. Example: period volume script (Node)
const base = process.env.LICENSECHAIN_API_BASE_URL || "https://api.licensechain.app";
const token = process.env.LICENSECHAIN_API_TOKEN;
async function fetchAllOrders(dateFrom, dateTo) {
const orders = [];
let offset = 0;
const limit = 100;
for (;;) {
const url = new URL(base + "/v1/orders");
url.searchParams.set("dateFrom", dateFrom);
url.searchParams.set("dateTo", dateTo);
url.searchParams.set("limit", String(limit));
url.searchParams.set("offset", String(offset));
const res = await fetch(url, {
headers: { Authorization: "Bearer " + token },
});
if (!res.ok) throw new Error("orders " + res.status + ": " + (await res.text()));
const body = await res.json();
orders.push(...(body.orders || []));
if (!body.hasMore) break;
offset += limit;
}
return orders;
}
const rows = await fetchAllOrders("2026-07-01", "2026-07-15");
const byCurrency = {};
for (const row of rows) {
if (row.transactionStatus !== "paid") continue;
const c = row.currency || "USD";
byCurrency[c] = (byCurrency[c] || 0) + Number(row.price || 0);
}
console.log({ count: rows.length, byCurrency }); 15. Example: webhook → validate (PHP sketch)
Same lifecycle as Laravel Mirror sellers: verify HMAC, accept payment.confirmed, then validate.
$raw = file_get_contents("php://input");
$signature = $_SERVER["HTTP_X_LC_PAY_SIGNATURE"] ?? "";
$timestamp = $_SERVER["HTTP_X_LC_PAY_TIMESTAMP"] ?? "";
$expected = hash_hmac("sha256", $timestamp . "." . $raw, getenv("MIRROR_PAGE_CALLBACK_SECRET"));
if (!hash_equals($expected, $signature)) { http_response_code(401); exit; }
$payload = json_decode($raw, true);
if (($payload["eventType"] ?? "") !== "payment.confirmed") {
echo json_encode(["ok" => true, "ignored" => true]);
exit;
}
$res = Http::post("https://pay.licensechain.app/api/checkout/mirror/validate", [
"license" => $payload["license"],
"email" => $payload["email"],
]);
// Then mark local order paid using $payload["orderId"] when present. 16. Notes
-
There is no dedicated Stripe
refundstable in schema. Stripe fraud/dispute refunds are recorded via Pay license revocation stamps and (when synced)payment_transactionsprovider status. Cross-check open chargebacks viaGET /v1/disputes. -
NET 15 period maturity is exposed on balance/settlements for reporting; withdrawable
availablematches Dashboard (revenue − held withdrawals − disputes ± adjustments − locked arbitrage).
17. Error handling
401— missing/invalid API key or JWT403— Free tier or wrong role for the route404— order/dispute not found or not owned by the seller400— invalid query (bad status enum, limit out of range)- Retry
5xxwith backoff; logx-lc-pay-event-id/ PaymentIntent ids for support
18. Env var checklist (redacted)
LICENSECHAIN_API_BASE_URL=https://api.licensechain.app
LICENSECHAIN_API_TOKEN=lc_app_••••••••
LICENSECHAIN_PAY_BASE_URL=https://pay.licensechain.app
LICENSECHAIN_PAY_CURRENCY=EUR
MIRROR_PRODUCT_ID_PLATINUM=••••
MIRROR_PRODUCT_ID_GOLD=••••
MIRROR_PRODUCT_ID_VIP=••••
MIRROR_PAGE_CALLBACK_PLATINUM=••••
MIRROR_PAGE_CALLBACK_GOLD=••••
MIRROR_PAGE_CALLBACK_VIP=••••
Product callback secrets are per product (Dashboard product record / callbackSecret).
Do not paste live secrets into tickets or docs.