Reseller API · v1
Lynx Reseller API
Browse the complete reference below. Use the explorer to try requests and generate client code.
Base URLhttps://api.lynxcheats.com/api/v1
This API gives your storefront or your bot what it needs to sell Lynx packages. Read your balance. Read your prices next to the customer prices. Generate keys, and get a claim link with each key.
Two words in this document look similar. An API key is the credential that identifies your integration. A key is a license that you sell to a customer.
Get started
- Open the dashboard. Go to Reseller → API.
- Create an API key. Copy it. The dashboard shows it one time only.
- Store the API key on your server. Use an environment variable.
- Call
GET /api/v1/mewith the API key. This confirms that the API key works. - Call
GET /api/v1/productsto read your prices. - Call
POST /api/v1/keysto buy keys and get their claim links.
Authentication
Send your API key in the Authorization header of each request. The
/openapi.json endpoint is the one exception.
Authorization: Bearer lyx_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
The server keeps only a hash of your API key. No page can show the API key to you again. If you lose an API key, revoke it and create another one.
An API key can spend your balance. Treat it like a password. Keep it on your server. Do not put it in a browser, in an app bundle, or in a public repository.
Scopes
Each API key holds a set of scopes. A scope permits one group of operations.
The server refuses a request that is outside the scopes of the API key, and
answers 403 forbidden.
Give each integration only the scopes that it needs. A price bot needs
catalog:read and nothing more.
| scope | permits |
|---|---|
account:read |
Read your reseller profile, your group, and your discount |
balance:read |
Read your balance and your credit limit |
catalog:read |
Read packages, plans, your prices, and customer prices |
keys:read |
List and read the keys in your inventory |
keys:write |
Generate keys |
orders:read |
Read your purchase history |
Money and idempotency
POST /api/v1/keys charges your balance.
The endpoint is idempotent on the idempotencyKey field. If you send the same
value again, the server charges you one time. The response holds the same keys
and alreadyMinted: true.
Make one idempotency key for each order. Use that same value for each retry of that order. A new value on each attempt charges you two times.
Claim links
Each key in a response has a claimUrl field. Give the customer the key, or
the link, or both.
The link opens a claim page. The page shows the package, your store name, and your message. The customer then redeems the key. Gift codes use this same page, so one flow serves both.
Set senderLabel and message when you generate a batch. These two fields
control what the customer sees. You can change both later in the dashboard.
Errors
A failed request returns this shape. The HTTP status matches the code.
{ "error": { "code": "invalid_request", "message": "quantity must be a number" } }
| code | status | meaning |
|---|---|---|
unauthorized |
401 | The API key is missing, malformed, unknown, or revoked. |
forbidden |
403 | The API key is valid, but it does not hold the necessary scope. |
not_found |
404 | The endpoint does not exist, or the key is not in your inventory. |
invalid_request |
400 | A field is missing, or its value is out of range. |
rate_limited |
429 | You sent too many requests. The message tells you when to retry. |
server_error |
500 | The server failed. A retry of an idempotent request is safe. |
Rate limits
Each API key holds two budgets. Read requests and write requests do not share a budget.
- Read: 600 each hour, with a burst of 120.
- Write: 120 each hour, with a burst of 30.
Do not share one API key between integrations that do not relate to each other. Create a second API key instead.
Pagination
List endpoints accept two query parameters. limit sets the page size, from 1
to 200. cursor continues a previous listing.
A list response holds three fields: data, hasMore, and nextCursor.
To read the next page, send nextCursor as the cursor parameter. Stop when
hasMore is false.
GET/me
Read your reseller profile
Returns your uid, your username, your key prefix, and your reseller group. Call this endpoint once at start-up. It confirms that an API key works, and it tells you your discount.
Authentication: Authorization: Bearer <API key>
Responses
200 Your reseller profile
application/json
Schema: Account
View JSON schema
{
"$ref": "#/components/schemas/Account"
}401 The server refused the request.
application/json
Schema: Error
View JSON schema
{
"$ref": "#/components/schemas/Error"
}403 The server refused the request.
application/json
Schema: Error
View JSON schema
{
"$ref": "#/components/schemas/Error"
}GET/balance
Read your balance
Read this endpoint before you generate keys. The availableCents field already includes your credit limit. A purchase succeeds if its total is at or below that number.
Authentication: Authorization: Bearer <API key>
Responses
200 Your balance
application/json
Schema: Balance
View JSON schema
{
"$ref": "#/components/schemas/Balance"
}401 The server refused the request.
application/json
Schema: Error
View JSON schema
{
"$ref": "#/components/schemas/Error"
}403 The server refused the request.
application/json
Schema: Error
View JSON schema
{
"$ref": "#/components/schemas/Error"
}GET/products
Read packages, plans, and both prices
Returns each active package with the plans that you can buy. Each plan holds customerPriceCents and yourPriceCents. customerPriceCents is the storefront price. yourPriceCents is your cost. Your bot can set its own prices from these two numbers, and it does not need a hard-coded discount. The list holds only the plans that are public on a platform you sell.
Authentication: Authorization: Bearer <API key>
Responses
200 The packages you can buy
application/json
| Field | Type | Description |
|---|---|---|
data | Package[] |
View JSON schema
{
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Package"
}
}
}
}401 The server refused the request.
application/json
Schema: Error
View JSON schema
{
"$ref": "#/components/schemas/Error"
}403 The server refused the request.
application/json
Schema: Error
View JSON schema
{
"$ref": "#/components/schemas/Error"
}GET/keys
List your keys
Returns your keys, newest first. Use the status parameter to select one state. Unsold stock has the status dormant. A redeemed key has the status active.
Authentication: Authorization: Bearer <API key>
Parameters
| Name | Location / type | Description |
|---|---|---|
limit | queryinteger | How many rows one page holds, from 1 to 200. |
cursor | querystring | The `nextCursor` value from the previous page. |
status | querystring | Return only the keys in this state. Values: dormant, active, expired, revoked |
productId | querystring | Return only the keys for this package. |
from | queryinteger | Return only the keys made at or after this unix time in milliseconds. |
to | queryinteger | Return only the keys made at or before this unix time in milliseconds. |
Responses
200 One page of keys
application/json
| Field | Type | Description |
|---|---|---|
data | Key[] | |
hasMore | boolean | |
nextCursor | string | null |
View JSON schema
{
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Key"
}
},
"hasMore": {
"type": "boolean"
},
"nextCursor": {
"type": [
"string",
"null"
]
}
}
}400 The server refused the request.
application/json
Schema: Error
View JSON schema
{
"$ref": "#/components/schemas/Error"
}401 The server refused the request.
application/json
Schema: Error
View JSON schema
{
"$ref": "#/components/schemas/Error"
}403 The server refused the request.
application/json
Schema: Error
View JSON schema
{
"$ref": "#/components/schemas/Error"
}POST/keys
Generate keys
Charges your balance and makes up to 500 keys of one plan. The response holds each key and its claim link.
This endpoint is idempotent on idempotencyKey. Use the same value for each retry of one order. The server then charges you one time. A repeat request returns the original keys and alreadyMinted: true.
senderLabel and message control what the customer sees on the claim page. Both are optional. You can change both later in the dashboard.
Authentication: Authorization: Bearer <API key>
Request body required
application/json
| Field | Type | Description |
|---|---|---|
priceIdrequired | string | Take this value from |
quantityrequired | integer | Minimum: 1. Maximum: 500. |
idempotencyKeyrequired | string | Your own id for this order, 8 to 64 characters. Use the same value for each retry. |
senderLabel | string | The sender name the claim page shows. The default is your account name. Maximum length: 40 characters. |
message | string | A note that the customer reads before redemption. Maximum length: 280 characters. |
note | string | Private to you. The customer never sees it. |
View JSON schema
{
"type": "object",
"required": [
"priceId",
"quantity",
"idempotencyKey"
],
"properties": {
"priceId": {
"type": "string",
"description": "Take this value from `GET /products`."
},
"quantity": {
"type": "integer",
"minimum": 1,
"maximum": 500
},
"idempotencyKey": {
"type": "string",
"description": "Your own id for this order, 8 to 64 characters. Use the same value for each retry."
},
"senderLabel": {
"type": "string",
"maxLength": 40,
"description": "The sender name the claim page shows. The default is your account name."
},
"message": {
"type": "string",
"maxLength": 280,
"description": "A note that the customer reads before redemption."
},
"note": {
"type": "string",
"description": "Private to you. The customer never sees it."
}
}
}Responses
201 The keys the server made
application/json
| Field | Type | Description |
|---|---|---|
orderId | string | |
batchId | string | |
quantity | integer | |
unitCostCents | integer | The cost of one key, in cents. |
totalCents | integer | The cost of the batch, in cents. |
balanceAfterCents | integer | null | Your balance after the charge, in cents. |
alreadyMinted | boolean | true means that this idempotencyKey was already used. The server did not charge you again. |
keys | MintedKey[] |
View JSON schema
{
"type": "object",
"properties": {
"orderId": {
"type": "string"
},
"batchId": {
"type": "string"
},
"quantity": {
"type": "integer"
},
"unitCostCents": {
"type": "integer",
"description": "The cost of one key, in cents."
},
"totalCents": {
"type": "integer",
"description": "The cost of the batch, in cents."
},
"balanceAfterCents": {
"type": [
"integer",
"null"
],
"description": "Your balance after the charge, in cents."
},
"alreadyMinted": {
"type": "boolean",
"description": "true means that this idempotencyKey was already used. The server did not charge you again."
},
"keys": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MintedKey"
}
}
}
}400 The server refused the request.
application/json
Schema: Error
View JSON schema
{
"$ref": "#/components/schemas/Error"
}401 The server refused the request.
application/json
Schema: Error
View JSON schema
{
"$ref": "#/components/schemas/Error"
}403 The server refused the request.
application/json
Schema: Error
View JSON schema
{
"$ref": "#/components/schemas/Error"
}429 The server refused the request.
application/json
Schema: Error
View JSON schema
{
"$ref": "#/components/schemas/Error"
}GET/keys/{key}
Read one key
Returns a key from your own inventory. Any other key returns 404, even if that key exists.
Authentication: Authorization: Bearer <API key>
Parameters
| Name | Location / type | Description |
|---|---|---|
keyrequired | pathstring | The key, with or without its dashes. |
Responses
401 The server refused the request.
application/json
Schema: Error
View JSON schema
{
"$ref": "#/components/schemas/Error"
}403 The server refused the request.
application/json
Schema: Error
View JSON schema
{
"$ref": "#/components/schemas/Error"
}404 The server refused the request.
application/json
Schema: Error
View JSON schema
{
"$ref": "#/components/schemas/Error"
}GET/orders
Read your purchase history
Returns your orders, newest first. The batchId field connects an order to the keys that it made. Use batchId to reconcile a charge against GET /keys.
Authentication: Authorization: Bearer <API key>
Parameters
| Name | Location / type | Description |
|---|---|---|
limit | queryinteger | How many rows one page holds, from 1 to 200. |
cursor | querystring | The `nextCursor` value from the previous page. |
Responses
200 One page of orders
application/json
| Field | Type | Description |
|---|---|---|
data | Order[] | |
hasMore | boolean | |
nextCursor | string | null |
View JSON schema
{
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Order"
}
},
"hasMore": {
"type": "boolean"
},
"nextCursor": {
"type": [
"string",
"null"
]
}
}
}400 The server refused the request.
application/json
Schema: Error
View JSON schema
{
"$ref": "#/components/schemas/Error"
}401 The server refused the request.
application/json
Schema: Error
View JSON schema
{
"$ref": "#/components/schemas/Error"
}403 The server refused the request.
application/json
Schema: Error
View JSON schema
{
"$ref": "#/components/schemas/Error"
}GET/openapi.json
Read this document
This is the one endpoint that does not need an API key.
Authentication is not required.
Responses
200 This OpenAPI document
Schemas
Error
| Field | Type | Description |
|---|---|---|
error | object | |
error.code | string | |
error.message | string |
View JSON schema
{
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
}
}
}
}Key
| Field | Type | Description |
|---|---|---|
key | string | Example: |
claimUrl | string | null | Give this link to the customer in place of the raw key. Example: |
status | string | One of: |
productId | string | null | |
packageName | string | null | |
priceId | string | null | |
planName | string | null | |
platform | string | null | null means that the key works on each platform of the package. |
durationDays | integer | How many days the license runs. 0 means that it never expires. |
deviceLimit | integer | How many devices the key permits. 0 means no limit. |
devicesUsed | integer | |
costCents | integer | The amount you paid, in cents. |
retailCents | integer | The customer price when the key was made, in cents. |
currency | string | USD for new financial operations; historical records retain their original currency. Example: |
batchId | string | |
createdAt | integer | Unix time in milliseconds. |
activatedAt | integer | null | |
expiresAt | integer | null | |
note | string | null | |
senderLabel | string | null | |
message | string | null |
View JSON schema
{
"type": "object",
"properties": {
"key": {
"type": "string",
"example": "LYNX-A4KD-92JM-P3QR-7WTX"
},
"claimUrl": {
"type": [
"string",
"null"
],
"description": "Give this link to the customer in place of the raw key.",
"example": "https://m.lynxcheats.com/redeem?code=LYNX-A4KD-92JM-P3QR-7WTX"
},
"status": {
"type": "string",
"enum": [
"dormant",
"active",
"expired",
"revoked"
]
},
"productId": {
"type": [
"string",
"null"
]
},
"packageName": {
"type": [
"string",
"null"
]
},
"priceId": {
"type": [
"string",
"null"
]
},
"planName": {
"type": [
"string",
"null"
]
},
"platform": {
"oneOf": [
{
"type": "string",
"enum": [
"ios",
"android"
]
},
{
"type": "null"
}
],
"description": "null means that the key works on each platform of the package."
},
"durationDays": {
"type": "integer",
"description": "How many days the license runs. 0 means that it never expires."
},
"deviceLimit": {
"type": "integer",
"description": "How many devices the key permits. 0 means no limit."
},
"devicesUsed": {
"type": "integer"
},
"costCents": {
"type": "integer",
"description": "The amount you paid, in cents."
},
"retailCents": {
"type": "integer",
"description": "The customer price when the key was made, in cents."
},
"currency": {
"type": "string",
"example": "usd",
"description": "USD for new financial operations; historical records retain their original currency."
},
"batchId": {
"type": "string"
},
"createdAt": {
"type": "integer",
"description": "Unix time in milliseconds."
},
"activatedAt": {
"type": [
"integer",
"null"
]
},
"expiresAt": {
"type": [
"integer",
"null"
]
},
"note": {
"type": [
"string",
"null"
]
},
"senderLabel": {
"type": [
"string",
"null"
]
},
"message": {
"type": [
"string",
"null"
]
}
}
}MintedKey
| Field | Type | Description |
|---|---|---|
key | string | |
claimUrl | string | null |
View JSON schema
{
"type": "object",
"properties": {
"key": {
"type": "string"
},
"claimUrl": {
"type": [
"string",
"null"
]
}
}
}Account
| Field | Type | Description |
|---|---|---|
id | string | |
uid | integer | |
username | string | null | |
role | string | |
keyPrefix | string | null | The four letters at the start of the keys you make. It is null if you do not have one. |
group | object | null | |
group.name | string | |
group.discountBps | integer | Your discount in basis points. 4000 means 40 percent below the customer price. |
group.perPlanPricing | boolean | true means that your group sets its own price for some plans. The group discount is then not the full picture. Read /products for the real numbers. |
View JSON schema
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"uid": {
"type": "integer"
},
"username": {
"type": [
"string",
"null"
]
},
"role": {
"type": "string"
},
"keyPrefix": {
"type": [
"string",
"null"
],
"description": "The four letters at the start of the keys you make. It is null if you do not have one."
},
"group": {
"type": [
"object",
"null"
],
"properties": {
"name": {
"type": "string"
},
"discountBps": {
"type": "integer",
"description": "Your discount in basis points. 4000 means 40 percent below the customer price."
},
"perPlanPricing": {
"type": "boolean",
"description": "true means that your group sets its own price for some plans. The group discount is then not the full picture. Read /products for the real numbers."
}
}
}
}
}Balance
| Field | Type | Description |
|---|---|---|
balanceCents | integer | Your cash balance, in cents. |
creditLimitCents | integer | How far below zero you can go, in cents. |
availableCents | integer | The sum of balanceCents and creditLimitCents. This is what you can spend now. |
currency | string | USD for new financial operations; historical records retain their original currency. Example: |
View JSON schema
{
"type": "object",
"properties": {
"balanceCents": {
"type": "integer",
"description": "Your cash balance, in cents."
},
"creditLimitCents": {
"type": "integer",
"description": "How far below zero you can go, in cents."
},
"availableCents": {
"type": "integer",
"description": "The sum of balanceCents and creditLimitCents. This is what you can spend now."
},
"currency": {
"type": "string",
"example": "usd",
"description": "USD for new financial operations; historical records retain their original currency."
}
}
}Package
| Field | Type | Description |
|---|---|---|
productId | string | |
name | string | |
slug | string | |
platforms | string[] | |
plans | object[] | |
plans[].priceId | string | |
plans[].name | string | |
plans[].platform | string | One of: |
plans[].durationDays | integer | |
plans[].currency | string | |
plans[].customerPriceCents | integer | The price a customer pays on the storefront, in cents. |
plans[].yourPriceCents | integer | Your cost for this plan, in cents. Sell above it to make a margin. |
plans[].discountBps | integer | Your discount below the customer price, in basis points. |
View JSON schema
{
"type": "object",
"properties": {
"productId": {
"type": "string"
},
"name": {
"type": "string"
},
"slug": {
"type": "string"
},
"platforms": {
"type": "array",
"items": {
"type": "string",
"enum": [
"ios",
"android"
]
}
},
"plans": {
"type": "array",
"items": {
"type": "object",
"properties": {
"priceId": {
"type": "string"
},
"name": {
"type": "string"
},
"platform": {
"type": "string",
"enum": [
"ios",
"android"
]
},
"durationDays": {
"type": "integer"
},
"currency": {
"type": "string"
},
"customerPriceCents": {
"type": "integer",
"description": "The price a customer pays on the storefront, in cents."
},
"yourPriceCents": {
"type": "integer",
"description": "Your cost for this plan, in cents. Sell above it to make a margin."
},
"discountBps": {
"type": "integer",
"description": "Your discount below the customer price, in basis points."
}
}
}
}
}
}Order
| Field | Type | Description |
|---|---|---|
id | string | |
kind | string | |
status | string | |
provider | string | |
amountCents | integer | null | |
currency | string | null | |
quantity | integer | null | |
batchId | string | null | |
productId | string | null | |
priceId | string | null | |
createdAt | integer |
View JSON schema
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"kind": {
"type": "string"
},
"status": {
"type": "string"
},
"provider": {
"type": "string"
},
"amountCents": {
"type": [
"integer",
"null"
]
},
"currency": {
"type": [
"string",
"null"
]
},
"quantity": {
"type": [
"integer",
"null"
]
},
"batchId": {
"type": [
"string",
"null"
]
},
"productId": {
"type": [
"string",
"null"
]
},
"priceId": {
"type": [
"string",
"null"
]
},
"createdAt": {
"type": "integer"
}
}
}