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 callbackSecret values; 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 filter
  • providerstripe | nowpayments
  • status — 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 pages
  • format=csv or Accept: 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/status body { "transaction": "pi_…" }
  • POST /v1/transactions body { "transactionsId": ["pi_…"], "limits": "week" }limits is all | today | week | month (relative windows only; max 200 rows). Optional email filter.

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"
  • statusopen | closed | all
  • search — dispute id, PaymentIntent, or customer email
  • GET /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 filters
  • format=csv or Accept: 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 matured means the settlement due date has passed; hold means not yet due
  • Optional ?period=2026-07-H1 filters to one half-month; also available as GET /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_withdrawals rows; pending/processing/completed amounts reduce available
  • Locked arbitrage is already excluded from available balance — you cannot withdraw it
  • First withdrawal requires approved business verification (KYC_REQUIRED)
  • Optional ?status=pending lists 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/payment derived 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 refunds table in schema. Stripe fraud/dispute refunds are recorded via Pay license revocation stamps and (when synced) payment_transactions provider status. Cross-check open chargebacks via GET /v1/disputes.
  • NET 15 period maturity is exposed on balance/settlements for reporting; withdrawable available matches Dashboard (revenue − held withdrawals − disputes ± adjustments − locked arbitrage).

17. Error handling

  • 401 — missing/invalid API key or JWT
  • 403 — Free tier or wrong role for the route
  • 404 — order/dispute not found or not owned by the seller
  • 400 — invalid query (bad status enum, limit out of range)
  • Retry 5xx with backoff; log x-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.