chatboxai/chatbox · error · Error
License validation response was not understood
Error message
License validation response was not understood
What it means
Thrown by validateNativeLicense (src/shared/services/native-license.ts:86) when POST /api/license/validate returns HTTP 2xx but the body cannot be confirmed as { data: { valid: boolean } }. postLicense already parses the body in a try/catch and yields null on non-JSON, so a captive-portal page, proxy-injected HTML, or empty body makes payload?.data?.valid undefined. The guard is deliberate and load-bearing: the caller premiumActions.useAutoValidate clears the user's license on valid === false, so an indeterminate 200 must throw and be treated like a network error (license kept), mirroring the old remote.validateLicense which threw on res.json().
Source
Thrown at src/shared/services/native-license.ts:86
error: data?.error,
}
}
export async function validateNativeLicense(
licenseKey: string,
instanceId: string,
options: NativeLicenseRequestOptions = {}
): Promise<boolean> {
const payload = (await postLicense('/api/license/validate', { licenseKey, instanceId }, options)) as {
data?: { valid?: boolean }
} | null
// Only an explicit boolean is authoritative. A garbage 200 response (captive
// portal / proxy HTML / empty body) must NOT be read as "invalid": the caller
// (premiumActions.useAutoValidate) clears the user's license on `valid === false`,
// so an indeterminate response has to throw and be treated like a network error
// (license kept), matching the old remote.validateLicense which threw on res.json().
if (typeof payload?.data?.valid !== 'boolean') {
throw new Error('License validation response was not understood')
}
return payload.data.valid
}
export async function deactivateNativeLicense(
licenseKey: string,
instanceId: string,
options: NativeLicenseRequestOptions = {}
): Promise<void> {
await postLicense('/api/license/deactivate', { licenseKey, instanceId }, options)
}
export interface NativeLicenseDetailResult {
detail: ChatboxAILicenseDetail | null
error?: { code?: string; message?: string }
}
type NativeLicenseDetailPayload = { data?: unknown; error?: { code?: string; message?: string } }View on GitHub (pinned to 81571269ad)
Solutions
- Treat the thrown error as transient in the caller: keep the existing license and surface a network-style message. Do NOT map it to valid === false (that would wipe a good license).
- Verify options.apiOrigin resolves to the real Chatbox API (default https://api.chatboxai.app) and is not behind a captive portal or rewriting proxy.
- Reproduce the raw exchange: curl -i -X POST '<origin>/api/license/validate' -H 'Content-Type: application/json' -d '{"licenseKey":"...","instanceId":"..."}' and inspect whether the body is JSON with data.valid as a real boolean.
- If you operate a custom apiOrigin/server, return exactly { "data": { "valid": true } } (or false) and never a 200 with an empty or HTML body.
- Check for a backend/schema version mismatch if the issue appears only after an app or server upgrade.
Example fix
// before (caller treats unknown as invalid -> wipes license)
try {
const ok = await validateNativeLicense(key, instanceId)
if (!ok) clearLicense()
} catch (e) {
throw e
}
// after (indeterminate response is network-equivalent -> keep license)
try {
const ok = await validateNativeLicense(key, instanceId)
if (!ok) clearLicense()
} catch (e) {
// HTTP error OR indeterminate 200: preserve the license, retry later
console.warn('license validation unavailable, keeping current license', e)
} Defensive patterns
Strategy: try-catch
Type guard
// Narrow a parsed validate payload before trusting it
function isLicenseValidatePayload(
v: unknown
): v is { data: { valid: boolean } } {
return (
!!v &&
typeof v === 'object' &&
'data' in v &&
!!((v as any).data) &&
typeof (v as any).data.valid === 'boolean'
)
} Try / catch
// Indeterminate 200 == network error: keep the license, do not clear
try {
const ok = await validateNativeLicense(licenseKey, instanceId, { signal })
if (!ok) clearLicense()
} catch (e) {
// thrown by either transport failure or an unparseable 200 body
keepLicenseAndRetryLater()
} Prevention
- Never translate this thrown error into valid === false; the guard exists precisely to protect the license.
- Inject the renderer's retrying afetch as fetchFn so transient transport errors retry before reaching this code.
- Do not override apiOrigin to an untrusted host that may return captive-portal HTML.
- Log the raw status/body when this fires once to confirm whether it is a schema change versus a network interception.
When it happens
Trigger: POST {apiOrigin}/api/license/validate resolves with response.ok === true, but either (a) response.json() throws inside postLicense so payload is null, or (b) payload.data is missing, or (c) payload.data.valid is present but not a strict boolean (e.g. the server sent a string "true" or null). All three fail the typeof === 'boolean' check at line 85.
Common situations: Captive WiFi portal intercepting the request and returning HTML with status 200; a corporate proxy rewriting the response body; a CDN/WAF serving a soft error page with 200; the Chatbox backend deploying a schema change that drops or renames data.valid; an empty body from a degraded gateway; a custom apiOrigin pointing at a mock/stub that returns a different envelope.
Related errors
- Unmatching product
- A license key is required to claim the Agent Mode reward
- License key is required for image generation
- No readable text content found in EPUB file
- Embedding batch failed: expected ${batchTexts.length}, got $
AI-assisted analysis of chatboxai/chatbox@81571269ad (2026-08-12).
Data as JSON: /api/errors/8d5421223d3b5d78.
Report an issue: GitHub.