---
title: "Errors and safe user messages"
description: "Handle every current licensing error, distinguish SDK-local and operational codes, and give users a safe next step."
slug: "/docs/v1/api/errors"
section: "API reference"
order: 37
audience: "all"
level: "intermediate"
last_updated: "2026-10-17"
version: "v1"
related: ["/docs/v1/start/authentication", "/docs/v1/api/conventions"]
draft: false
---
Use error.code for decisions and a clear, non-technical message for users; never infer a [license](/docs/v1/start/glossary#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](/docs/v1/start/glossary#feature).
- Use [request IDs](/docs/v1/start/glossary#request-id) with support.

## Prerequisites
- Read [API conventions](/docs/v1/api/conventions).
- Complete [the Sandbox quickstart](/docs/v1/start/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:
```json
{"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](/docs/v1/start/glossary#developer) adjusts policy; reset-devices does not zero count | activate | This license has reached its [device](/docs/v1/start/glossary#device) or [activation](/docs/v1/start/glossary#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](/docs/v1/start/glossary#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](/docs/v1/start/glossary#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](/docs/v1/start/glossary#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](/docs/v1/start/glossary#product)/environment/[customer](/docs/v1/start/glossary#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](/docs/v1/start/glossary#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](/docs/v1/start/glossary#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](/docs/v1/start/glossary#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](/docs/v1/start/glossary#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](/docs/downloads/api-examples.zip); its48 messages/actions match the full table above. Keep that dependency beside the executable sample instead of inventing a second mapping.
:::tabs
:::tab python Python
```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)
```
:::
:::tab javascript JavaScript
```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;}
```
:::
:::tab php PHP
```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);}
```
:::
:::tab csharp C#
```csharp
// 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;
}
```
```xml
<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](/docs/v1/start/glossary#official-sdk) 1.0.0 as in the [quickstart](/docs/v1/start/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](/docs/v1/api/public-key) and [signature verification](/docs/v1/security/verify-ed25519).

## Related
- [Authentication](/docs/v1/start/authentication)
- [API conventions](/docs/v1/api/conventions)
- [License lifecycle](/docs/v1/start/license-lifecycle)
