# Sources Listen Command Reference


> Complete Rudder CLI reference for sources listen and sources events: flags, output streams, the local query API, limits, and error codes.



This page is a detailed reference for the `rudder-cli sources` commands. `sources listen` runs a local server that accepts events the way RudderStack ingestion does and prints each accepted event on stdout. `sources events` reads the events a listener keeps in memory.

For a step-by-step walkthrough with examples, see [How to Verify App Events Locally](https://www.rudderstack.com/docs/rudder-cli/verify-events-locally/).

**Warning**

**Experimental feature**

The `sources` commands are experimental and disabled by default. You must [enable experimental mode and the `sourcesListen` flag](#enable-the-experimental-flag) before you run them. Experimental features are functional but their commands, flags, and output can change or be removed in future releases, so avoid relying on them in production workflows.

<!-- REVIEW(DOCS) — `sources listen` GA status is BLOCKED, left at alpha: true.

The Slack announcement listed `sources listen` as a v1.0.0 capability and the
reviewer asked for it to be marked GA. It cannot be, as of v1.0.0:

  - rudder-iac/cli/internal/config/experimental.go:32 defines
    `SourcesListen bool `mapstructure:"sourcesListen"`` — an experimental flag,
    off by default.
  - rudder-iac/cli/tests/command_sources_test.go:35 shows enabling it requires
    `rudder-cli experimental enable sourcesListen`.
  - verify-events-locally.md:18-22 states it "can change or be removed in future
    releases".

Documenting it as GA would claim general availability for a feature that ships
disabled by default, and "may be removed" contradicts the semantic-versioning
commitment now stated in installation.md.

To make it genuinely GA, rudder-iac must enable `sourcesListen` by default and
drop the experimental gate — a code change, out of scope for this docs PR.
Resolve by either (a) landing that rudder-iac change and then flipping these two
pages, or (b) keeping Alpha. Until then these pages stay alpha: true and the
release note does not mention `sources listen`.

For contrast: rETL table sources are also called "experimental" in a code
comment (rudder-iac/cli/internal/providers/retl/provider.go:87), but
`retl.WithTableSupport()` is wired unconditionally at
cli/internal/app/dependencies.go:268, so they are on by default and are
correctly marked GA here. The stale comment is a rudder-iac nit.
-->

## Overview

| Command | Use case |
| :--- | :--- |
| `rudder-cli sources listen` | Start a local server that answers RudderStack SDKs and prints each accepted event on stdout. |
| `rudder-cli sources events` | Read the events a listener started with `--store` keeps in memory. |

The listener needs no access token, no workspace, and no network. It does not deliver events to destinations, run transformations, or validate events against a Tracking Plan.

## Enable the experimental flag

The `sources` commands require **two** settings:

1. **Experimental mode** (`experimental`): The main switch for all experimental features.
2. **The `sourcesListen` flag**: Turns on the `sources` commands.

If either is disabled, every `sources` command exits with code `1` and the error code `experimental_disabled`. The message names both environment variables.

#### Config file

Edit your Rudder CLI configuration file (`~/.rudder/config.json`):

```json
{
  "experimental": true,
  "flags": {
    "sourcesListen": true
  }
}
```
#### CLI commands

Experimental mode must be on before the `experimental` subcommands run. Export `RUDDERSTACK_CLI_EXPERIMENTAL=true`, then enable the flag:

```shell
rudder-cli experimental enable sourcesListen
rudder-cli experimental list
```
#### Environment variables

Use environment variables in CI pipelines, agent shells, and other non-interactive shells:

```shell
export RUDDERSTACK_CLI_EXPERIMENTAL=true
export RUDDERSTACK_X_SOURCES_LISTEN=true
```

**Info:** `rudder-cli sources --help` prints the full user guide for these commands, and does not need the flag.

## Start a listener

### Command syntax

```bash
rudder-cli sources listen [flags]
```

### Parameters

| Parameter | Type | Description |
| :--- | :--- | :--- |
| `--port` | Integer | TCP port to listen on. `0`, the default, lets the operating system pick a free port, so parallel listeners never collide. A fixed port that is taken fails at start with `port_in_use`. |
| `--bind` | String | IP address to listen on. Default: `127.0.0.1`. Use `0.0.0.0` only inside a container. A non-loopback bind prints a warning on stderr. |
| `--write-key` | String | Accept only this write key and answer `401` to every other key and to a request without one on the ingestion routes. <br /><br />On `/sourceConfig`, a missing key gets `401` and a key that is not allowed gets `400`. <br /><br />Repeat the flag for more keys. Without it, the listener accepts any key, including a missing one. |
| `-s`, `--store` | Flag | Also keep every request in memory, and serve the [query API](#query-api) and the [review page](#review-page). |
| `--allow-host` | String | With `--store`, also accept this `Host` name on the query API, such as a container service name. Repeat the flag for more names. |
| `-h`, `--help` | Flag | Displays help information for the command. |

### Description

`sources listen` runs in the foreground until it receives `SIGINT` (Ctrl+C) or `SIGTERM`. It then finishes the requests in progress and exits with code `0`. To run it in the background, use your shell's `&` and stop it with `kill <pid>`.

The listener answers every route an SDK calls:

- The ingestion routes `/v1/track`, `/v1/identify`, `/v1/page`, `/v1/screen`, `/v1/group`, `/v1/alias`, `/v1/batch`, `/v1/merge`, `/v1/import`, `/v1/audiencelist`, `/beacon/v1/batch`, `/pixel/v1/track`, and `/pixel/v1/page`, with RudderStack's status codes and error messages.
- `/sourceConfig`, with a fixed source configuration that has no destinations, so browser and mobile SDKs finish loading.
- CORS preflights and the health routes.

### Output

The listener writes two streams:

| Stream | Content |
| :--- | :--- |
| stdout | Each accepted event as one JSON line, exactly as the SDK sent it, with whitespace removed. A batch of five events gives five lines. Nothing is added: no sequence number, no receive time, no `messageId` the SDK did not send. Configuration calls, preflights, health checks, and refused requests write nothing to stdout. |
| stderr | First, the ready line. Then one `refused: STATUS METHOD ROUTE: REASON` line for each refused ingestion request. A startup error is the last line, as one JSON object. |

The **ready line** is one JSON object that the listener prints once the port accepts connections:

```json
{"ready":true,"apiVersion":"v1","serverId":"225d1f4623d83972","url":"http://127.0.0.1:4321","port":4321,"bind":"127.0.0.1","pid":66751,"startedAt":"2026-10-14T17:14:57Z","writeKey":"dev","writeKeyPolicy":"any"}
```

| Field | Description |
| :--- | :--- |
| `url` | The base URL to give the SDK as its data plane URL (and config URL). |
| `port`, `bind`, `pid` | The port, the bound address, and the process ID to stop. |
| `serverId` | A random ID for this run. A restarted listener has a new one. |
| `writeKey`, `writeKeyPolicy` | `dev` and `any` by default. With `--write-key`, the first allowed key and `allowlist`. A key longer than 8 characters appears masked. |
| `cursor`, `ui` | Only with `--store`: the current position in the store, and the review page URL. |

A **refused line** looks like this:

```text
refused: 400 POST /v1/track: invalid json
refused: 401 POST /v1/batch: invalid write key
refused: 401 POST /v1/track: failed to read writekey from header
```

A pixel request answers `200` even when its event is dropped. It still prints a `refused:` line.

### Examples

- Print events as they arrive:

```bash
rudder-cli sources listen --port 4321
```

- Show only one event name:

```bash
rudder-cli sources listen --port 4321 | grep '"event":"Order Completed"'
```

- Run in the background, with the events and the log in files:

```bash
rudder-cli sources listen --port 0 > events.ndjson 2> listen.log &
```

- Accept only two write keys:

```bash
rudder-cli sources listen --port 4321 --write-key web-key --write-key api-key
```

- Keep requests in memory for `sources events` and the review page:

```bash
rudder-cli sources listen --port 4321 --store
```

## Read stored events

### Command syntax

```bash
rudder-cli sources events --url <listener-url> [flags]
```

### Parameters

| Parameter | Type | Description |
| :--- | :--- | :--- |
| `--url` | String | The listener URL from the ready line. Default: the `RUDDERSTACK_DEV_URL` environment variable. |
| `--since` | String | Read after this cursor, since a duration such as `5m`, or since an RFC 3339 time. Default: `0`. |
| `--event` | String | Only events with this name, or a prefix that ends in `*` such as `'Order*'`. Repeat the flag for more names. |
| `--type` | String | Only events of this type, such as `track` or `page`. Repeat the flag for more types. |
| `--user-id` | String | Only events with this `userId`. |
| `--anonymous-id` | String | Only events with this `anonymousId`. |
| `--write-key` | String | Only requests sent with this write key. `''` selects requests without one. Repeat the flag for more keys. |
| `--limit` | Integer | Print at most this many events, from 1 to 1000. Default: `100`. A request is never split across pages. |
| `--view` | String | `list` (a table of whole events, the default), `compact` (without the context the SDK collects on its own), or `full`. |
| `--fields` | String | Keep only this dotted path of each event, such as `properties`. Repeat the flag for more paths. Cannot be combined with `--view`. |
| `--server-id` | String | Fail with `server_changed` unless the listener has this `serverId`. |
| `--timeout` | Duration | Give up on the listener after this long. Default: `5s`. |
| `-j`, `--json` | Flag | Print NDJSON, one event per line, instead of a table. |
| `-h`, `--help` | Flag | Displays help information for the command. |

Different flags combine with AND. A repeated flag combines with OR.

### Description

`sources events` reads from a listener started with `--store`. Without `--store`, it fails with `store_disabled`. With `--json`, each line is an accepted event exactly as the SDK sent it, the same lines the listener prints on stdout. When more events are left than `--limit` allows, stderr prints `more events: continue with --since N`, and stdout stays pure.

### Examples

- The properties of one event:

```bash
rudder-cli sources events --url http://127.0.0.1:4321 --event 'Order Completed' --fields properties --json
```

- A table of what arrived in the last five minutes:

```bash
rudder-cli sources events --url http://127.0.0.1:4321 --since 5m
```

- Events without the auto-collected context:

```bash
rudder-cli sources events --url http://127.0.0.1:4321 --view compact --json
```

## Query API

A listener started with `--store` serves a read-only HTTP API on the same port, under `/_dev/v1/`. Use it from a test process in any language.

| Route | Description |
| :--- | :--- |
| `GET /_dev/v1/` | The index: every route with its parameters and an example URL. |
| `GET /_dev/v1/info` | The listener's identity, current cursor, and store statistics. Use it as a readiness probe. |
| `GET /_dev/v1/events` | The stored accepted events as NDJSON. It takes the same parameters as `sources events`: `since`, `event`, `type`, `userId`, `anonymousId`, `writeKey`, `limit`, `view`, `fields`, `serverId`. |
| `GET /_dev/v1/requests` | The request records after `since`: status, outcome, the reason a request was refused, six request headers, the raw body, and the response. <br /><br />Filter with `kind` (`ingestion` by default, `control`, or `all`), `statusCode` (such as `401` or `4xx`), `failed`, `writeKey`, `messageId`, and `serverId`. <br /><br />Shape the page with `limit` (0 to 1,000, default `100`), `view` (`list` by default, `compact`, or `full`), `fields`, and `maxBytes` (default 24,000 bytes; `0` turns the cap off). When `maxBytes` cuts the page, `truncated.next` is the command that reads the rest. |
| `GET /_dev/v1/requests/{seq}` | One request record. |
| `GET /_dev/v1/guide` | The user guide that `rudder-cli sources --help` prints. |
| `GET /_dev/v1/events?view=counts` | Counts of the requests and events after `since`, and a diagnosis of why events are missing: codes such as `nothing_received`, `filtered_empty`, `preflight_only`, `sdk_config_rejected`, `sdk_loaded_no_events`, and `all_accepted`, each with a `next` command. <br /><br />The counts follow the event filters. The diagnosis reads every request after `since`, so a filter that matches nothing shows as `filtered_empty`, not as `nothing_received`. <br /><br />Add `wait` (up to `110s`) and `min` to hold the answer until at least `min` matching events arrive. `limit` and `fields` do not apply. |

Every `GET` route also answers `HEAD`.

The query API has no authentication. It accepts only requests whose `Host` is `localhost`, an address in `127.0.0.0/8`, the bound IP, or a name passed with `--allow-host`. It sends no CORS headers and refuses cross-site browser requests, so a web page cannot read captured events. Call it from a shell or a test process, never from inside the page under test.

Records never keep `Authorization` or cookie headers. A write key longer than 8 characters appears masked.

## Review page

A listener started with `--store` serves a read-only web page at the `ui` URL from the ready line. It shows the captured events and requests, filters by event name and write key, searches payload values, and explains why events are missing. It loads nothing from the network.

## Limits and differences from RudderStack

A passing check is strong evidence that the app instruments events correctly, not a guarantee. The listener answers with RudderStack's status codes and messages, and adds limits of its own:

- A request body is at most 2,048,000 bytes, raw and decoded, and a batch has at most 10,000 events. Larger requests get `413`.
- At most 8 requests are read at once. More get `503`. A body that takes over 10 seconds to arrive is refused.
- With `--store`, the listener keeps the last 10,000 requests or 64 MiB, and drops the oldest whole requests first.
- It does not check the 32 KB per-event limit or the JSON depth limit.
- It does not deliver events, run transformations, or validate events against a Tracking Plan.
- If a reader stops reading stdout, the SDK's request waits until it reads again.

## Error codes

Errors print on stderr with a `Next:` command that fixes them. With `--json`, an error is one JSON object with `error.code` and `error.next`. Every command exits with code `0` on success and `1` on an error.

| Code | Cause | Fix |
| :--- | :--- | :--- |
| `experimental_disabled` | Experimental mode or the `sourcesListen` flag is off. | [Enable the experimental flag](#enable-the-experimental-flag). |
| `port_in_use` | The fixed `--port` is taken. | Use another port, or `--port 0`. |
| `store_disabled` | `sources events` or the query API reached a listener started without `--store`. | Restart the listener with `--store`, or read its stdout. |
| `server_unreachable` | No listener answers at `--url`, or something else answered there. | Check the `url` in the ready line. |
| `server_changed` | Another or a restarted listener answers at `--url`, and you passed `--server-id`. | Read the new ready line and repeat the action. |
| `usage` | A bad flag, value, or combination, such as `--limit 0` or `--fields` with `--view`. | Run the command with `--help`. |
| `host_not_allowed` | A query API request with a `Host` the listener does not accept. | Call it as `127.0.0.1:<port>` or `localhost:<port>`, or start the listener with `--allow-host <name>`. |
| `browser_origin` | A web page on another site called the query API. | Call the API from a shell or a test process. |
| `listen_failed` | The listener could not listen on the address, or stopped serving, for a reason other than a taken port. For example, the `--bind` IP is not on this machine. | Use a `--bind` address the machine owns, such as `127.0.0.1`. |

## See more

- [How to Verify App Events Locally](https://www.rudderstack.com/docs/rudder-cli/verify-events-locally/): The local loop, SDK setup, agents, and CI recipes.
- [Install Rudder CLI](https://www.rudderstack.com/docs/rudder-cli/installation/): Install the CLI and its Docker image.
- [Live Events](https://www.rudderstack.com/docs/monitor/live-events/): Check the events a workspace source receives after you deploy.

