hcengineering/platform · error · ApiError

err.code

err.code

Error message

err.message

What it means

The pod-notification error-handling middleware converts a thrown ApiError into HTTP 400 { code: err.code, message: err.message }. The message in the response is the ApiError's own message. This indicates the server deemed the request invalid — a client-fixable problem — as opposed to the anonymous 500 fallback for non-ApiError exceptions.

Source

Thrown at services/notification/pod-notification/src/server.ts:52

  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, onListening?: () => void): Server {
  const cb = (): void => {
    if (onListening !== undefined) {
      onListening()
    } else {
      console.log(`Notification service has been started at ${host ?? '*'}:${port}`)
    }
  }

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Inspect the response body's 'code' field — it pinpoints the failing validation; adjust the request accordingly.
  2. Validate each subscription (endpoint URL, keys.p256dh, keys.auth) client-side before submitting the batch.
  3. Update stored subscriptions: remove ones whose codes indicate permanent rejection (e.g. 410 gone upstream).
  4. In handler code you own, throw ApiError with specific codes rather than raw Errors so clients get actionable 400s.

Example fix

// before
if (!res.ok) throw new Error(`request failed: ${res.status}`)
// after
const body = await res.json()
if (!res.ok) throw new Error(`[${body.code ?? res.status}] ${body.message}`)
Defensive patterns

Strategy: try-catch

Validate before calling

function validatePushPayload(body: unknown): string[] {
  const errs: string[] = []
  if (typeof (body as any)?.data !== 'object') errs.push('data missing')
  if (!Array.isArray((body as any)?.subscriptions)) errs.push('subscriptions missing')
  return errs
}

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(pushUrl, opts)
if (!res.ok && res.status !== 404) {
  const body = await res.json()
  if (isApiErrorBody(body)) throw new Error(`Push ApiError ${body.code}: ${body.message}`)
}

Prevention

When it happens

Trigger: Any notification endpoint handler (wrapped by catchError) throws ApiError — e.g. push payload validation failures inside the handler, invalid subscription entries that fail domain checks, or web-push library errors deliberately re-thrown as ApiError.

Common situations: Clients sending PushSubscription objects with missing/invalid keys that deeper validation rejects; expired push endpoints rejected by upstream causing the handler to throw ApiError; generic SDK handling that ignores the 'code' field and reports only the message.

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/6f23f501cef52e78. Report an issue: GitHub.