Use error.code for decisions and a clear, non-technical message for users; never infer a license from an HTTP status alone.
What you will learn
- Recognize all current API/router codes and the separate SDK/web namespaces.
- Handle refusal without leaking credentials or enabling features.
- Use request IDs with support.
Prerequisites
- Read API conventions.
- Complete the Sandbox quickstart.
Error envelope
Licensing API failures use the actual non-2xx HTTP status and this shape. This is a real invalid-token capture with a synthetic request ID:
{"success":false,"error":{"code":"INVALID_TOKEN","message":"The activation token is invalid."},"request_id":"req_000000000000000000000000"}
SDK result objects instead expose valid/Valid, error_code/errorCode/ErrorCode, message and request_id/requestId/RequestId. A local result has no HTTP response. Operational/billing endpoints can use a string error field without this envelope. Do not apply their status codes or Retry-After headers to licensing requests.
A known code can occur in more than one namespace: INVALID_LICENSE and LICENSE_EXPIRED can also be SDK-local, while FORBIDDEN/RATE_LIMITED/NOT_FOUND are reused by unrelated web routes. Preserve the operation and source as well as the code.
Licensing API and router
| Code | HTTP or local | Meaning / common causes | Fix and retry rule | Where it is returned | Suggested end-user message |
|---|---|---|---|---|---|
| ACTIVATION_LIMIT_REACHED | 409 | New/inactive installation while activation_count >= max_activations | Developer adjusts policy; reset-devices does not zero count | activate | This license has reached its device or activation limit. Contact the software developer for help. |
| APP_VERSION_NOT_SUPPORTED | 403 | Nonempty activation app_version outside configured range | Use supported version | activate | This version of the application is not covered by your license. Install a supported version. |
| DEVICE_LIMIT_REACHED | 409 | New/inactive installation while active seats >= license.max_devices | Deactivate/reset a seat | activate | This license has reached its device or activation limit. Contact the software developer for help. |
| DEVICE_MISMATCH | 403 | Provided installation ID does not match token-bound HMAC hash | Use original installation; do not copy token | validate | Please activate this installation again with your license details. |
| DEVICE_NOT_AUTHORIZED | 403 | Token is active but associated device is not ACTIVE | Reactivation if eligible | validate | Please activate this installation again with your license details. |
| FORBIDDEN | 403 | Developer scope missing; checkout account role not OWNER/ADMIN | Use authorized scope/role | create, get, revoke, suspend, restore, renew, reset-devices | Your account connection needs attention. Please contact the software developer. |
| IDEMPOTENCY_CONFLICT | 409 | Organization key reused with different decoded-body hash (member order matters) | Use same body; new key only for new operation | create | This request differs from an earlier request. Please contact support before trying again. |
| INTERNAL_ERROR | 500 | Uncaught exception at global boundary | Safe retry only for idempotent operation; preserve request_id | public-key, create, activate, validate, deactivate, get, revoke, suspend, restore, renew, reset-devices | We could not complete this request. Please try again later or contact support if it continues. |
| INVALID_API_KEY | 401 | Missing/invalid/revoked key or prohibited subscription/environment | Replace/configure key | create, get, revoke, suspend, restore, renew, reset-devices | Your account connection needs attention. Please contact the software developer. |
| INVALID_LICENSE | 404 | Activation key/product/environment/customer email mismatch; SDK empty key local error has no HTTP | Correct details | activate | We could not activate these license details. Check your key and email, then try again. |
| INVALID_REQUEST | 422 | Input constraints or entitlement/resource/renewal domain rejection | Correct input/plan; not blind retry | create, activate, validate, renew, reset-devices | Some information or account settings need attention. Check your details or contact support. |
| INVALID_STATE | 409 | Forbidden state transition or restore with elapsed expiry | Resolve state/renew first | revoke, suspend, restore | This change is not available for the current license. Please contact the software developer. |
| INVALID_TOKEN | 401 | Missing/non-act_/unknown/revoked/deactivated activation token | Reactivate eligible installation | validate, deactivate | Please activate this installation again with your license details. |
| LICENSE_CANCELLED | 403 | License CANCELLED and no elapsed deadline taking precedence | Do not retry; new eligible license | activate, validate | This license is no longer available. Please contact the software developer. |
| LICENSE_EXPIRED | 403 | Elapsed expires_at before state check; or EXPIRED state; SDK local expiry has no HTTP | Renew eligible license | activate, validate | Your license has expired. Contact the software developer to renew it. |
| LICENSE_NOT_FOUND | 404 | Management scoped license absent | Correct org/product/environment/ID | get, revoke, suspend, restore, renew, reset-devices | We could not find the requested license or plan. Please contact the software developer. |
| LICENSE_PENDING | 403 | License PENDING and no elapsed deadline taking precedence | Wait for authorized activation | activate, validate | This license is not ready to use. Please contact the software developer. |
| LICENSE_REVOKED | 403 | License REVOKED and no elapsed deadline taking precedence | Do not retry; contact developer | activate, validate | This license is no longer available. Please contact the software developer. |
| LICENSE_SUSPENDED | 403 | License SUSPENDED and no elapsed deadline taking precedence | Restore then reactivate | activate, validate | This license is not ready to use. Please contact the software developer. |
| NOT_FOUND | 404 | Unregistered API route/wrong method; or protected operational guard mismatch | Correct path/method/token; no blind retry | Global API router (unknown path or wrong method); separate guarded operational routes | This request is not available. Please contact support. |
| PLAN_NOT_FOUND | 404 | Create product/plan does not resolve within active API-key product | Correct scoped IDs | create | We could not find the requested license or plan. Please contact the software developer. |
| RATE_LIMITED | 429 | Bucket exhausted; wait for window reset; no licensing Retry-After header | After window; apply jitter | public-key, create, get, revoke, suspend, restore, renew, reset-devices, activate, validate | Too many requests were sent. Please wait a minute before trying again. |
SDK-local and transport results
| Code | HTTP or local | Meaning / common causes | Fix and retry rule | Where it is returned | Suggested end-user message |
|---|---|---|---|---|---|
| CLOCK_ROLLBACK_DETECTED | local | Clock is >300 seconds behind saved/server trusted checkpoint | Correct clock then validate | No API-v1 emitter; SDK/web/operational namespace | Your device clock needs attention. Set the correct date and time, then connect to check your license. |
| HTTP_ERROR | local / inherited HTTP | Non-success remote response without structured licensing error; fallback SDK code | Inspect actual status; no automatic blind retry | No API-v1 emitter; SDK/web/operational namespace | We could not complete this request. Please try again later or contact support if it continues. |
| INVALID_RESPONSE | local | Malformed JSON or missing token/envelope in SDK response | Reconnect/contact support | No API-v1 emitter; SDK/web/operational namespace | We could not safely confirm your license. Connect again or contact the software developer. |
| LICENSE_NOT_ACTIVE | local | SDK verified payload status is not active | Check account/license state | No API-v1 emitter; SDK/web/operational namespace | This license is not ready to use. Please contact the software developer. |
| NETWORK_ERROR | local | SDK activation/deactivation transport failure; check may instead use offline cache | Retry connectivity, do not bypass verification | No API-v1 emitter; SDK/web/operational namespace | Connect to the internet to check your license, then try again. |
| NOT_ACTIVATED | local | SDK has no stored token for check/deactivate | Activate | No API-v1 emitter; SDK/web/operational namespace | Please activate this installation again with your license details. |
| OFFLINE_CACHE_INVALID | local | Cached envelope cannot verify; SDK removes it | Online validation; investigate tampering or known canonical defect | No API-v1 emitter; SDK/web/operational namespace | We could not safely confirm your license. Connect again or contact the software developer. |
| OFFLINE_CACHE_MISSING | local | Transport fallback without stored signed cache | Online validation | No API-v1 emitter; SDK/web/operational namespace | Connect to the internet to check your license, then try again. |
| OFFLINE_GRACE_EXPIRED | local | Cache/offline grace missing or now beyond signed deadline | Online validation | No API-v1 emitter; SDK/web/operational namespace | Connect to the internet to check your license, then try again. |
| SIGNATURE_INVALID | local | SDK signature/envelope/key/product/environment/installation verification exception | Fail closed; investigate key/canonicalization | No API-v1 emitter; SDK/web/operational namespace | We could not safely confirm your license. Connect again or contact the software developer. |
Other web and operational namespaces
| Code | HTTP or local | Meaning / common causes | Fix and retry rule | Where it is returned | Suggested end-user message |
|---|---|---|---|---|---|
| BACKUP_FAILED | 500 | Backup exception | Operator investigates | No API-v1 emitter; SDK/web/operational namespace | This service action could not be completed. Please contact support at hi@kdpika.com. |
| CHECKOUT_UNAVAILABLE | 409 | Checkout creation DomainException | Resolve account/order then retry | No API-v1 emitter; SDK/web/operational namespace | This service action could not be completed. Please contact support at hi@kdpika.com. |
| CRON_FAILED | 500 | Cron exception | Operator investigates | No API-v1 emitter; SDK/web/operational namespace | This service action could not be completed. Please contact support at hi@kdpika.com. |
| INSTALLER_LOCKED | 410 | Installer lock exists | No public retry; installation already locked | No API-v1 emitter; SDK/web/operational namespace | This service action could not be completed. Please contact support at hi@kdpika.com. |
| INSTALL_FAILED | 500 | Installer exception | Operator investigates; not public API | No API-v1 emitter; SDK/web/operational namespace | This service action could not be completed. Please contact support at hi@kdpika.com. |
| INVALID_CSRF | 419 | JSON request through CSRF middleware has invalid session token; not currently API-v1 middleware | Refresh session token | No API-v1 emitter; SDK/web/operational namespace | This service action could not be completed. Please contact support at hi@kdpika.com. |
| INVALID_EVENT | 400 | Stripe event domain validation failure | Investigate account/event binding | No API-v1 emitter; SDK/web/operational namespace | This service action could not be completed. Please contact support at hi@kdpika.com. |
| INVALID_JSON | 400 | Stripe webhook malformed JSON/provider unexpected value | Correct event; do not retry identical malformed payload | No API-v1 emitter; SDK/web/operational namespace | This service action could not be completed. Please contact support at hi@kdpika.com. |
| INVALID_SIGNATURE | 400 | Stripe webhook missing secret or invalid signature | Correct configuration; reject untrusted event | No API-v1 emitter; SDK/web/operational namespace | This service action could not be completed. Please contact support at hi@kdpika.com. |
| METHOD_NOT_ALLOWED | 405 | Installer called with non-POST | Operational only; correct method | No API-v1 emitter; SDK/web/operational namespace | This service action could not be completed. Please contact support at hi@kdpika.com. |
| MIGRATION_FAILED | 500 | Migration exception | Operator investigates | No API-v1 emitter; SDK/web/operational namespace | This service action could not be completed. Please contact support at hi@kdpika.com. |
| PROCESSING_FAILED | 500 | Stripe event unexpected processing failure | Provider delivery retry is appropriate | No API-v1 emitter; SDK/web/operational namespace | This service action could not be completed. Please contact support at hi@kdpika.com. |
| PROVISION_FAILED | 500 | Admin provisioning exception | Operator investigates | No API-v1 emitter; SDK/web/operational namespace | This service action could not be completed. Please contact support at hi@kdpika.com. |
| SDK_NOT_FOUND | 404 | Authenticated download requested unsupported language | Correct language | No API-v1 emitter; SDK/web/operational namespace | This service action could not be completed. Please contact support at hi@kdpika.com. |
| SDK_UNAVAILABLE | 503 | SDK directory/archive temporary failure | Retry later | No API-v1 emitter; SDK/web/operational namespace | This service action could not be completed. Please contact support at hi@kdpika.com. |
| STRIPE_UNAVAILABLE | 503 | Checkout creation unexpected exception | Retry after 10s | No API-v1 emitter; SDK/web/operational namespace | This service action could not be completed. Please contact support at hi@kdpika.com. |
Retry decisions
RATE_LIMITED: wait a full 60-second licensing window plus jitter when no previous reset time is available. Authentication/input/state/signature errors require correction, not blind retries. Network/500 errors after mutations are uncertain outcomes: reconcile creation/renewal before another write. HTTP_ERROR is a fallback, not proof that an upstream error is safe to retry. Unknown future codes fail closed.
PRODUCT_NOT_FOUND and DEVICE_NOT_FOUND appeared in older documentation but have no current API-v1 emitter. They are not supported error promises.
Safe handling in every official SDK language
These complete programs call the unchanged SDK check() equivalent. An invalid result never enables a feature. Messages/actions come from one adjacent policy.json, so the four languages use the same text. The full self-test mode uses actual result models with synthetic inputs; it does not claim to reproduce48 runtime emitters. The separate API contract tests reproduce the 22 licensing/router codes.
The exact shared policy.json is in the complete example archive; its48 messages/actions match the full table above. Keep that dependency beside the executable sample instead of inventing a second mapping.
Python
"""Safe SDK error UX, Python3.9+ / official Python SDK1.0.0.
Run: python3 python.py (KDP_APP_ID, KDP_STATE_DIR, optional KDP_BASE_URL origin).
Self-test: python3 python.py --self-test < cases.json; real result model, no network.
Output: valid/message/action/request_id only; never raw payload, key or token.
"""
import json
import os
import re
import sys
from pathlib import Path
from license_sdk import LicenseClient
from license_sdk.models import LicenseResult
POLICY=json.loads(Path(__file__).with_name('policy.json').read_text())
def explain(result):
# Check valid first. A feature helper alone is not an access check.
entry={'message':'Your license is ready.','action':'continue'} if result.valid else POLICY['codes'].get(result.error_code,POLICY['fallback'])
request_id=result.request_id if isinstance(result.request_id,str) and re.fullmatch(r'req_[a-f0-9]{24}',result.request_id) else None
return {'valid':result.valid,'message':entry['message'],'action':entry['action'],'request_id':request_id}
def main():
if sys.argv[1:]==['--self-test']:
rows=json.load(sys.stdin)
for row in rows:
result=LicenseResult(valid=row['valid'],source='synthetic contract',error_code=row.get('code'),request_id=row.get('request_id'))
print(json.dumps(explain(result)))
return
if len(sys.argv)!=1:raise ValueError('Unknown action')
client=LicenseClient(product=os.environ['KDP_APP_ID'],environment='SANDBOX',
base_url=os.getenv('KDP_BASE_URL','https://licensing.kdpika.com'),storage_dir=Path(os.environ['KDP_STATE_DIR']),prefer_keyring=False)
result=client.check(force_online=False)
print(json.dumps(explain(result)))
if __name__=='__main__':
try:main()
except Exception:
print('The license check is unavailable. Check configuration or contact the software developer.',file=sys.stderr);sys.exit(2)
JavaScript
// Safe SDK error UX. Node18.20+, official JavaScript SDK1.0.0, trusted runtime only.
// Run: node javascript.mjs; KDP_APP_ID/KDP_STATE_DIR, optional KDP_BASE_URL site origin.
// --self-test reads synthetic cases from stdin and uses actual SDK result objects.
// Output: safe UX/request ID only; no raw payload, key or token.
import fs from 'node:fs';
import {LicenseClient,LicenseResult} from '@kdpika/license-sdk';
const policy=JSON.parse(fs.readFileSync(new URL('./policy.json',import.meta.url),'utf8'));
export function explain(result){
const entry=result.valid?{message:'Your license is ready.',action:'continue'}:policy.codes[result.errorCode]||policy.fallback;
const requestId=typeof result.requestId==='string'&&/^req_[a-f0-9]{24}$/.test(result.requestId)?result.requestId:null;
return {valid:result.valid,message:entry.message,action:entry.action,request_id:requestId};
}
try {
if(process.argv.slice(2).join(' ')==='--self-test'){
const rows=JSON.parse(fs.readFileSync(0,'utf8'));
for(const row of rows)console.log(JSON.stringify(explain(new LicenseResult({valid:row.valid,source:'synthetic contract',errorCode:row.code,requestId:row.request_id}))));
}else{
if(process.argv.length!==2)throw Error();
const client=new LicenseClient({product:process.env.KDP_APP_ID,environment:'SANDBOX',baseUrl:process.env.KDP_BASE_URL||'https://licensing.kdpika.com',storageDir:process.env.KDP_STATE_DIR});
console.log(JSON.stringify(explain(await client.check(false))));
}
}catch{console.error('The license check is unavailable. Check configuration or contact the software developer.');process.exitCode=2;}
PHP
<?php
// Safe SDK error UX. PHP8.2+, official PHP SDK1.0.0 direct source.
// Run: php php.php; KDP_SDK_DIR/KDP_APP_ID/KDP_STATE_DIR and optional KDP_BASE_URL.
// --self-test reads synthetic cases from stdin; actual result model, no network.
// Output safe message/action/request_id only; never payload/key/token.
declare(strict_types=1);
try {
$sdk=rtrim((string)getenv('KDP_SDK_DIR'),'/');
require $sdk.'/src/LicenseResult.php';require $sdk.'/src/LicenseClient.php';
$policy=json_decode((string)file_get_contents(__DIR__.'/policy.json'),true,64,JSON_THROW_ON_ERROR);
function explain(KDPika\LicenseSdk\LicenseResult $result,array $policy):array {
$entry=$result->valid?['message'=>'Your license is ready.','action'=>'continue']:($policy['codes'][$result->errorCode??'']??$policy['fallback']);
$id=$result->requestId!==null&&preg_match('/^req_[a-f0-9]{24}$/D',$result->requestId)?$result->requestId:null;
return ['valid'=>$result->valid,'message'=>$entry['message'],'action'=>$entry['action'],'request_id'=>$id];
}
if(($argv[1]??'')==='--self-test'&&$argc===2){
foreach(json_decode((string)stream_get_contents(STDIN),true,64,JSON_THROW_ON_ERROR) as $row){
$result=new KDPika\LicenseSdk\LicenseResult(valid:$row['valid'],source:'synthetic contract',errorCode:$row['code']??null,requestId:$row['request_id']??null);
echo json_encode(explain($result,$policy),JSON_THROW_ON_ERROR).PHP_EOL;
}
}else{
if($argc!==1)throw new RuntimeException('Unknown action');
$client=new KDPika\LicenseSdk\LicenseClient(product:(string)getenv('KDP_APP_ID'),environment:'SANDBOX',baseUrl:getenv('KDP_BASE_URL')?:'https://licensing.kdpika.com',storageDir:(string)getenv('KDP_STATE_DIR'));
echo json_encode(explain($client->check(false),$policy),JSON_THROW_ON_ERROR).PHP_EOL;
}
}catch(Throwable){fwrite(STDERR,"The license check is unavailable. Check configuration or contact the software developer.\n");exit(2);}
C#
// Safe SDK error UX. .NET7+, official C# SDK1.0.0 project/package reference.
// Run: dotnet run --project Errors.csproj; KDP_APP_ID/KDP_STATE_DIR, optional site origin KDP_BASE_URL.
// --self-test reads synthetic cases from stdin; actual SDK result model, no network.
// Output safe message/action/request_id only; never raw payload/key/token.
using System.Text.Json;
using System.Text.RegularExpressions;
using KDPika.LicenseSdk;
try
{
using var policy=JsonDocument.Parse(File.ReadAllText(Path.Combine(AppContext.BaseDirectory,"policy.json")));
object Explain(LicenseResult result)
{
var found=result.ErrorCode is not null&&policy.RootElement.GetProperty("codes").TryGetProperty(result.ErrorCode,out _);
var entry=found?policy.RootElement.GetProperty("codes").GetProperty(result.ErrorCode!):policy.RootElement.GetProperty("fallback");
var message=result.Valid?"Your license is ready.":entry.GetProperty("message").GetString();
var action=result.Valid?"continue":entry.GetProperty("action").GetString();
var id=result.RequestId is not null&&Regex.IsMatch(result.RequestId,"^req_[a-f0-9]{24}$")?result.RequestId:null;
return new {valid=result.Valid,message,action,request_id=id};
}
if(args.Length==1&&args[0]=="--self-test")
{
using var rows=JsonDocument.Parse(Console.In.ReadToEnd());
foreach(var row in rows.RootElement.EnumerateArray())
{
var result=new LicenseResult(row.GetProperty("valid").GetBoolean(),"synthetic contract",default,
row.TryGetProperty("code",out var code)?code.GetString():null,
RequestId:row.TryGetProperty("request_id",out var requestId)?requestId.GetString():null);
Console.WriteLine(JsonSerializer.Serialize(Explain(result)));
}
}
else
{
if(args.Length!=0)throw new Exception("Unknown action");
var client=new LicenseClient(product:Environment.GetEnvironmentVariable("KDP_APP_ID")!,environment:"SANDBOX",
baseUrl:Environment.GetEnvironmentVariable("KDP_BASE_URL")??"https://licensing.kdpika.com",
storageDir:Environment.GetEnvironmentVariable("KDP_STATE_DIR"));
Console.WriteLine(JsonSerializer.Serialize(Explain(await client.CheckAsync(false))));
}
return 0;
}
catch(Exception)
{
Console.Error.WriteLine("The license check is unavailable. Check configuration or contact the software developer.");return 2;
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType><TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../../../sdk/csharp/KDPika.LicenseSdk/KDPika.LicenseSdk.csproj" />
<None Include="policy.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>
Install official SDK 1.0.0 as in the quickstart. Python requires 3.9+; PHP requires 8.2+ for the actual readonly result class; Node requires 18.20+ for these examples; C# targets .NET 7+. Keep policy.json beside each sample. For C#, the included project references the SDK in this repository; when using a downloaded SDK, set ProjectReference to that extracted SDK project, and keep policy.json copied to output. Run each file with no arguments to check the configured Sandbox installation. Supply KDP_APP_ID, KDP_STATE_DIR and optional KDP_BASE_URL as the site origin, not /api/v1. PHP additionally uses KDP_SDK_DIR. Node resolves the locally installed @kdpika/license-sdk.
Request IDs and support
Record method, path, UTC time, actual HTTP status and current X-Request-ID. If a create replay body has a different request_id, retain both. SDK-local results can have no request ID; do not manufacture one. Send the IDs and a short description to hi@kdpika.com, not raw keys, tokens, customer payloads or unredacted screenshots.
Show a support reference only if it has the expected req_ correlation format. A reference is not authentication or an access grant. The sample does not render raw server error.message into HTML.
Expected result
Your application denies invalid results, shows the next step, and retains safe correlation details without credentials.
Common mistakes
- Calling a feature helper without checking result.valid/Valid first.
- Treating SDK-local errors as an HTTP 500 or retrying every failure.
- Displaying internal exception text or sending private payloads to support.
Security notes
Fail closed on unknown/signature/cache/clock errors. A friendly message must never turn a refusal into success. The policy is client UX, not new server behavior.
Troubleshooting
| Symptom | Cause | Action |
|---|---|---|
| No request ID | Local setup/cache/transport path | Record the app version/time/code; do not invent a server ID |
| Code not in this table | New server/client version or unrelated route | Fail closed, preserve safe metadata and contact support |
| Error message changed | Human wording is not a stable machine contract | Branch on code and namespace, not English text |
Next steps
Review the endpoint reference and signature verification.