Seller API overview
The Seller API is the panel’s own API, for your shop, Discord bot or scripts. It uses a token instead of a Google sign-in, and it can do what the panel’s License keys, Users, Subscriptions, Variables, Files and Audit log pages do.
Base URL
Section titled “Base URL”https://panel.eonauth.fyi/seller/v1Every path in this reference is relative to it, for example POST /seller/v1/license/create.
Authentication
Section titled “Authentication”Create a token in the panel under Seller API & stores → New token (see Seller API & stores). Send it on every request:
Authorization: Bearer eon_sk_...- A token belongs to one app. Calls about any other app answer
403 FORBIDDEN. - It is shown once. Only a hash is stored, so a lost token can’t be recovered; revoke it and make a new one.
- Everything a token changes is in the audit log as
api-token:<token name>.
Keep tokens on your server. Never put one in a client app, a browser, or a public repository.
Tokens and managers
Section titled “Tokens and managers”Managers don’t get the Seller API by default. Creating a token needs App settings: All on the app, plus All on every scope ticked, and only an owner can grant All. Otherwise an owner creates the token and hands it over.
A token is not tied to a person. It keeps All on its scopes whatever the holder’s own permissions are. The keys it makes belong to the token, not the holder. It also keeps working after the manager who made or holds it is disabled or deleted. See Giving a token to someone.
Scopes
Section titled “Scopes”A token acts like a manager with All access to the features you ticked, and nothing else.
| Scope | Lets the token |
|---|---|
licenses |
Generate, list, search, revoke, delete, import and export keys (Licenses) |
users |
List, ban, pause, rename, delete users; time tools; blacklist; free devices (Users) |
logins |
Read a user’s sessions and sign-in events (Logins and audit) |
subscriptions |
List, create, edit and delete tiers (Subscriptions) |
variables |
List, read, create, edit and delete variables (Variables) |
files |
List, upload, edit and delete files (Files) |
audit |
Read the app’s audit log (Logins and audit) |
A call outside the token’s scopes answers 403 FORBIDDEN. A few routes are never open to tokens:
owner-only actions (forced HWID resets, resetting every HWID) and anything under App settings
(such as update builds). They are marked not available to tokens in this reference.
Requests
Section titled “Requests”GETroutes take query parameters.POSTroutes take a JSON body. SendContent-Type: application/json. Unknown fields are refused, so a typo can’t be silently ignored.- The one exception is file upload, which takes the raw file as the body.
- IDs carry a prefix:
app_,tier_,lic_,usr_,var_,file_,bl_,dev_,bat_, then 32 hex characters. - Times are unix seconds.
Responses
Section titled “Responses”Success:
{ "ok": true, "data": { "...": "..." } }Failure:
{ "ok": false, "error": { "code": "INVALID_REQUEST", "message": "duration_days: duration_days is required for preset 'custom' and not allowed otherwise" } }Some errors add error.details. Check ok (or the HTTP status) first, then read data or
error.code. The message is meant for people and may change; the code won’t.
Lists and paging
Section titled “Lists and paging”List routes take page (from 1) and page_size (1 to 100, default 50) and answer:
{ "ok": true, "data": { "items": [], "total": 120, "page": 1, "page_size": 50 } }Rate limits
Section titled “Rate limits”120 requests per minute per token, counted per Cloudflare location. Over the limit you get:
HTTP/1.1 429 Too Many RequestsRetry-After: 60{ "ok": false, "error": { "code": "RATE_LIMITED", "message": "Too many requests" } }Wait for Retry-After seconds. If your shop sends bursts, queue the calls instead of retrying in a
loop.
Errors
Section titled “Errors”| HTTP | code |
When |
|---|---|---|
| 400 | INVALID_REQUEST |
A field is missing, has the wrong type, or breaks a rule. The message names the field |
| 401 | UNAUTHORIZED |
No token, a malformed one (Missing or malformed API token), or an unknown or revoked one (Invalid or revoked API token) |
| 402 | PLAN_LIMIT |
Your plan’s limit for keys, users, apps or storage is reached |
| 403 | FORBIDDEN |
Outside the token’s scopes, another app, or owner-only |
| 403 | QUOTA_EXCEEDED |
A trial or free key limit is reached |
| 404 | NOT_FOUND |
No such record in this app, or no such route |
| 405 | INVALID_REQUEST |
A method other than GET or POST |
| 409 | CONFLICT |
The key mask can’t make enough new keys, or a key or name is taken |
| 409 | HWID_RESET_COOLDOWN, HWID_RESET_QUOTA_EXCEEDED |
A HWID reset is too soon or over the allowance |
| 413 | INVALID_REQUEST |
A file is too large |
| 429 | RATE_LIMITED |
See Rate limits |
| 500 | INTERNAL |
Something went wrong on our side; safe to retry reads, check before retrying writes |
All codes are listed in Error codes.
Your first call
Section titled “Your first call”Generate two month keys for the Default tier. Find your app ID and tier IDs with
GET /tiers.
curl -X POST https://panel.eonauth.fyi/seller/v1/license/create \ -H "Authorization: Bearer $EONAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{"app_id":"app_ea9745b1df9e4a9bacb22cb1c200993b","tier_id":"tier_048ef9f8f38c4ab2af05db05bbde4aed","preset":"month","quantity":2,"note":"order 1042"}'const res = await fetch("https://panel.eonauth.fyi/seller/v1/license/create", { method: "POST", headers: { Authorization: `Bearer ${process.env.EONAUTH_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ app_id: "app_ea9745b1df9e4a9bacb22cb1c200993b", tier_id: "tier_048ef9f8f38c4ab2af05db05bbde4aed", preset: "month", quantity: 2, note: "order 1042", }),});const body = await res.json();if (!body.ok) throw new Error(`${body.error.code}: ${body.error.message}`);const keys = body.data.licenses.map((l) => l.key);import os, requests
res = requests.post( "https://panel.eonauth.fyi/seller/v1/license/create", headers={"Authorization": f"Bearer {os.environ['EONAUTH_TOKEN']}"}, json={ "app_id": "app_ea9745b1df9e4a9bacb22cb1c200993b", "tier_id": "tier_048ef9f8f38c4ab2af05db05bbde4aed", "preset": "month", "quantity": 2, "note": "order 1042", }, timeout=15,)body = res.json()if not body["ok"]: raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")keys = [l["key"] for l in body["data"]["licenses"]]The response holds the keys. They are shown only here; store or deliver them right away.
{ "ok": true, "data": { "batch_id": "bat_00c349d64f1d4fd8a44d9b4dc004bc1e", "app_id": "app_ea9745b1df9e4a9bacb22cb1c200993b", "tier": "Default", "preset": "month", "type": "days", "duration_days": 30, "key_mask": "*****-*****-*****-*****-*****", "created_at": 1794497606, "kind": "paid", "price_cents": 0, "base_price_cents": 0, "licenses": [ { "id": "lic_59872ba8a1e94a5db020e505c9790731", "key": "7DY5Q-3BK6F-K8SVH-J4MRT-2WQ9Z" }, { "id": "lic_8e1f0c2b7a5d4e3f9a6b1c0d2e3f4a5b", "key": "N4XKP-8QJ2M-RT6VW-C3HZL-9YBDA" } ], "notice": "Keys are shown once. Only their hashes are stored." }}