Gateway & streaming errors
Corvus Solana JSON-RPC and gRPC error reference: exact codes, message prefixes, gRPC reasons, and which failures are retryable.
Match on three things together: the transport status, the application code, and the stable message prefix. Any gateway message can pick up a trailing support sentence (Please contact support at discord.gg/corvus-labs), and one of the codes deliberately covers more than one condition, so full-string equality won't hold.
JSON-RPC and HTTP errors
What you get back is an ordinary JSON-RPC error object, usually with id: null:
{
"jsonrpc": "2.0",
"error": {
"code": -32001,
"message": "RPS limit exceeded"
},
"id": null
}| HTTP | JSON-RPC code | Message prefix | Meaning | Action |
|---|---|---|---|---|
| 400 | -32600 | Invalid request body | Malformed or unsupported request body | Fix serialization; do not retry unchanged |
| 401 | -32002 | Unauthorized | Endpoint, IP, or token is not authenticated | Fix access configuration |
| 403 | -32003 or -32004 | You have no access to this service | Resource does not include the requested service | Check plan/product access |
| 429 | -32001 | RPS limit exceeded | Read allowance exhausted | Reduce concurrency and back off |
| 429 | -32005 | Transaction rate limit exceeded | Transaction TPS exhausted | Pace sends and check signature before retry |
| 429 | -32005 | Scan request timed out waiting for concurrency slot | Too many concurrent scan-style requests | Reduce scan concurrency; retry with jitter |
| 502 | -32000 | Unable to process Batch Request | Batch could not be proxied | Split or retry idempotent batch items |
| 503 | -32099 | Server is not available at the moment | Transient service unavailability | Retry idempotent work with bounded backoff |
Two things in that table catch people out:
-32005covers two unrelated conditions, and the prefix is the only thing telling a TPS rejection apart from a scan-concurrency one.-32001counts weighted units rather than requests. See Solana RPC rate limits.
-32100 / PPS limit requirement not met isn't in the table because it never turns up in a JSON-RPC error body. It's a gRPC condition, covered under PPS limit requirement not met.
gRPC errors
On the streaming side you get a standard gRPC status plus a machine-readable reason. Where there's a detail, it's JSON:
{
"reason": "stream_limit",
"detail": "maximum concurrent streams exceeded"
}retry_after_ms is optional and often missing. Stream, filter and subscription-rate rejections never carry it at all; the two-key {reason, detail} object above is all you get.
| gRPC status | Code | Common reason | Action |
|---|---|---|---|
INVALID_ARGUMENT | 3 | invalid_argument, malformed_subscribe | Fix the request; do not retry unchanged |
PERMISSION_DENIED | 7 | permission_denied | Verify product access |
RESOURCE_EXHAUSTED | 8 | rate_limit, connection_limit, filters, subscribe_rate_limit, stream_limit | Follow the detail, reduce load, then back off |
FAILED_PRECONDITION | 9 | message beginning from_slot replay | Replay unavailable on that connection; reconnect and retry, or re-subscribe without from_slot. See replay on reconnect |
INTERNAL | 13 | internal | Retry only under a bounded transient-error policy |
UNAVAILABLE | 14 | service_unavailable | Reconnect with backoff |
DATA_LOSS | 15 | message, e.g. lagged | The stream dropped data because the client fell behind; reconnect, optionally replaying with from_slot |
UNAUTHENTICATED | 16 | unauthenticated | Fix token or IP access before reconnecting |
Stream reset protection
If you open and cancel streams fast enough, HTTP/2 deals with you before gRPC gets a look in. The server sends a connection-level GOAWAY with error code ENHANCE_YOUR_CALM (11), and its debug payload is the bare string RST_STREAM rate limit exceeded (potential rapid reset attack). There's no grpc-status and no JSON reason, and the whole connection goes rather than one stream. Match on the GOAWAY.
PPS limit requirement not met
On streaming endpoints this one reaches you as a gRPC status rather than as a JSON-RPC error:
| Field | Value |
|---|---|
| gRPC status | PERMISSION_DENIED (7) |
| Reason | permission_denied |
| Detail prefix | PPS limit requirement not met |
| Transport | aRPC streaming endpoint, http://arpc.<region>.corvus-labs.io:20202 |
| After the rejection | The server shuts the connection down |
| Appears in a JSON-RPC body | Never |
The check has nothing to do with how much traffic you're actually sending. It runs once, when the connection is admitted: it adds the RPS and TPS on your plan together and looks for a total of 500. If you're short, raise RPS or TPS in the dashboard. See plan contents and aRPC limits.
So PERMISSION_DENIED on a streaming endpoint means one of two things: the PPS condition above, or a resource that doesn't include the product you asked for. The detail prefix tells you which.
Deciding whether to retry
| Class | Codes | Retry? | Rule |
|---|---|---|---|
| Authentication or permission | -32002, -32003, -32004, UNAUTHENTICATED, PERMISSION_DENIED | No | Change configuration first; a PPS close forces a new connection, but not an immediate one |
| Malformed request or filter | -32600, INVALID_ARGUMENT | No | Correct the payload first |
| Rate or resource exhaustion | -32001, -32005, RESOURCE_EXHAUSTED | Yes, after reducing pressure | Honor retry_after_ms when present; otherwise capped backoff with jitter |
| Service unavailable | -32099, -32000, UNAVAILABLE, INTERNAL | Yes | Retry idempotent reads; reconnect streams and resend the full subscription set |
| Ambiguous transaction submission | — | Conditional | Query the signature, then resend the same signed bytes only while the blockhash is valid |
Reliability goes further into per-code recovery, and Troubleshooting starts from the symptom instead.
Solana RPC rate limits
How Corvus meters the Solana JSON-RPC endpoint: weighted method costs, the getMultipleAccounts formula, scan concurrency, and TPS.
Reliability
Which Corvus Solana RPC errors are retryable, how gRPC and aRPC streams recover, and why acceptance of a transaction is not confirmation.