Errors
The typed errors the TypeScript SDK throws, which HTTP status maps to each, and how to handle them.
Every error the SDK raises extends KeelwaveError, so one catch covers the
whole SDK.
import { Keelwave, KeelwaveError, KeelwaveRateLimited } from 'keelwave'
const client = new Keelwave({
apiKey: process.env.KEELWAVE_API_KEY ?? '',
raiseOnError: true,
})
try {
await client.ingestAi({ model: 'gpt-4o', status: 'success' })
} catch (err) {
if (err instanceof KeelwaveRateLimited) {
console.warn(`rate limited, retry after ${err.retryAfter}s`)
} else if (err instanceof KeelwaveError) {
console.warn(`keelwave: ${err.message}`)
} else {
throw err
}
}With the default raiseOnError: false the ingest methods never throw — they
log [keelwave] emit failed: … and continue. The examples on this page assume
raiseOnError: true. See Client.
Error types
| Class | Extends | Raised when |
|---|---|---|
KeelwaveError | Error | Base class. Also used to wrap non-keelwave errors when raiseOnError is on. |
KeelwaveAuthError | KeelwaveError | HTTP 401 — missing, malformed, or revoked API key. |
KeelwaveValidationError | KeelwaveError | HTTP 400 — the payload failed server-side validation. |
KeelwaveRateLimited | KeelwaveError | HTTP 429 — rate limit hit. |
KeelwaveBufferFull | KeelwaveError | HTTP 503 — the server's ingest buffer is full. |
KeelwaveServerError | KeelwaveError | Any other non-2xx status, including 5xx. |
KeelwaveTransportError | KeelwaveError | fetch itself failed — DNS, connection refused, timeout. |
All seven are exported from the package root:
import {
KeelwaveError,
KeelwaveAuthError,
KeelwaveValidationError,
KeelwaveRateLimited,
KeelwaveBufferFull,
KeelwaveServerError,
KeelwaveTransportError,
} from 'keelwave'Each sets name to its class name, so err.name is usable in logs.
Extra properties
Two error types carry more than a message.
KeelwaveRateLimited.retryAfter — the Retry-After response header as a
number, or null when the server did not send one.
catch (err) {
if (err instanceof KeelwaveRateLimited) {
const waitMs = (err.retryAfter ?? 5) * 1000
await new Promise((r) => setTimeout(r, waitMs))
}
}KeelwaveServerError.status — the HTTP status code that produced the error.
catch (err) {
if (err instanceof KeelwaveServerError && err.status >= 500) {
// transient server-side problem, safe to retry
}
}Status mapping
The HTTP layer maps responses to types as follows.
| Status | Error |
|---|---|
| 400 | KeelwaveValidationError |
| 401 | KeelwaveAuthError |
| 429 | KeelwaveRateLimited |
| 503 | KeelwaveBufferFull |
| any other 4xx/5xx | KeelwaveServerError |
| network failure | KeelwaveTransportError |
The message comes from the response body's error field when present, falling
back to the HTTP status text.
Which calls throw
| Call | With raiseOnError: false | With raiseOnError: true |
|---|---|---|
client.health() | throws | throws |
client.ingestAi() | warns | throws |
run.step(), run.toolCall(), run.finish() | warns | throws |
client.run() opening the run | warns, uses a local run id | throws |
client.observe() / client.agent() emits | warns | throws |
wrapModel() emits | swallowed | swallowed |
Errors thrown by your own agent code are never intercepted — client.run()
marks the run failed with reason error and rethrows.
await expect(
client.run('test-agent', async () => {
throw new Error('simulated failure')
}),
).rejects.toThrow('simulated failure')Common causes
KeelwaveAuthError — check the key is a full kw_… value and that it
matches the server you are pointing at. Keys are per-deployment.
KeelwaveValidationError — usually a field outside its allowed set, for
example status on ingestAi must be 'success', 'error', or 'timeout'.
KeelwaveTransportError — the endpoint is wrong or the server is down. The
default endpoint is http://localhost:8080; set endpoint if your server lives
elsewhere.
KeelwaveBufferFull — the server is ingesting faster than it can flush.
Back off and retry; retryAfter is not set on this type, so choose your own
delay.