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

  1. Verify the target frps binary actually implements /api/v2 (check version and release notes); upgrade frps or use v1 endpoints
  2. curl the exact /api/v2 URL and confirm the body is {"code":...,"msg":...,"data":...}
  3. Remove intermediate proxies that rewrite the response body, or make them pass it through untouched
  4. 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

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


AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15). Data as JSON: /api/errors/a2aac7ed01e4948c. Report an issue: GitHub.