Release active devices and revoke their tokens.
What you will learn
- Send this exact operation with its required credentials and fields.
- Interpret success, refusal, side effects and retry risks.
Prerequisites
- Complete the Sandbox quickstart.
- Read API conventions and authentication.
- Keep customer credentials private and management keys on your trusted backend.
Method and path
POST /api/v1/licenses/{license}/reset-devices
When to use it
Release all active installation seats from your trusted backend after an authorized device-recovery/support request. This is not a reset of lifetime activation policy.
Authentication and environment
Use Authorization: Bearer YOUR_DEVELOPER_API_KEY with scope devices:write. The key fixes the organization/product/environment; X-Environment cannot override it.
Request headers
| Header | Required | Description | Example |
|---|---|---|---|
| Accept | recommended | JSON responses | application/json |
| Content-Type | for JSON body | Use the documented JSON object | application/json |
| Authorization | yes | Scoped backend-only developer key | Bearer YOUR_DEVELOPER_API_KEY |
Path and query parameters
| Parameter | Type | Required | Description | Example |
|---|---|---|---|---|
| license | string | yes | Opaque license identifier. Scoped lookup returns 404; no UUID/length contract. | YOUR_LICENSE_ID |
No pagination, filtering, sorting or list query parameters are implemented. Use JSON rather than relying on alternate form/query parsing.
Request body
No body fields are consumed. Omit the body or send an empty object.
Full request JSON
{}
The schemas describe supported recommended input types. Current PHP parsing trims/casts strings and may ignore unknown fields; it is not strict JSON-schema validation. Invalid optional descriptive fields can become empty/null. Conventions describes malformed JSON and alternate parsing.
Success response
HTTP 200. The following is a real isolated Sandbox capture; secret values are replaced with explicit placeholders.
{
"success": true,
"devices_reset": true,
"request_id": "req_94a800afce76076106be37b8"
}
Response fields
| Field | Type | Required | Constraints | Description | Example |
|---|---|---|---|---|---|
| success | boolean | yes | const=true | The operation succeeded. | true |
| devices_reset | boolean | yes | const=true | Active devices released and tokens revoked; cumulative count unchanged. | true |
| request_id | string | yes | pattern="^req_[a-f0-9]{24}$" | Request correlation ID. Replay bodies can retain an earlier ID. | "req_94a800afce76076106be37b8" |
Response headers
X-Request-ID identifies the current request. Successful limited calls include X-RateLimit-Remaining and X-RateLimit-Reset, the latter in Unix seconds.
Errors omit those rate headers and Retry-After. See the complete rate-limit reference.
Error responses
| Code | HTTP | Cause | Fix | Retryable? |
|---|---|---|---|---|
| LICENSE_NOT_FOUND | 404 | Management scoped license absent | Correct org/product/environment/ID | No blind retry; apply the correction |
| INVALID_REQUEST | 422 | Input constraints or entitlement/resource/renewal domain rejection | Correct input/plan; not blind retry | No blind retry; apply the correction |
| INTERNAL_ERROR | 500 | Uncaught exception at global boundary | Safe retry only for idempotent operation; preserve request_id | Only safe reads after backoff; reconcile mutations |
| INVALID_API_KEY | 401 | Missing/invalid/revoked key or prohibited subscription/environment | Replace/configure key | No blind retry; apply the correction |
| FORBIDDEN | 403 | Developer scope missing; checkout account role not OWNER/ADMIN | Use authorized scope/role | No blind retry; apply the correction |
| RATE_LIMITED | 429 | Bucket exhausted; wait for window reset; no licensing Retry-After header | After window; apply jitter | After the 60-second window, with jitter |
An unregistered path or wrong method is handled by the global router as NOT_FOUND (HTTP 404), not405. Every licensing error has success=false, error.code, error.message and request_id. Errors keeps SDK-local and operational namespaces separate.
Side effects
Marks all ACTIVE devices DEACTIVATED and revokes active tokens for the scoped license. Does not change license status, expiration or activation_count. Re-activation still obeys status, expiry and max_activations. Audit license.devices_reset and webhook device.reset are emitted; no email is sent directly.
Authenticated requests can consume the organization monthly API usage counter once tenant context is established, including some failures. This usage counter is currently warning-only, not a monthly API rejection gate. Developer-key authentication can update last_used_at before a later failure. No request body or Authorization value is saved by the API metadata logger.
Idempotency and concurrency
Idempotency-Key is ignored. Repeating a reset may emit another audit/webhook even if no seats remain. The locked mutation can return INVALID_REQUEST if a concurrent soft deletion occurs after the controller's initial scoped read; an already missing license returns LICENSE_NOT_FOUND instead. No caller revision check is available.
Rate limits
Developer pre-authentication:240 per60 seconds per IP. Reset bucket:60 per60 seconds per developer-key device-write bucket, separate from shared license-write.
Fixed windows begin with the first hit after expiry. Attempted calls consume every bucket they reach, even if later business validation fails.
Runnable examples
These are complete native HTTP transport programs, not full SDK clients or entitlement verifiers. JSON request bodies and credentials come from KDP_* environment variables. They print raw responses to a private terminal; do not send their stdout to application/CI logs. Use the P5 verification guides before granting access.
JavaScript (Node)
Save every file shown below. Install the exact runtime/dependencies from the complete example archive README. Set the endpoint request JSON in KDP_BODY_JSON and the documented KDP_* credential variables in your private process environment.
Run node request.mjs reset-devices. Expected: full JSON, exit 0 for success, exit 1 for an API rejection; exit 2 for setup/transport failure. The cURL sample checks HTTP status; parse JSON before use. Never authorize a feature from transport output alone.
// Purpose: inspect one Sandbox API operation; not an entitlement verifier.
// Requirements: Node18.20+ native fetch, trusted server/Electron main process.
// Run: node request.mjs public-key. Expected: JSON; exit0 success/1 rejection/2 transport or setup.
// Credentials/body use KDP_* environment variables. Output can contain new secrets.
// Use a private terminal, never application/CI logs. Verify signed state separately.
const operations = Object.freeze({
"public-key": {
"method": "GET",
"path": "/public-key",
"auth": "public"
},
"create-license": {
"method": "POST",
"path": "/licenses",
"auth": "developer"
},
"activate": {
"method": "POST",
"path": "/licenses/activate",
"auth": "publicApp"
},
"validate": {
"method": "POST",
"path": "/licenses/validate",
"auth": "activation"
},
"deactivate": {
"method": "POST",
"path": "/licenses/deactivate",
"auth": "activation"
},
"get-license": {
"method": "GET",
"path": "/licenses/{license}",
"auth": "developer"
},
"revoke": {
"method": "POST",
"path": "/licenses/{license}/revoke",
"auth": "developer"
},
"suspend": {
"method": "POST",
"path": "/licenses/{license}/suspend",
"auth": "developer"
},
"restore": {
"method": "POST",
"path": "/licenses/{license}/restore",
"auth": "developer"
},
"renew": {
"method": "POST",
"path": "/licenses/{license}/renew",
"auth": "developer"
},
"reset-devices": {
"method": "POST",
"path": "/licenses/{license}/reset-devices",
"auth": "developer"
}
});
function required(name) {
const value = process.env[name];
if (!value || /[\r\n]/.test(value)) throw new Error(`Set a valid ${name}.`);
return value;
}
async function main() {
const name = process.argv[2];
if (process.argv.length !== 3 || !Object.hasOwn(operations, name)) throw new Error('Choose a documented operation.');
const operation = operations[name];
const base = (process.env.KDP_BASE_URL || 'https://licensing.kdpika.com/api/v1').replace(/\/+$/, '');
const url = new URL(base);
const loopback = url.protocol === 'http:' && ['127.0.0.1', 'localhost'].includes(url.hostname) && process.env.KDP_ALLOW_LOOPBACK === '1';
if ((url.protocol !== 'https:' && !loopback) || url.username || url.password || url.search || url.hash || url.pathname !== '/api/v1') throw new Error('Use an HTTPS API URL ending in /api/v1; loopback HTTP requires explicit test opt-in.');
let path = operation.path;
if (path.includes('{license}')) {
const id = required('KDP_LICENSE_ID');
if (!/^[A-Za-z0-9_-]+$/.test(id)) throw new Error('Use the simple license ID issued by this platform with this sample.');
path = path.replace('{license}', id);
}
const headers = {'Accept': 'application/json', 'User-Agent': 'KDPikaDocsExample/1.0 Node'};
if (operation.auth === 'developer') {
const key = required('KDP_API_KEY');
if (!key.startsWith('lmp_sandbox_')) throw new Error('Use a Sandbox developer key.');
headers.Authorization = `Bearer ${key}`;
} else if (operation.auth === 'activation') headers.Authorization = `Bearer ${required('KDP_ACTIVATION_TOKEN')}`;
else if (operation.auth === 'publicApp') {
headers['X-App-ID'] = required('KDP_APP_ID'); headers['X-Environment'] = 'SANDBOX';
}
if (name === 'create-license') headers['Idempotency-Key'] = required('KDP_IDEMPOTENCY_KEY');
if (name === 'validate') headers['X-Installation-ID'] = required('KDP_INSTALLATION_ID');
let body;
if (operation.method === 'POST') {
body = process.env.KDP_BODY_JSON || '{}';
const value = JSON.parse(body);
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('KDP_BODY_JSON must be a JSON object.');
headers['Content-Type'] = 'application/json';
}
const response = await fetch(base + path, {method: operation.method, headers, body, redirect: 'error', signal: AbortSignal.timeout(15000)});
const raw = await response.text();
const result = JSON.parse(raw);
process.stdout.write(raw + '\n');
if (!response.ok || result.success !== true) {
process.stderr.write('The API rejected this request. Inspect error.code and request_id; reconcile mutations before retrying.\n');
return 1;
}
return 0;
}
main().then(code => {process.exitCode = code;}).catch(() => {
process.stderr.write('Request unavailable or configuration invalid. Check KDP_* settings/connectivity; do not blindly retry mutations.\n');
process.exitCode = 2;
});
Expected result
A correctly authenticated eligible request returns the documented HTTP status and schema. Check the actual success/error result and request IDs; a successful transport or unsigned management response does not grant a feature.
Common mistakes
- Mixing Public App IDs, internal product IDs and product-plan IDs.
- Assuming a mutation timeout means no work committed, or assuming Idempotency-Key protects every action.
- Logging raw responses that contain a newly issued license key or activation token.
Security notes
Keep lmp_ credentials out of shipped desktop/browser code. Verify signed payload bytes and product/environment/installation/status/time/feature policy. The examples do not fix the documented idempotency, cache canonicalization or current-key-only rotation limitations.
Troubleshooting
| Symptom | Cause | Action |
|---|---|---|
| LICENSE_NOT_FOUND (HTTP 404) | Management scoped license absent | Correct org/product/environment/ID |
| INVALID_REQUEST (HTTP 422) | Input constraints or entitlement/resource/renewal domain rejection | Correct input/plan; not blind retry |
| INTERNAL_ERROR (HTTP 500) | Uncaught exception at global boundary | Safe retry only for idempotent operation; preserve request_id |
| INVALID_API_KEY (HTTP 401) | Missing/invalid/revoked key or prohibited subscription/environment | Replace/configure key |
| FORBIDDEN (HTTP 403) | Developer scope missing; checkout account role not OWNER/ADMIN | Use authorized scope/role |
| RATE_LIMITED (HTTP 429) | Bucket exhausted; wait for window reset; no licensing Retry-After header | After window; apply jitter |
Next steps
Read idempotency and signed responses before integrating a purchase-to-license workflow.