fatedier/frp · error · HTTPError
envelope?.msg || `HTTP ${response.status}`
Error message
envelope?.msg || `HTTP ${response.status}` What it means
Failure from the frps v2 API client (requestV2). It reads the response body as a V2Envelope even when HTTP status is not OK, and prefers the server-supplied envelope.msg as the error message, falling back to 'HTTP <status>' only when the body is not valid JSON. This surfaces the real server error text (e.g. 'token in login doesn't match') instead of a bare status code.
Source
Thrown at web/frps/src/api/http.ts:66
return response.json()
}
async function requestV2<T>(
url: string,
options: RequestInit = {},
): Promise<T> {
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 = (View on GitHub (pinned to 6c8a8d0a97)
Solutions
- Read the thrown error message — it is the server's own explanation (envelope.msg), so act on it directly
- For auth failures, re-login / refresh the v2 API session and retry the request
- If the message is the bare 'HTTP nnn' fallback, the body was not JSON — check whether a proxy or wrong port is intercepting the request
- Check frps logs for the matching request to confirm the server-side reason
Example fix
// before
const res = await requestV2('/api/v2/proxy/tcp') // throws 'session expired'
// after
try {
const res = await requestV2('/api/v2/proxy/tcp')
} catch (e: any) {
if (e?.status === 401) {
await relogin()
return retry()
}
throw e
} Defensive patterns
Strategy: try-catch
Type guard
function isV2HTTPError(e: unknown): e is { status: number; msg?: string } & Error {
return e instanceof Error && typeof (e as any).status === 'number'
} Try / catch
try {
await requestV2('/api/v2/proxy/tcp', init)
} catch (e) {
if (isV2HTTPError(e) && e.status === 401) { await refreshSession(); return retryOnce() }
showError(e instanceof Error ? e.message : String(e)) // message is envelope.msg from frps
throw e
} Prevention
- Wrap requestV2 calls in one place and standardize on the thrown message for user-facing errors
- Refresh the v2 session proactively on a timer instead of waiting for 401s
- Treat bare 'HTTP nnn' (envelope fallback) as a routing/proxy misconfiguration signal
When it happens
Trigger: Calling /api/v2/* endpoints on frps when the server rejects the request with a non-2xx status: expired or missing session/JWT for v2 API auth, requesting a resource ID that does not exist, or an invalid parameter — and the server responds with a JSON envelope whose msg carries the reason.
Common situations: v2 API session expired while the dashboard stayed open; token authentication misconfigured between dashboard and frps; mixing v1 and v2 endpoint paths so the server returns an HTML/empty error body (then the fallback 'HTTP <status>' appears).
Related errors
- Invalid API v2 response
- 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/50373c03d59be9fe.
Report an issue: GitHub.