Reading this in another language? Use your browser's built-in translate option. Button and field names stay in English on purpose, to match what you see in Keysender.
The Keysender API lets your own software do what the panel does: create inventory databases, upload codes, record sales that deliver a code to a buyer, manage customers, and buy from the Supply catalog. You authenticate once with your API key and secret, receive a bearer token, and send that token with every request afterwards.
TL;DR
POST /login, then send it as Authorization: Bearer ... on every call.page/pagesize, filter with a JSON filter param, and stay under 600 requests a minute.Make sure you have:
Keep the secret private. Anyone who has both values can read and change everything in your account.
https://panel.keysender.com/api/v1.0, followed by the endpoint path. The full reference, generated from the same definition the API runs on, is at panel.keysender.com/api. Content-Type: application/json, and read JSON back. POST /login with your two values in the body: curl -X POST "https://panel.keysender.com/api/v1.0/login" \
-H "Content-Type: application/json" \
-d '{"api_key": "YOUR_API_KEY", "api_secret": "YOUR_API_SECRET"}'
access_token from the response. It also returns token_type (Bearer) and expires_at. {"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOi...", "token_type": "Bearer", "expires_at": "2027-08-26 10:15:00"}
Authorization: Bearer YOUR_ACCESS_TOKEN. Logging in again issues another token; the earlier one stays valid until it expires or you call GET /logout with it. curl "https://panel.keysender.com/api/v1.0/databases" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
page and pagesize. /databases, /codes, /customers) accept page (starting at 0) and pagesize (default 100, maximum 1000). filter query parameter, for example filter=[{"name":"Steam keys"}] on /databases. Each endpoint's guide lists the fields it can filter on. 401 with {"message": "Unauthorized"} means the token is missing, expired, or logged out. Log in again. 422 means the request was understood but rejected. The body is {"ack": "Failure", "message": "...", "code": 1001}; the message says which field is wrong. {"status": "...", "error_code": "...", "message": "..."} instead; see Buying from the Supply catalog with the API. GET /databases returns HTTP 200 and a JSON array (empty if you have no databases yet) with page and pagesize at the end. If you see the same databases you see under Inventory in the panel, your token works. For the full path from a fresh key to a delivered code, see Connecting your own app to Keysender.
| Symptom | Cause | Action |
|---|---|---|
401 or a generic server error on /login itself |
The key or secret has a typo or a stray space | Copy both again from the API Information card |
401 on every other call |
The Authorization header is missing the word Bearer and a space before the token |
Send Authorization: Bearer YOUR_ACCESS_TOKEN exactly |
| Every call logs in first | The token is not being reused | Keep one token per integration and reuse it until it expires |
Submit a ticket with the endpoint, the exact response body, and the time of the call. Never include your API secret in a ticket.
In the panel under Account > Settings, in the API Information card. Both values are issued when you sign up, so there is nothing to request. Copy them exactly; a stray space is the most common reason a login is rejected.
No. You send the key and secret once to POST /login, receive an access token, and send that token as an Authorization: Bearer header on every other call. Reuse the token until it expires or you log it out.
The request reached the API but was rejected. The body carries ack, message, and code; the message names the field that is missing or wrong. A 401 means the token is missing, expired, or logged out.