fatedier/frp · error · Error
Invalid API v2 response
Error message
Invalid API v2 response
What it means
Thrown by requestV2 in the frps dashboard when the HTTP request succeeded (2xx) but the body cannot be interpreted as a V2Envelope — i.e. it is null, not JSON, or lacks a numeric 'code' field. The v2 client requires every response to be shaped {code, msg, data}; anything else is treated as a protocol violation rather than a server rejection.
Source
Thrown at web/frps/src/api/http.ts:74
const defaultOptions: RequestInit = {
credentials: 'include',
}
const response = await fetch(url, { ...defaultOptions, ...options })
const envelope = (await response.json().catch(() => null)) as
| V2Envelope<T>
| null
if (!response.ok) {
throw new HTTPError(
response.status,
response.statusText,
envelope?.msg || `HTTP ${response.status}`,
)
}
if (!envelope || typeof envelope.code !== 'number') {
throw new Error('Invalid API v2 response')
}
if (envelope.code >= 400) {
throw new HTTPError(envelope.code, envelope.msg, envelope.msg)
}
return envelope.data
}
export const buildQueryString = (
params: Record<string, QueryParamValue>,
): string => {
const query = new URLSearchParams()
for (const [key, value] of Object.entries(params)) {
if (value === null || value === undefined) continue
query.append(key, String(value))
}
const text = query.toString()View on GitHub (pinned to 6c8a8d0a97)
Solutions
- Verify the target frps binary actually implements /api/v2 (check version and release notes); upgrade frps or use v1 endpoints
- curl the exact /api/v2 URL and confirm the body is {"code":...,"msg":...,"data":...}
- Remove intermediate proxies that rewrite the response body, or make them pass it through untouched
- Ensure the URL path and port match the frps API surface exactly
Example fix
// before
const data = await requestV2('/api/v2/serverinfo') // Invalid API v2 response
// after: guard the response shape at the call site
const resp = await fetch('/api/v2/serverinfo', { credentials: 'include' })
const body = await resp.json().catch(() => null)
if (!body || typeof body.code !== 'number') {
throw new Error(`frps has no v2 API at this endpoint (got: ${JSON.stringify(body)?.slice(0, 80)})`)
} Defensive patterns
Strategy: validation
Validate before calling
// Probe v2 API support before using v2 endpoints
async function v2Supported(base: string): Promise<boolean> {
const r = await fetch(`${base}/api/v2/serverinfo`, { credentials: 'include' })
const body = await r.json().catch(() => null)
return !!body && typeof body.code === 'number'
} Type guard
function isV2Envelope(v: unknown): v is { code: number; msg: string; data: unknown } {
return typeof v === 'object' && v !== null && typeof (v as any).code === 'number'
} Try / catch
try {
return await requestV2<T>(url)
} catch (e) {
if (e instanceof Error && e.message === 'Invalid API v2 response') {
throw new Error('frps at this address does not speak the v2 API; upgrade frps or check the URL')
}
throw e
} Prevention
- Feature-detect the v2 envelope shape once at startup and disable v2 UI when absent
- Never front frps with a proxy that rewrites 2xx bodies
- Pin dashboard and frps to the same release line
When it happens
Trigger: Calling a /api/v2/* URL on a server that answers with a non-envelope body: an frps version without v2 API support returning plain JSON or HTML, a reverse proxy serving an HTML error/login page with 200, or an empty body from a misrouted request.
Common situations: Dashboard frontend newer than the frps binary it points at (v2 endpoints missing); a gateway/proxy in front of frps rewriting responses; pointing the v2 client at a v1-only endpoint path; CORS middleware returning its own 200 response.
Related errors
- envelope?.msg || `HTTP ${response.status}`
- envelope.msg
- HTTP ${response.status}
- HTTP ${response.status}
- create vhost http listener error, %v
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/a2aac7ed01e4948c.
Report an issue: GitHub.