# grpcurl: Install, List, Describe and Stream gRPC APIs

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](https://github.com/fullstorydev/grpcurl/releases/tag/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 `-d` and `-d @`, and headers and API keys with `-H`
- How to stream with grpcurl, stop a stream with `-max-time` and decode base64 `bytes` fields with jq
- How to work without reflection, with `-proto`, `-import-path`, `-protoset` and `-protoset-out`
- What common grpcurl errors and exit codes mean

## Prerequisites

- A terminal on macOS, Linux or Windows
- [jq](https://jqlang.org/) 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](https://dashboard.dwellir.com/register).

```bash
export DWELLIR_API_KEY=YOUR_API_KEY
```

The Sui examples work with any Dwellir API key. The Hyperliquid examples need the [Hyperliquid gRPC add-on](https://www.dwellir.com/docs/hyperliquid/grpc), 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.

## 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](https://github.com/fullstorydev/grpcurl/releases).

| System                          | Command                                                         | Version on November 3, 2026                                                                               |
| ------------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| macOS (Homebrew)                | `brew install grpcurl`                                          | 1.9.4 ([formula](https://formulae.brew.sh/formula/grpcurl))                                               |
| 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](https://snapcraft.io/grpcurl)                                                             |
| Windows (zip)                   | Download `grpcurl_1.9.4_windows_x86_64.zip`                     | 1.9.4                                                                                                     |
| Windows (Scoop)                 | `scoop install grpcurl`                                         | 1.9.4 ([manifest](https://github.com/ScoopInstaller/Main/blob/master/bucket/grpcurl.json))                |
| Windows (winget)                | `winget install fullstorydev.grpcurl`                           | 1.9.3 ([manifest](https://github.com/microsoft/winget-pkgs/tree/master/manifests/f/fullstorydev/grpcurl)) |
| 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](https://github.com/fullstorydev/grpcurl#installation) 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.

```bash
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 -version
```

```text
grpcurl v1.9.4
```

### Install 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.

```bash
grpcurl -expand-headers -H 'x-api-key: ${DWELLIR_API_KEY}' \
  api-sui-mainnet-full.n.dwellir.com:443 list
```

```text
grpc.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.ProofService
```

List the methods of one service:

```bash
grpcurl -expand-headers -H 'x-api-key: ${DWELLIR_API_KEY}' \
  api-sui-mainnet-full.n.dwellir.com:443 list sui.rpc.v2.LedgerService
```

```text
sui.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.ListTransactions
```

`list` 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:

```bash
grpcurl -expand-headers -H 'x-api-key: ${DWELLIR_API_KEY}' \
  api-hyperliquid-mainnet-grpc.n.dwellir.com:443 list
```

```text
grpc.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.MarketStreaming
```

`list hyperliquid_l1_gateway.v3.MarketStreaming` returns 14 typed streams, from `StreamActiveAssetCtx` to `StreamUserFills`. The [Hyperliquid gRPC overview](https://www.dwellir.com/docs/hyperliquid/grpc) 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:

```bash
grpcurl -expand-headers -H 'x-api-key: ${DWELLIR_API_KEY}' \
  api-sui-mainnet-full.n.dwellir.com:443 describe sui.rpc.v2.LedgerService.GetServiceInfo
```

```text
sui.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:

```bash
grpcurl -expand-headers -H 'x-api-key: ${DWELLIR_API_KEY}' -msg-template \
  api-sui-mainnet-full.n.dwellir.com:443 describe sui.rpc.v2.GetCheckpointRequest
```

```text
sui.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.

```bash
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
```

```json
{
  "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:

```bash
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
```

```json
{
  "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:

```bash
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'
{}
EOF
```

Piping 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.

```bash
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:

```text
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 response
```

We 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.

![grpcurl stream with -max-time 5 on Sui: 17 checkpoint frames in 5 seconds, then DeadlineExceeded and exit code 68 (64 + status code 4). Other exit codes: 67, 69 and 71 for gRPC statuses, 2 for usage errors, 1 for dial, reflection or JSON errors.](https://www.dwellir.com/posts/grpcurl/stream-lifecycle-max-time.webp)

### Stream Sui checkpoints

`SubscribeCheckpoints` pushes every new Sui checkpoint. The read mask keeps each frame to the sequence number:

```bash
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=$?"
```

```text
{
  "cursor": "341332852",
  "checkpoint": {
    "sequenceNumber": "341332852"
  }
}
{
  "cursor": "341332853",
  "checkpoint": {
    "sequenceNumber": "341332853"
  }
}
... 15 more frames ...
ERROR:
  Code: DeadlineExceeded
  Message: context deadline exceeded
exit=68
```

The 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:

```bash
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}'
```

```text
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](https://www.dwellir.com/docs/sui/grpc-setup-grpcurl) 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:

```bash
grpcurl -expand-headers -H 'x-api-key: ${DWELLIR_API_KEY}' \
  api-hyperliquid-mainnet-grpc.n.dwellir.com:443 describe hyperliquid_l1_gateway.v3.TradesRequest
```

```text
hyperliquid_l1_gateway.v3.TradesRequest is a message:
message TradesRequest {
  repeated string coins = 1;
  repeated string users = 2;
}
```

```bash
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
```

```json
{
  "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](https://www.dwellir.com/docs/hyperliquid/stream_trades) 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`:

```text
{
  "blockNumber": "1208044715",
  "timestamp": "1793740687002",
  "data": "eyJsb2NhbF90aW1lIjoiMjAyNi0xMS0wM1QyMToxODowNy40NDMwNzY2NjkiLCJibG9ja190aW1lIjo..."
}
```

jq's `@base64d` decodes it, and `fromjson` parses the result:

```bash
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}}'
```

```text
{"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](https://www.dwellir.com/docs/hyperliquid/stream_fills) and [ListFeeds](https://www.dwellir.com/docs/hyperliquid/list_feeds).

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](https://www.dwellir.com/docs/hyperliquid/troubleshooting) covers both. To see what the same feeds look like in a production pipeline, read how we [streamed the Hyperliquid mempool over gRPC](https://www.dwellir.com/blog/hyperliquid-mempool-grpc-stream) and [built a machine learning dataset from Hyperliquid gRPC](https://www.dwellir.com/blog/hyperliquid-machine-learning-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:

```text
Failed to list services: server does not support the reflection API
```

That output is from `grpcurl pubsub.googleapis.com:443 list`, a Google API that does not expose reflection.

![grpcurl gets a gRPC schema three ways: server reflection by default, local .proto files with -proto and -import-path, or a compiled protoset with -protoset; the last two work offline.](https://www.dwellir.com/posts/grpcurl/reflection-vs-proto-protoset.webp)

### 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:

```bash
curl -fsSLo v3.proto https://www.dwellir.com/docs/hyperliquid/grpc/v3.proto
grpcurl -proto v3.proto list
```

```text
hyperliquid_l1_gateway.v3.HyperliquidL1Gateway
hyperliquid_l1_gateway.v3.MarketStreaming
```

A call with `-proto` skips reflection and sends the RPC directly:

```bash
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/ListFeeds
```

If 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:

```bash
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 list
```

```text
sui.rpc.v2.LedgerService
```

The 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:

```json
{
  "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](https://github.com/fullstorydev/grpcurl)                                     | Command line                         | Reflection, `.proto`, protoset      | All four kinds, interactive bidirectional from a terminal                                          | v1.9.4, August 31, 2026     |
| [grpcui](https://github.com/fullstorydev/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](https://learning.postman.com/docs/sending-requests/grpc/first-grpc-request/) | Desktop and web app                  | Reflection, or an imported `.proto` | Unary, client, server and bidirectional streaming                                                  | Hosted app                  |
| [Evans](https://github.com/ktr0731/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](https://www.dwellir.com/docs/sui/grpc-setup-grpcurl) for Sui-specific calls: balances, package functions, checkpoint ranges and transaction execution. The [Sui gRPC overview](https://www.dwellir.com/docs/sui/grpc-overview) lists every service.
- Read the [Hyperliquid gRPC overview](https://www.dwellir.com/docs/hyperliquid/grpc) for positions, filters, progress frames and retention on the V3 API, and see [Hyperliquid gRPC streaming](https://www.dwellir.com/hyperliquid-grpc) for plans and pricing.
- Move from grpcurl to code with the [Dwellir gRPC code examples](https://github.com/dwellir-public/gRPC-code-examples) once your request bodies work.
