fatedier/frp · error · HTTPError
envelope.msg
Error message
envelope.msg
What it means
Business-level error from the frps v2 API: the HTTP response was 200, but the V2Envelope's application code is >= 400. The v2 protocol carries errors inside a successful HTTP transaction, and requestV2 converts envelope.code/msg into an HTTPError so callers see a uniform error object with the server's message.
Source
Thrown at web/frps/src/api/http.ts:78
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()
return text ? `?${text}` : ''
}
export const http = {View on GitHub (pinned to 6c8a8d0a97)
Solutions
- Read the error message — it is envelope.msg verbatim from frps and names the exact problem (e.g. 'proxy already exists')
- Fix the submitted payload per the message and resubmit
- If the code is an auth/permission code, refresh the v2 session and check the operator's permissions
- Refresh the dashboard list to reconcile stale UI state before retrying creates
Example fix
// before
await requestV2('/api/v2/proxy/tcp', { method: 'POST', body: cfg }) // throws 'name already exists'
// after
try {
await requestV2('/api/v2/proxy/tcp', { method: 'POST', body: cfg })
} catch (e: any) {
if (e?.status === 409 || /already exists/.test(e?.message)) {
await requestV2(`/api/v2/proxy/tcp/${cfg.name}`, { method: 'PUT', body: cfg })
} else throw e
} Defensive patterns
Strategy: try-catch
Type guard
function isV2HTTPError(e: unknown): e is { status: number } & Error {
return e instanceof Error && typeof (e as any).status === 'number'
} Try / catch
try {
await requestV2(url, { method: 'POST', body })
} catch (e) {
if (isV2HTTPError(e) && e.status === 409) { await updateExisting(); return }
surfaceServerMessage(e) // envelope.msg is authoritative
throw e
} Prevention
- GET-before-POST to distinguish create from update
- Show the server msg verbatim in UI toasts — it names the offending field
- Reconcile list state after every failed mutation
When it happens
Trigger: Any /api/v2 operation the server rejects after receiving it: creating a proxy whose name already exists, submitting an invalid config payload, or performing an operation not allowed for the current session — the server replies 200 with {code: 4xx/5xx, msg: '<reason>'}.
Common situations: Duplicate proxy names when adding via the dashboard; validation failures in submitted proxy/visitor configs; permission checks enforced at the application layer rather than HTTP layer; stale UI state resubmitting an already-applied change.
Related errors
- envelope?.msg || `HTTP ${response.status}`
- Invalid API v2 response
- HTTP ${response.status}
- HTTP ${response.status}
- subdomain and custom domains should not be both empty
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/f6893f404eec75c9.
Report an issue: GitHub.