hcengineering/platform · warning
Unprocessable Entity
Error message
Unprocessable Entity
What it means
The link-preview service's GET /api/v1/preview endpoint catches any exception thrown while parsing the URL given in the `q` query parameter and responds with HTTP 422 {"message":"Unprocessable Entity"}. The service throws this whenever parseLinkPreviewDetails fails — e.g. the target site is unreachable, blocks scraping, returns non-HTML, or the URL is malformed. The actual cause is only logged server-side, so the client only sees the generic 422 body.
Source
Thrown at pods/link-preview/src/server.ts:101
app.get(
'/details',
withAuthorization,
wrapRequest(ctx, 'getLinkPreviewDetails', async (ctx, req, res) => {
if (req.query === undefined) {
res.status(400).json({ message: 'Bad Request' })
return
}
if (req.query.q === undefined) {
res.status(400).json({ message: 'Bad Request' })
return
}
const url = req.query.q as string
try {
const result = await parseLinkPreviewDetails(ctx, config, url)
res.status(200).json(result)
} catch (err) {
console.error({ message: 'failed to parse link preview details', url, err })
res.status(422).json({ message: 'Unprocessable Entity' })
}
})
)
app.get('/api/v1/statistics', (req, res) => {
try {
const token = req.query.token as string
const payload = decodeToken(token)
const admin = payload.extra?.admin === 'true'
res.setHeader('Content-Type', 'application/json')
res.setHeader('Cache-Control', cacheControlNoCache)
const json = JSON.stringify({
metrics: metricsAggregate((ctx as any).metrics),
statistics: {
cpu: getCPUInfo(),
memory: getMemoryInfo()
},View on GitHub (pinned to 63e28dc964)
Solutions
- Verify the `q` parameter is a fully qualified, reachable absolute URL (curl it first) and retry.
- Check the server logs for the 'failed to parse link preview details' entry containing the underlying error and the offending url.
- If the target site blocks bots, configure the fetcher (user-agent, proxy) or skip previews for that domain.
- Add client-side fallback UI rendering a plain link when a 422 is returned instead of surfacing an error.
Example fix
// before
await fetch(`/api/v1/preview?q=${userUrl}`).then(r => r.json())
// after
const res = await fetch(`/api/v1/preview?q=${encodeURIComponent(userUrl)}`)
if (res.status === 422) {
renderPlainLink(userUrl)
} else {
renderPreview(await res.json())
} Defensive patterns
Strategy: fallback
Validate before calling
const u = new URL(q)
if (!/^https?:$/.test(u.protocol)) throw new Error('Only http(s) URLs are supported')
const reachable = await fetch(u, { method: 'HEAD' }).then(r => r.ok).catch(() => false)
if (!reachable) throw new Error('Target URL is not reachable') Type guard
function isAbsoluteHttpUrl(value: unknown): value is string {
if (typeof value !== 'string') return false
try { const u = new URL(value); return u.protocol === 'http:' || u.protocol === 'https:' } catch { return false }
} Try / catch
try {
const preview = await getPreview(url)
renderPreview(preview)
} catch (err) {
if (err.status === 422) renderPlainLink(url)
else throw err
} Prevention
- Always send fully qualified absolute http(s) URLs, URL-encoded in the q parameter.
- Pre-flight check reachability with a HEAD request before requesting a preview.
- Design UI to gracefully fall back to a plain link when previews fail.
- Maintain a skip-list for domains known to block scrapers.
When it happens
Trigger: GET /api/v1/preview?q=<url> where the target URL cannot be fetched or parsed: invalid/unreachable URL, DNS failure, target returns 4xx/5xx, redirects to non-HTML content, TLS errors, or timeouts inside parseLinkPreviewDetails.
Common situations: Passing a URL without a scheme (example.com instead of https://example.com), previewing sites behind Cloudflare bot protection or a corporate firewall, previewing intranet URLs from a service with no network access, or previewing a page that was taken down.
Related errors
- response.statusText
- Failed to fetch config
- rate-limit
- unknownError(response.statusText)
- Missing response body
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/03d216eeb929a4e3.
Report an issue: GitHub.