Corvus Labs

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
}
HTTPJSON-RPC codeMessage prefixMeaningAction
400-32600Invalid request bodyMalformed or unsupported request bodyFix serialization; do not retry unchanged
401-32002UnauthorizedEndpoint, IP, or token is not authenticatedFix access configuration
403-32003 or -32004You have no access to this serviceResource does not include the requested serviceCheck plan/product access
429-32001RPS limit exceededRead allowance exhaustedReduce concurrency and back off
429-32005Transaction rate limit exceededTransaction TPS exhaustedPace sends and check signature before retry
429-32005Scan request timed out waiting for concurrency slotToo many concurrent scan-style requestsReduce scan concurrency; retry with jitter
502-32000Unable to process Batch RequestBatch could not be proxiedSplit or retry idempotent batch items
503-32099Server is not available at the momentTransient service unavailabilityRetry idempotent work with bounded backoff

Two things in that table catch people out:

  • -32005 covers two unrelated conditions, and the prefix is the only thing telling a TPS rejection apart from a scan-concurrency one.
  • -32001 counts 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 statusCodeCommon reasonAction
INVALID_ARGUMENT3invalid_argument, malformed_subscribeFix the request; do not retry unchanged
PERMISSION_DENIED7permission_deniedVerify product access
RESOURCE_EXHAUSTED8rate_limit, connection_limit, filters, subscribe_rate_limit, stream_limitFollow the detail, reduce load, then back off
FAILED_PRECONDITION9message beginning from_slot replayReplay unavailable on that connection; reconnect and retry, or re-subscribe without from_slot. See replay on reconnect
INTERNAL13internalRetry only under a bounded transient-error policy
UNAVAILABLE14service_unavailableReconnect with backoff
DATA_LOSS15message, e.g. laggedThe stream dropped data because the client fell behind; reconnect, optionally replaying with from_slot
UNAUTHENTICATED16unauthenticatedFix 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:

FieldValue
gRPC statusPERMISSION_DENIED (7)
Reasonpermission_denied
Detail prefixPPS limit requirement not met
TransportaRPC streaming endpoint, http://arpc.<region>.corvus-labs.io:20202
After the rejectionThe server shuts the connection down
Appears in a JSON-RPC bodyNever

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

ClassCodesRetry?Rule
Authentication or permission-32002, -32003, -32004, UNAUTHENTICATED, PERMISSION_DENIEDNoChange configuration first; a PPS close forces a new connection, but not an immediate one
Malformed request or filter-32600, INVALID_ARGUMENTNoCorrect the payload first
Rate or resource exhaustion-32001, -32005, RESOURCE_EXHAUSTEDYes, after reducing pressureHonor retry_after_ms when present; otherwise capped backoff with jitter
Service unavailable-32099, -32000, UNAVAILABLE, INTERNALYesRetry idempotent reads; reconnect streams and resend the full subscription set
Ambiguous transaction submissionConditionalQuery 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.

On this page