Developer API
API keys for your own bot, store or server: make, look up, reset, ban and delete keys.
The Developer API lets your own tools manage your licence keys: a Discord or Telegram bot of yours, a store, your website or a script. Make keys, look a key up (status, time left, which PCs it is bound to), free it for a new PC, ban it, add time or delete it, without ever giving anyone your panel password. Make API keys on the Developer API page.
API keys
- An API key works for one application. Up to 10 per app; give each tool its own.
- Tick what it may do: See keys, Make keys, Add or remove time, Reset PCs, Ban and pause, Edit keys, Delete keys. Everything else is refused with 403 NOT_ALLOWED.
- The key (pwf_…) is shown once. PWF Auth keeps only a fingerprint of it, so it cannot be shown again; make a new one if it is lost.
- Optionally, allow calls only from your server's IP addresses (single addresses or ranges like 198.51.100.0/24).
- 120 calls a minute per API key. Above that the answer is 429 with a Retry-After header.
Everything an API key changes goes through the same code as the panel: it shows in the key's Logs as api:<name>, reaches your Webhooks with "by": "api", and keys it makes use your app's key format.
Calls
The address is https://pwfauth.com/api/v1. Send the key in Authorization: Bearer pwf_… (or X-API-Key). Bodies are JSON (a form body works too). Every answer is JSON with "success"; a refusal also has "error" (a code) and "message" (a sentence you can show).
| Call | Does | Needs |
|---|---|---|
| GET /me | The app, the key's name and what it may do. Handy to test a key. | any key |
| GET /keys | The app's keys, newest first. ?status= unused, active, expired, paused, banned or redeemed; ?q= searches key, note and customer; ?limit= 1–100 (50), ?offset=. | See keys |
| GET /keys/{key} | One key, with its PCs (hwids). | See keys |
| POST /keys | {"count": 1–50, "type": "days|weeks|months|lifetime", "duration": 30, "max_devices": 1, "note": ""}. Answers 201 with "keys". Keys start counting the first time they are used. | Make keys |
| POST /keys/{key}/reset-hwid | Frees the key from every PC; the next sign-in binds a new one. Running copies close. | Reset PCs |
| POST /keys/{key}/add-time | {"days": 7}; negative removes time (or /remove-time). Same rules as Add time in Licences. | Add or remove time |
| POST /keys/{key}/ban | {"reason": "", "refund": false}: signs it out at once; refund gives a reseller who sold it their credits back. Also /unban, /pause (keeps the time left) and /resume. | Ban and pause |
| PATCH /keys/{key} | {"note", "customer_name", "customer_contact", "max_devices"} (or POST /keys/{key}/update). | Edit keys |
| DELETE /keys/{key} | Deletes it (or POST /keys/{key}/delete). A reseller who sold it gets the credits back. | Delete keys |
What a key looks like
{
"success": true,
"key": {
"key": "ABCDE-12345-FGHIJ-67890",
"status": "active", // unused | active | expired | paused | banned | redeemed
"type": "days", "duration": 30, "lifetime": false,
"expires_at": "2026-11-11T10:00:00Z", "days_left": 30,
"max_devices": 1, "devices": 1, "hwid_locked": true,
"hwids": [{"hwid": "2f5a1c8e-…", "label": "", "first_seen": "…", "last_seen": "…"}],
"online": true, "sessions": 1, "last_login": "…", "total_logins": 12,
"note": "", "customer_name": "", "customer_contact": "",
"source": "panel", // panel | api | reseller | store | discord | telegram | trial
"created_at": "…", "activated_at": "…", "paused_days_left": null, "flagged": false
}
}Errors
| Status | error | Means |
|---|---|---|
| 401 | NO_API_KEY · BAD_API_KEY | No key sent, or it is wrong, deleted, or its account no longer has the app. |
| 403 | NOT_ALLOWED · IP_NOT_ALLOWED | The key may not do this, or not from this address. |
| 404 | KEY_NOT_FOUND | No such licence key in this app. |
| 400 · 409 | REFUSED, BAD_…, ALREADY_BANNED, NOT_BANNED | The request can't be done; the message says why (for example "Only an active key can be paused."). |
| 429 | RATE_LIMITED | Over 120 calls a minute: wait the seconds in Retry-After. |
Examples
import os, requests
API = "https://pwfauth.com/api/v1"
H = {"Authorization": "Bearer " + os.environ["PWF_API_KEY"]}
key = requests.get(f"{API}/keys/ABCDE-12345", headers=H, timeout=15).json()
if key["success"]:
k = key["key"]
print(k["status"], k["days_left"], "PCs:", k["devices"], "/", k["max_devices"])
made = requests.post(f"{API}/keys", headers=H, json={"count": 1, "type": "days", "duration": 30}, timeout=15).json()
print(made["keys"])// discord.js v14: /keyinfo key:<text> answers only the person who asked
const API = 'https://pwfauth.com/api/v1';
const H = { Authorization: 'Bearer ' + process.env.PWF_API_KEY };
client.on('interactionCreate', async (i) => {
if (!i.isChatInputCommand() || i.commandName !== 'keyinfo') return;
const key = encodeURIComponent(i.options.getString('key'));
const r = await (await fetch(`${API}/keys/${key}`, { headers: H })).json();
if (!r.success) return i.reply({ content: r.message, ephemeral: true });
const k = r.key;
await i.reply({ ephemeral: true, content:
`**${k.status}** · ${k.lifetime ? 'lifetime' : (k.days_left ?? k.duration) + ' days'} · ` +
`PCs ${k.devices}/${k.max_devices}${k.hwid_locked ? ' (locked)' : ''}` });
});PWF Auth's own Discord bot already does this inside your server without code; the API is for when you want your own bot or system.
Something missing or unclear? Tell us on Telegram.