Every error the service returns carries one numeric code from the table below, over REST and over the WebSocket alike. The first two digits mirror the HTTP status family, so 40101 maps to a 401 response.
The envelope
REST errors return a JSON body:
{ "error": { "message": "capability does not permit publish on this channel", "code": 40301, "statusCode": 403 } }
Over the WebSocket the same code arrives in an error frame, and the SDKs surface it as an Error with a code property on whichever promise or channel the failure belongs to.
Error codes
| Code | Name | Meaning | Retry? |
|---|---|---|---|
40001 |
BadFrame |
Malformed request, body, or channel name | No, fix the request |
40101 |
BadAuth |
Authentication failed, bad key or token | No, fix the credential |
40102 |
AuthExpired |
The token was valid and has expired | Yes, after re-authenticating. The SDK does this itself when it has an authCallback |
40300 |
Forbidden |
Authenticated but not permitted for this action | No |
40301 |
Capability |
The token’s capability doesn’t grant the operation | No, widen the capability |
40302 |
ChannelDenied |
The token’s capability doesn’t cover this channel | No, widen the capability |
40400 |
NotFound |
The referenced channel or app doesn’t exist | No |
42900 |
RateLimited |
A rate limit or quota was exceeded, see Limits | Yes, back off first |
50000 |
Server |
Unexpected server-side error | Maybe, once |
50001 |
Bootstrap |
The edge is still provisioning its streams | Yes, retry shortly |
The JavaScript SDK exports the table as ErrorCode, so handlers compare against names instead of numbers:
import { ErrorCode } from '@foony/realtime';
channel.publish('chat', data).catch((error) => {
if (error.code === ErrorCode.RateLimited) {
scheduleRetry();
}
});
WebSocket close codes
The codes above describe application errors. The WebSocket itself can also close, and the close code says why:
| Close code | Meaning |
|---|---|
1000, 1001 |
Normal closure, for example close() was called or the page navigated away |
1006 |
Abnormal closure. The socket died without a close frame |
4002 |
The SDK’s own keep-alive timeout. The server went silent, so the SDK cut the connection to reconnect |
A WebSocket error 1006 is not a Foony error code, it’s the browser reporting that the connection dropped without a goodbye: a network change, a killed tab, a proxy cutting an idle socket. The SDK treats it as disconnected and reconnects with backoff, so occasional 1006 closes are normal background noise. A 1006 that happens on every connect during the handshake is different: it usually means something between the client and realtime.foony.io is blocking WebSockets entirely, like a corporate proxy or firewall, or the auth frame never got a reply. Check that plain wss:// connections work from that network, and watch connection.on for the reason on the state change.
Where errors surface in the SDK
Publishes reject with the error. Attach failures move the channel to the failed state with the error as the reason. Credential problems the SDK can’t fix on its own move the connection to failed, as described in Connections. Everything recoverable is retried internally, so if you see an error, it’s one the SDK needed you to know about.