
grpcurl: Install, List, Describe and Stream gRPC APIs
grpcurl is curl for gRPC. Install it on macOS, Linux or Windows, then list, describe, call and stream real gRPC APIs with headers, API keys and jq.
grpcurl is a command-line tool from FullStory that works like curl for gRPC servers: it takes a JSON request, encodes it as protobuf, calls the method and prints the response as JSON. It reads the API schema from server reflection, from .proto source files or from compiled protoset files, and the latest release is v1.9.4, published on August 31, 2026.
This guide shows how to install grpcurl, then list, describe, call and stream two production gRPC APIs: Dwellir's Sui and Hyperliquid mainnet endpoints. These grpcurl examples are real. We ran every command on November 3, 2026 with grpcurl 1.9.4, and the outputs below come from those runs. We trimmed long outputs where marked.
What you will learn
- How to install grpcurl on macOS, Linux and Windows, or run it with Docker or Go
- How to list services and methods and describe messages with server reflection
- How to send JSON with
-dand-d @, and headers and API keys with-H - How to stream with grpcurl, stop a stream with
-max-timeand decode base64bytesfields with jq - How to work without reflection, with
-proto,-import-path,-protosetand-protoset-out - What common grpcurl errors and exit codes mean
Prerequisites
- A terminal on macOS, Linux or Windows
- jq 1.6 or later for the streaming examples (we used jq 1.8.0)
- A Dwellir API key, exported as an environment variable. Create one in the Dwellir dashboard.
export DWELLIR_API_KEY=YOUR_API_KEYThe Sui examples work with any Dwellir API key. The Hyperliquid examples need the Hyperliquid gRPC add-on, which costs $299/month on an API plan plus 1 API credit per streamed message. Without a key, or with a key that the server does not accept, both endpoints return PermissionDenied.
Try it: grpcurl command builder
Pick an endpoint, an action and the flags. The builder shows the exact command, what each part does and the response we captured for that combination, with its exit code.
Endpoint
Flags
Action
grpcurl \
-max-time 5 \
-expand-headers -H 'x-api-key: ${DWELLIR_API_KEY}' \
-d '{"read_mask":{"paths":["sequence_number"]}}' \
api-sui-mainnet-full.n.dwellir.com:443 \
sui.rpc.v2.SubscriptionService/SubscribeCheckpointsWhat each part does
grpcurlThe grpcurl binary. Every flag goes before the address.-max-time 5A deadline for the whole operation, in seconds. A stream that hits it ends with DeadlineExceeded and exit code 68.-expand-headersMakes grpcurl replace ${DWELLIR_API_KEY} with the environment variable. The single quotes stop your shell from doing it, so the key stays out of the process list.-H 'x-api-key: ${DWELLIR_API_KEY}'Adds gRPC metadata. Dwellir reads the key from x-api-key. grpcurl sends -H headers on reflection requests too, which Dwellir also authenticates.-d '{"read_mask":{"paths":["sequence_number"]}}'The request body as JSON. grpcurl converts it to protobuf with the schema it got from reflection. Field names can be snake_case or camelCase.api-sui-mainnet-full.n.dwellir.com:443host:port, with no scheme. grpcurl uses TLS unless you pass -plaintext, and Dwellir serves gRPC over TLS on 443.sui.rpc.v2.SubscriptionService/SubscribeCheckpointspackage.Service/Method to call. SubscribeCheckpoints pushes every new checkpoint. The read mask keeps each frame to the sequence number.
Captured response
exit 68{
"cursor": "341332852",
"checkpoint": {
"sequenceNumber": "341332852"
}
}
{
"cursor": "341332853",
"checkpoint": {
"sequenceNumber": "341332853"
}
}
{
"cursor": "341332854",
"checkpoint": {
"sequenceNumber": "341332854"
}
}14 more frames trimmedERROR:
Code: DeadlineExceeded
Message: context deadline exceededExit code 68, which is 64 + 4 (DeadlineExceeded). The deadline set with -max-time ended the stream after 5 s, as intended.
Responses captured with grpcurl 1.9.4 against Dwellir mainnet on November 3, 2026. Long responses are trimmed where marked, and Hyperliquid wallet addresses are shortened.
How do I install grpcurl?
Install grpcurl with brew install grpcurl on macOS, with a release binary on Linux or Windows, or with go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest on any system with Go. Every release ships prebuilt binaries for macOS, Linux and Windows on the grpcurl releases page.
| System | Command | Version on November 3, 2026 |
|---|---|---|
| macOS (Homebrew) | brew install grpcurl | 1.9.4 (formula) |
| macOS or Linux (release binary) | Download grpcurl_1.9.4_<os>_<arch>.tar.gz | 1.9.4 |
| Debian, Ubuntu, Fedora | .deb and .rpm files on the releases page | 1.9.4 |
| Linux (Snap) | snap install grpcurl | See Snapcraft |
| Windows (zip) | Download grpcurl_1.9.4_windows_x86_64.zip | 1.9.4 |
| Windows (Scoop) | scoop install grpcurl | 1.9.4 (manifest) |
| Windows (winget) | winget install fullstorydev.grpcurl | 1.9.3 (manifest) |
| Docker | docker pull ghcr.io/fullstorydev/grpcurl:latest | Follows releases |
| Any system with Go | go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest | Latest tag |
We tested the macOS arm64 release binary and Homebrew. The Linux, Windows, Docker and Snap commands come from the grpcurl README and the package manifests linked in the table.
Install the release binary on macOS or Linux
The tarball holds a single grpcurl binary. Pick the file for your system: osx_arm64, osx_x86_64, linux_x86_64, linux_arm64 and others.
curl -sSL https://github.com/fullstorydev/grpcurl/releases/download/v1.9.4/grpcurl_1.9.4_osx_arm64.tar.gz | tar -xz grpcurl
sudo mv grpcurl /usr/local/bin/
grpcurl -versiongrpcurl v1.9.4Install grpcurl on Windows
Download grpcurl_1.9.4_windows_x86_64.zip (or the arm64 or x86_32 zip) from the releases page, unzip it and add the folder with grpcurl.exe to your PATH. With a package manager, use Scoop (scoop install grpcurl) or winget (winget install fullstorydev.grpcurl). Chocolatey has no grpcurl package: community.chocolatey.org/packages/grpcurl returned 404 on November 3, 2026.
In PowerShell, set the key with $env:DWELLIR_API_KEY = "YOUR_API_KEY". PowerShell handles quotes inside arguments differently from bash, so the -d @ form below, which reads the body from standard input, avoids escaping inline JSON. We ran the examples in this guide in bash on macOS.
Run grpcurl with Docker or Go
The README points to the GitHub Container Registry image, ghcr.io/fullstorydev/grpcurl. The Docker Hub image fullstorydev/grpcurl was last updated on March 11, 2025, so pull from ghcr.io. Everything after the image name in docker run goes to grpcurl, so the flags in this guide work unchanged. The README lists three Docker details: add -i to docker run when you pipe a body into -d @, mount a folder with -v $(pwd):/protos when you pass -proto or -protoset files, and use host.docker.internal instead of localhost to reach a server on your machine. We did not run the Docker image for this guide.
With Go, go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest puts the binary in $(go env GOPATH)/bin, which defaults to $HOME/go/bin. Add that folder to your PATH.
How do I list services and methods?
Run grpcurl HOST:PORT list to list every service a server exposes through reflection, and grpcurl HOST:PORT list SERVICE to list the methods of one service. All flags, including -H, go before the address.
grpcurl -expand-headers -H 'x-api-key: ${DWELLIR_API_KEY}' \
api-sui-mainnet-full.n.dwellir.com:443 listgrpc.health.v1.Health
grpc.reflection.v1.ServerReflection
sui.rpc.v2.LedgerService
sui.rpc.v2.MovePackageService
sui.rpc.v2.NameService
sui.rpc.v2.SignatureVerificationService
sui.rpc.v2.StateService
sui.rpc.v2.SubscriptionService
sui.rpc.v2.TransactionExecutionService
sui.rpc.v2alpha.ProofServiceList the methods of one service:
grpcurl -expand-headers -H 'x-api-key: ${DWELLIR_API_KEY}' \
api-sui-mainnet-full.n.dwellir.com:443 list sui.rpc.v2.LedgerServicesui.rpc.v2.LedgerService.BatchGetObjects
sui.rpc.v2.LedgerService.BatchGetTransactions
sui.rpc.v2.LedgerService.GetCheckpoint
sui.rpc.v2.LedgerService.GetEpoch
sui.rpc.v2.LedgerService.GetObject
sui.rpc.v2.LedgerService.GetServiceInfo
sui.rpc.v2.LedgerService.GetTransaction
sui.rpc.v2.LedgerService.ListCheckpoints
sui.rpc.v2.LedgerService.ListEvents
sui.rpc.v2.LedgerService.ListTransactionslist prints methods as Service.Method. To call one, you can write either Service/Method or Service.Method. This guide uses the slash.
The Hyperliquid endpoint also has reflection. It exposes the V2 and V3 gateways and the V3 MarketStreaming service:
grpcurl -expand-headers -H 'x-api-key: ${DWELLIR_API_KEY}' \
api-hyperliquid-mainnet-grpc.n.dwellir.com:443 listgrpc.health.v1.Health
grpc.reflection.v1.ServerReflection
grpc.reflection.v1alpha.ServerReflection
hyperliquid_l1_gateway.v2.HyperliquidL1Gateway
hyperliquid_l1_gateway.v3.HyperliquidL1Gateway
hyperliquid_l1_gateway.v3.MarketStreaminglist hyperliquid_l1_gateway.v3.MarketStreaming returns 14 typed streams, from StreamActiveAssetCtx to StreamUserFills. The Hyperliquid gRPC overview documents each one.
How do I describe a message?
Run grpcurl HOST:PORT describe SYMBOL to print a service, method or message as proto source. Add -msg-template to also print a JSON template of the request, which you can edit into a -d body.
Describe a method first to find its request type:
grpcurl -expand-headers -H 'x-api-key: ${DWELLIR_API_KEY}' \
api-sui-mainnet-full.n.dwellir.com:443 describe sui.rpc.v2.LedgerService.GetServiceInfosui.rpc.v2.LedgerService.GetServiceInfo is a method:
rpc GetServiceInfo ( .sui.rpc.v2.GetServiceInfoRequest ) returns ( .sui.rpc.v2.GetServiceInfoResponse );Then describe the request message, with a template:
grpcurl -expand-headers -H 'x-api-key: ${DWELLIR_API_KEY}' -msg-template \
api-sui-mainnet-full.n.dwellir.com:443 describe sui.rpc.v2.GetCheckpointRequestsui.rpc.v2.GetCheckpointRequest is a message:
message GetCheckpointRequest {
oneof checkpoint_id {
uint64 sequence_number = 1;
string digest = 2;
}
optional .google.protobuf.FieldMask read_mask = 3;
}
Message template:
{
"readMask": {
"paths": [
""
]
}
}The template leaves out oneof members. You set a oneof by naming the member directly, as in {"sequence_number":"1"}. The wrapper name does not work: {"checkpoint_id":{...}} fails with has no known field named checkpoint_id.
describe takes one symbol per run. Two symbols in one command fail with Too many arguments.
How do I call a method with JSON input?
Pass the request body as JSON with -d '{...}', then the address, then package.Service/Method. grpcurl converts the JSON to protobuf with the schema from reflection and prints the response as JSON. Without -d, grpcurl sends an empty request.
grpcurl -max-time 10 -expand-headers -H 'x-api-key: ${DWELLIR_API_KEY}' \
-d '{}' api-sui-mainnet-full.n.dwellir.com:443 sui.rpc.v2.LedgerService/GetServiceInfo{
"chainId": "4btiuiMPvEENsttpZC7CZ53DruC3MAgfznDbASZ7DR6S",
"chain": "mainnet",
"epoch": "1300",
"checkpointHeight": "341332843",
"timestamp": "2026-11-03T21:17:24.319Z",
"lowestAvailableCheckpoint": "335870173",
"lowestAvailableCheckpointObjects": "335870173",
"server": "sui-node/1.82.5-550e784ea54d"
}A field mask keeps the response small:
grpcurl -max-time 10 -expand-headers -H 'x-api-key: ${DWELLIR_API_KEY}' \
-d '{"read_mask":{"paths":["sequence_number","summary.timestamp"]}}' \
api-sui-mainnet-full.n.dwellir.com:443 sui.rpc.v2.LedgerService/GetCheckpoint{
"checkpoint": {
"sequenceNumber": "341332956",
"summary": {
"timestamp": "2026-11-03T21:17:52.148Z"
}
}
}Three rules of the JSON mapping show up here. Request field names can be snake_case (read_mask) or camelCase (readMask). Responses use camelCase. 64-bit integers such as uint64 print as strings.
Read the body from stdin with -d @
-d @ tells grpcurl to read the request body from standard input. Use it for long bodies, for JSON built with jq, or on Windows, where inline JSON quoting is awkward:
grpcurl -max-time 10 -expand-headers -H 'x-api-key: ${DWELLIR_API_KEY}' \
-d @ api-sui-mainnet-full.n.dwellir.com:443 sui.rpc.v2.LedgerService/GetServiceInfo <<'EOF'
{}
EOFPiping works the same way: echo '{}' | grpcurl ... -d @ .... For methods that accept a stream of requests, put several JSON objects on stdin, and grpcurl sends each one as a message.
How do I send headers and API keys?
Add gRPC metadata with -H 'name: value' before the address, and repeat -H for each header. Dwellir reads the API key from the x-api-key header. grpcurl sends -H headers on reflection requests too, so a key that the server needs for reflection works without extra flags.
grpcurl -expand-headers -H 'x-api-key: ${DWELLIR_API_KEY}' \
api-sui-mainnet-full.n.dwellir.com:443 list-expand-headers makes grpcurl replace ${NAME} in header values with the environment variable. The single quotes stop the shell from expanding it first, so the key never appears in the process list or in your shell history. Without -expand-headers, use double quotes and let the shell expand it: -H "x-api-key: $DWELLIR_API_KEY".
| Flag | Sent on the RPC | Sent on reflection |
|---|---|---|
-H 'name: value' | Yes | Yes |
-rpc-header 'name: value' | Yes | No |
-reflect-header 'name: value' | No | Yes |
Add -v to see what grpcurl sends and receives. It prints the resolved method, the request metadata, the response headers and trailers:
Resolved method descriptor:
rpc GetServiceInfo ( .sui.rpc.v2.GetServiceInfoRequest ) returns ( .sui.rpc.v2.GetServiceInfoResponse );
Request metadata to send:
x-api-key: YOUR_API_KEY
Response headers received:
content-type: application/grpc
server: sui-node/1.82.5-550e784ea54d
x-sui-chain: mainnet
x-sui-checkpoint-height: 341332845
x-sui-epoch: 1300
x-sui-lowest-available-checkpoint: 336040478
Response contents:
{ ... }
Response trailers received:
(empty)
Sent 1 request and received 1 responseWe trimmed the response headers. -v prints the real key in Request metadata to send, so redact it before you paste the output into a ticket or a chat. -vv adds timing data. On one of our runs, a GetServiceInfo call took 466.7 ms in total, of which 40.8 ms was the dial.
How do I stream with grpcurl?
Call a server-streaming method the same way as a unary one: grpcurl prints each message as a separate JSON object as it arrives. The stream runs until the server ends it, you press Ctrl+C, or the -max-time deadline expires. With -max-time, the stream ends with DeadlineExceeded and exit code 68, which is the expected way to stop an unbounded stream in a script.
Stream Sui checkpoints
SubscribeCheckpoints pushes every new Sui checkpoint. The read mask keeps each frame to the sequence number:
grpcurl -max-time 5 -expand-headers -H 'x-api-key: ${DWELLIR_API_KEY}' \
-d '{"read_mask":{"paths":["sequence_number"]}}' \
api-sui-mainnet-full.n.dwellir.com:443 sui.rpc.v2.SubscriptionService/SubscribeCheckpoints
echo "exit=$?"{
"cursor": "341332852",
"checkpoint": {
"sequenceNumber": "341332852"
}
}
{
"cursor": "341332853",
"checkpoint": {
"sequenceNumber": "341332853"
}
}
... 15 more frames ...
ERROR:
Code: DeadlineExceeded
Message: context deadline exceeded
exit=68The stream delivered 17 frames in 5 seconds. Each frame is a separate JSON object, not an array, so pipe the stream to jq -c to get one line per message:
grpcurl -max-time 3 -expand-headers -H 'x-api-key: ${DWELLIR_API_KEY}' \
-d '{"read_mask":{"paths":["sequence_number","summary.timestamp"]}}' \
api-sui-mainnet-full.n.dwellir.com:443 sui.rpc.v2.SubscriptionService/SubscribeCheckpoints \
| jq -c '{seq: .checkpoint.sequenceNumber, ts: .checkpoint.summary.timestamp}'ERROR:
Code: DeadlineExceeded
Message: context deadline exceeded
{"seq":"341335344","ts":"2026-11-03T21:27:34.474Z"}
{"seq":"341335345","ts":"2026-11-03T21:27:34.675Z"}
{"seq":"341335346","ts":"2026-11-03T21:27:34.876Z"}The ERROR lines go to stderr, so they print before jq flushes its output. In a pipe, $? is jq's exit code. Use set -o pipefail or ${PIPESTATUS[0]} in bash to get grpcurl's 68. The Sui grpcurl setup page has more Sui stream and checkpoint-range examples.
Stream Hyperliquid trades
The Hyperliquid MarketStreaming service returns decoded JSON. Describe the request first, because the field names are plural:
grpcurl -expand-headers -H 'x-api-key: ${DWELLIR_API_KEY}' \
api-hyperliquid-mainnet-grpc.n.dwellir.com:443 describe hyperliquid_l1_gateway.v3.TradesRequesthyperliquid_l1_gateway.v3.TradesRequest is a message:
message TradesRequest {
repeated string coins = 1;
repeated string users = 2;
}grpcurl -max-time 5 -expand-headers -H 'x-api-key: ${DWELLIR_API_KEY}' \
-d '{"coins":["BTC"]}' \
api-hyperliquid-mainnet-grpc.n.dwellir.com:443 hyperliquid_l1_gateway.v3.MarketStreaming/StreamTrades{
"time": "1793740659339",
"blockNumber": "1208044332",
"trades": [
{
"coin": "BTC",
"side": "B",
"px": "77911.0",
"sz": "0.12925",
"hash": "0x6efdf4436318ab1670770448014b2c0205550028fe1bc9e812c69f96221c8501",
"time": "1793740659339",
"tid": "170037552688317",
"bidUsers": ["0xecb6…2b00"],
"askUsers": ["0x7717…bd7e"]
}
]
}We shortened the wallet addresses and the trades array. In 5 seconds the stream sent 9 updates for BTC. -d '{"coin":"BTC"}' (singular) fails before the RPC is sent, with message type hyperliquid_l1_gateway.v3.TradesRequest has no known field named coin. See the StreamTrades reference for every field.
Decode base64 bytes fields with jq
grpcurl prints protobuf bytes fields as base64. The Hyperliquid V3 raw feeds, such as StreamFills, put the block's JSON in a bytes field named data:
{
"blockNumber": "1208044715",
"timestamp": "1793740687002",
"data": "eyJsb2NhbF90aW1lIjoiMjAyNi0xMS0wM1QyMToxODowNy40NDMwNzY2NjkiLCJibG9ja190aW1lIjo..."
}jq's @base64d decodes it, and fromjson parses the result:
grpcurl -max-time 10 -expand-headers -H 'x-api-key: ${DWELLIR_API_KEY}' \
-d '{"filter":{"fields":[{"field":"coins","values":["BTC"]}]}}' \
api-hyperliquid-mainnet-grpc.n.dwellir.com:443 hyperliquid_l1_gateway.v3.HyperliquidL1Gateway/StreamFills \
| jq -c '.data | @base64d | fromjson | {block_number, n: (.events|length), first: .events[0][1] | {coin, px, sz, side, dir}}'{"block_number":1208044577,"n":2,"first":{"coin":"BTC","px":"77915.0","sz":"0.00032","side":"B","dir":"Open Long"}}
{"block_number":1208044592,"n":2,"first":{"coin":"BTC","px":"77915.0","sz":"0.00031","side":"B","dir":"Open Long"}}
{"block_number":1208044615,"n":2,"first":{"coin":"BTC","px":"77915.0","sz":"0.00019","side":"B","dir":"Open Long"}}The filter in this request is a server-side filter. ListFeeds tells you which filter fields each feed accepts. On November 3, 2026 the fills feed accepted users, coins and dexes. See the StreamFills reference and ListFeeds.
A filtered stream can stay silent for minutes when nothing matches. Ask for progress frames, or use a longer -max-time. The Hyperliquid gRPC troubleshooting page covers both. To see what the same feeds look like in a production pipeline, read how we streamed the Hyperliquid mempool over gRPC and built a machine learning dataset from Hyperliquid gRPC.
What if the server has no reflection?
Without reflection, grpcurl needs the schema from you: pass -proto with the .proto file (and -import-path for its imports), or -protoset with a compiled descriptor set. A server without reflection fails list with this error:
Failed to list services: server does not support the reflection APIThat output is from grpcurl pubsub.googleapis.com:443 list, a Google API that does not expose reflection.
Use a .proto file with -proto and -import-path
Dwellir publishes the Hyperliquid V3 schema at /docs/hyperliquid/grpc/v3.proto. With -proto, list and describe read the file and need no server address:
curl -fsSLo v3.proto https://www.dwellir.com/docs/hyperliquid/grpc/v3.proto
grpcurl -proto v3.proto listhyperliquid_l1_gateway.v3.HyperliquidL1Gateway
hyperliquid_l1_gateway.v3.MarketStreamingA call with -proto skips reflection and sends the RPC directly:
grpcurl -max-time 10 -proto v3.proto \
-expand-headers -H 'x-api-key: ${DWELLIR_API_KEY}' -d '{}' \
api-hyperliquid-mainnet-grpc.n.dwellir.com:443 hyperliquid_l1_gateway.v3.HyperliquidL1Gateway/ListFeedsIf your .proto imports other files, add -import-path for each folder that holds them. grpcurl knows the google.protobuf well-known types without an import path. An absolute file path needs an import path too: grpcurl -proto /abs/path/v3.proto list fails with must specify at least one import path if any absolute file paths are given. Use -import-path /abs/path -proto v3.proto instead.
Save and reuse a protoset with -protoset-out
When a server has reflection, -protoset-out saves the schema of what you list or describe to a file, with all its dependencies. You can then work offline, or against a copy of the server without reflection:
grpcurl -expand-headers -H 'x-api-key: ${DWELLIR_API_KEY}' \
-protoset-out sui.protoset \
api-sui-mainnet-full.n.dwellir.com:443 describe sui.rpc.v2.LedgerService
grpcurl -protoset sui.protoset listsui.rpc.v2.LedgerServiceThe file for this one service was 52,646 bytes, because it bundles every message the service depends on. A call with -protoset sui.protoset returns the same GetServiceInfo response as the reflection call above. To build a protoset from source files without a server, use protoc --descriptor_set_out=out.protoset --include_imports your.proto.
You cannot combine -proto and -protoset in one command. Add -use-reflection to either one if you want grpcurl to use reflection as well.
Common grpcurl errors and fixes
Most grpcurl errors come from a flag that does not match the server, a missing key, or a request body that does not match the schema. Each error below is real output from grpcurl 1.9.4.
| Error output | Cause | Fix |
|---|---|---|
Failed to list services: server does not support the reflection API | The server has no reflection | Pass -proto with -import-path, or -protoset |
PermissionDenied desc = unexpected HTTP status code received from server: 403 (Forbidden) | Missing or invalid API key. Dwellir adds API key does not exist in a data: line | Send -H 'x-api-key: ...'. Hyperliquid also needs the gRPC add-on |
Failed to dial target host "api-sui-mainnet-full.n.dwellir.com:443": EOF | -plaintext against a TLS port | Drop -plaintext. Use it only for servers without TLS, such as localhost |
Failed to dial target host "...": context deadline exceeded | Wrong host or port, or a firewall. grpcurl waits 10 s by default | Check the address. Add -connect-timeout 2 to fail faster |
message type ... has no known field named coin | The JSON body does not match the schema | describe the request message, or use -msg-template |
service "sui.rpc.v2.LedgerService" does not include a method named "Nope" | A typo in the method name | Run list SERVICE |
grpc: received message larger than max (1426 vs. 10) with ResourceExhausted | The response is larger than -max-msg-sz (default 4,194,304 bytes) | Raise it, for example -max-msg-sz 16777216 |
Code: DeadlineExceeded after streamed output | -max-time expired | Expected for a capped stream. Raise or drop -max-time for longer runs |
Code: NotFound, Message: Checkpoint 1 not found | The data is older than the node keeps | Read lowestAvailableCheckpoint from GetServiceInfo |
Code: InvalidArgument, block_number 0 is not a valid cursor; omit position for latest | An explicit zero cursor on a Hyperliquid stream | Leave position out to start from the latest record |
must specify at least one import path if any absolute file paths are given | -proto with an absolute path | Add -import-path and pass the file name |
error getting request data: line 1, col 1: Expecting an identifier or tag number | -format text with a JSON body | Drop -format text, or write the body in protobuf text format |
Add -format-error to print the gRPC error status as JSON, which is easier to parse in scripts:
{
"code": 5,
"message": "Checkpoint 1 not found"
}grpcurl exit codes
grpcurl 1.9.4 exits with 0 on success, 2 on a usage error (a bad flag or too many arguments), 1 when a call fails in grpcurl itself, and 64 plus the gRPC status code when the RPC returns an error status. We observed these codes in our runs. The grpcurl README and -help text do not document them, so treat the mapping as observed behaviour that a future release could change.
| Exit code | Meaning | Where we saw it |
|---|---|---|
| 0 | Success | Every successful call |
| 1 | Tool error: connection failure, reflection failure, bad request JSON | -plaintext on a TLS port, no reflection, an unknown field |
| 2 | Usage error: unknown flag or too many arguments | -nope, describe with two symbols |
| 67 | 64 + 3, InvalidArgument | Hyperliquid stream with block_number 0 |
| 68 | 64 + 4, DeadlineExceeded | Any stream stopped by -max-time |
| 69 | 64 + 5, NotFound | Sui GetCheckpoint for checkpoint 1 |
| 71 | 64 + 7, PermissionDenied | Hyperliquid call with -proto and no API key |
The same missing key gives different exit codes depending on how grpcurl gets the schema. With reflection, the 403 hits the reflection request, so grpcurl reports a tool error and exits with 1. With -proto, there is no reflection request, the RPC itself returns PermissionDenied, and grpcurl exits with 71.
grpcurl vs grpcui vs Postman vs Evans
grpcurl is the scriptable choice: one command, JSON in and out, exit codes you can test. The other three tools suit interactive exploration.
| Tool | Interface | Schema sources | Streaming | Latest release |
|---|---|---|---|---|
| grpcurl | Command line | Reflection, .proto, protoset | All four kinds, interactive bidirectional from a terminal | v1.9.4, August 31, 2026 |
| grpcui | Web UI started from the command line | Reflection, .proto, protoset | All kinds, but you send the whole request stream at once and see the whole response stream at once | v1.5.4, September 2, 2026 |
| Postman | Desktop and web app | Reflection, or an imported .proto | Unary, client, server and bidirectional streaming | Hosted app |
| Evans | REPL and CLI modes | Reflection, .proto | Client, server and bidirectional streaming | v0.10.11, February 26, 2023 |
grpcui comes from the same FullStory team and takes the same flags for reflection, -proto and -protoset, so a command that works in grpcurl usually works in grpcui. Evans has had no release since February 2023.
Next steps
- Use the Sui grpcurl setup page for Sui-specific calls: balances, package functions, checkpoint ranges and transaction execution. The Sui gRPC overview lists every service.
- Read the Hyperliquid gRPC overview for positions, filters, progress frames and retention on the V3 API, and see Hyperliquid gRPC streaming for plans and pricing.
- Move from grpcurl to code with the Dwellir gRPC code examples once your request bodies work.
Frequently Asked Questions
What is grpcurl?
grpcurl is an open-source command-line tool from FullStory that works like curl for gRPC servers. It takes a JSON request, encodes it as protobuf, calls the method and prints the response as JSON. It reads the API schema from server reflection, from .proto source files or from a compiled protoset file. The latest release is v1.9.4, published on August 31, 2026.
How do I pass headers in grpcurl?
Use -H 'name: value' before the server address, and repeat -H for each extra header. grpcurl sends -H headers on the RPC and on reflection requests. Use -rpc-header for headers that only the RPC needs, and -reflect-header for headers that only reflection needs. To keep an API key out of the process list, add -expand-headers and write the header as -H 'x-api-key: ${DWELLIR_API_KEY}' in single quotes.
Does grpcurl support streaming?
Yes. grpcurl supports unary, server streaming, client streaming and bidirectional streaming methods. A server stream prints each message as its own JSON object until the server ends the stream, you press Ctrl+C, or -max-time expires. With -max-time the stream ends with DeadlineExceeded, and grpcurl 1.9.4 exits with code 68. Pipe the output to jq -c to get one line per message.
How do I use grpcurl without reflection?
Pass the schema yourself. Use -proto file.proto, with -import-path for the folders that hold its imports, or use -protoset file.protoset with a compiled FileDescriptorSet. With either flag, list and describe work offline without a server address. If a server does have reflection, -protoset-out saves its schema to a protoset file that you can reuse later.
How do I install grpcurl on Windows?
Download grpcurl_1.9.4_windows_x86_64.zip (or the arm64 or x86_32 zip) from the grpcurl GitHub releases page and put grpcurl.exe on your PATH. Scoop has the package in its main bucket at version 1.9.4: scoop install grpcurl. winget has fullstorydev.grpcurl at version 1.9.3. Chocolatey has no grpcurl package. You can also run the ghcr.io/fullstorydev/grpcurl Docker image.
What do grpcurl exit codes mean?
In our tests with grpcurl 1.9.4, exit code 0 meant success, 2 meant a usage error (a bad flag or too many arguments) and 1 meant a tool-side failure, such as a failed connection, a reflection error or a request body that does not match the schema. A gRPC error status from the RPC gave 64 plus the status code: 67 for InvalidArgument, 68 for DeadlineExceeded, 69 for NotFound and 71 for PermissionDenied. The grpcurl README does not document these codes.