hcengineering/platform · error · ApiError
err.code
err.code
Error message
err.message
What it means
The pod-mail error-handling middleware catches an ApiError thrown (or forwarded via catchError) by an endpoint handler and responds HTTP 400 with { code: err.code, message: err.message }. The message text is whatever the ApiError carried — often a field-validation message like "'from' is missing". It signals a client-side bad request rather than a server fault.
Source
Thrown at services/mail/pod-mail/src/server.ts:53
app.use(cors())
app.use(express.json())
endpoints.forEach((endpoint) => {
if (endpoint.type === 'get') {
app.get(endpoint.endpoint, catchError(endpoint.handler))
} else if (endpoint.type === 'post') {
app.post(endpoint.endpoint, catchError(endpoint.handler))
}
})
app.use((_req, res, _next) => {
res.status(404).send({ message: 'Not found' })
})
app.use((err: any, _req: any, res: any, _next: any) => {
if (err instanceof ApiError) {
res.status(400).send({ code: err.code, message: err.message })
return
}
res.status(500).send({ message: err.message })
})
return app
}
export function listen (e: Express, port: number, host?: string): Server {
const cb = (): void => {
console.log(`Mail service has been started at ${host ?? '*'}:${port}`)
}
return host !== undefined ? e.listen(port, host, cb) : e.listen(port, cb)
}
View on GitHub (pinned to 63e28dc964)
Solutions
- Read the 'code' and 'message' fields in the 400 response body — they identify exactly which validation failed; fix the request payload accordingly.
- In client code, parse the response body and branch on code instead of treating every non-2xx as a generic failure.
- If the ApiError originates from your own handler code, correct the thrown condition or throw a more specific ApiError code.
- Ensure catchError wraps handlers so ApiErrors surface as 400 rather than becoming 500s.
Example fix
// before
const res = await fetch(url, opts)
const data = await res.json()
use(data)
// after
if (!res.ok) {
const err = await res.json()
if (err.code) throw new Error(`ApiError ${err.code}: ${err.message}`)
}
const data = await res.json()
use(data) Defensive patterns
Strategy: try-catch
Validate before calling
function isApiErrorBody(b: unknown): b is { code: string; message: string } {
return typeof (b as any)?.code === 'string' && typeof (b as any)?.message === 'string'
} Type guard
function isApiErrorBody(v: unknown): v is { code: string; message: string } {
return v !== null && typeof v === 'object' && 'code' in v && 'message' in v
} Try / catch
const res = await fetch(url, opts)
if (!res.ok) {
const body = await res.json()
if (isApiErrorBody(body)) throw new Error(`ApiError ${body.code}: ${body.message}`)
throw new Error(`HTTP ${res.status}`)
} Prevention
- Always branch on the response 'code' field instead of the status alone
- Validate the full request payload against the endpoint's schema before sending
- Log code+message together to make failures greppable
When it happens
Trigger: Any endpoint handler throws ApiError (directly or via the catchError wrapper) with a 4xx-class code — e.g. malformed request parameters, invalid token format, or payload that fails domain validation inside pod-mail.
Common situations: Client sending payloads that pass the top-level undefined checks but fail deeper validation inside the mail handler; code reusing ApiError from the shared server library for business-rule violations; SDK wrappers surfacing the raw { code, message } body to application code.
Related errors
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/e68b8af22640423e.
Report an issue: GitHub.