> ## Documentation Index
> Fetch the complete documentation index at: https://www.helius.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Parsed Streams Quickstart

> Connect to Parsed Streams, send your first filter, and read a decoded notification. Plus the full JSON-RPC 2.0 protocol reference.

> [!TIP]
>
> New to Parsed Streams? Read [the mental model](https://www.helius.dev/docs/parsed-streams.md#the-mental-model) first — it explains why filters look the way they do.

## Quickstart

**1. Get Access**

Parsed Streams is available on all plans at 1 credit per delivered event. Get your API key from the [Helius Dashboard](https://dashboard.helius.dev) and connect to the [Gatekeeper](https://www.helius.dev/docs/gatekeeper/overview.md) endpoint at `wss://beta.helius-rpc.com`, the same host as Helius RPC and WebSocket traffic.

Authenticate with your project's API key, passed as the `api-key` query parameter (or the `x-api-key` header).

**2. Connect**

```bash wscat theme={"system"}
wscat -c "wss://beta.helius-rpc.com/?api-key=YOUR_API_KEY"
```

A missing or invalid key is rejected with HTTP 401. A project at its connection cap gets HTTP 429.

**3. Subscribe with a Filter**

Send `parsedTransactionSubscribe` with a filter and optional options:

```json theme={"system"}
{"jsonrpc":"2.0","id":1,"method":"parsedTransactionSubscribe","params":[{"programs":["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"]}]}
```

The response `result` is an integer **subscription id**:

```json theme={"system"}
{ "jsonrpc": "2.0", "id": 1, "result": 23 }
```

**4. Read a Notification**

Every matching transaction arrives as a `parsedTransactionNotification`, already decoded, with `matchedIndexes` pointing at the instructions your filter hit. See [Notifications](#notifications) for the full shape.

**5. Unsubscribe**

```json theme={"system"}
{ "jsonrpc": "2.0", "id": 2, "method": "parsedTransactionUnsubscribe", "params": [23] }
```

Or just close the connection — it removes all its subscriptions.

## Guides

> [card] [Track Jupiter Swaps](https://www.helius.dev/docs/parsed-streams/guides/track-jupiter-swaps.md)
>
> Use `describeProgram` to build a filter you can trust before you subscribe.

> [card] [Track Pump.fun Mints](https://www.helius.dev/docs/parsed-streams/guides/track-pumpfun-mints.md)
>
> A reconnect-safe listener that logs every new Pump.fun token deploy.

> [card] [Handling Reconnects](https://www.helius.dev/docs/parsed-streams/guides/handling-reconnects.md)
>
> Survive idle timeouts and deploys, then backfill exactly what you missed.

## Protocol Reference

Parsed Streams uses **JSON-RPC 2.0** over a single WebSocket connection. Each request receives a response with the same `id`. A subscription then pushes `parsedTransactionNotification` messages until you unsubscribe or disconnect.

| Method | Purpose |
| - | - |
| `parsedTransactionSubscribe` | Start a subscription with a filter |
| `parsedTransactionUnsubscribe` | Stop a subscription |
| `describeProgram` | List a program's instructions, events, and account roles |

### Subscribe

Send `parsedTransactionSubscribe` with a filter and optional options. The response `result` is an integer **subscription id**.

```json Request theme={"system"}
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "parsedTransactionSubscribe",
  "params": [
    {
      "programs": ["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"],
      "instructionNames": ["route", "shared_accounts_route"],
      "accounts": {
        "include": ["So11111111111111111111111111111111111111112"],
        "roles": { "user_transfer_authority": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin" }
      },
      "includeFailed": false,
      "includeCpi": true
    },
    { "commitment": "confirmed", "details": "full" }
  ]
}
```

```json Response theme={"system"}
{ "jsonrpc": "2.0", "id": 1, "result": 23 }
```

#### Filter fields

At least one of `programs` or `accounts.include` is required. The fields you set combine with **AND**: an instruction must satisfy all of them to match.

**programs** `string[]`

Program IDs to match (base58 addresses, not names). An instruction matches if its program is in this list. OR within the list.

**instructionNames** `string[]`

Decoded instruction names, such as `route`. Matched exactly first, then with a case and separator insensitive fallback, so `sharedAccountsRoute` also matches the wire name `shared_accounts_route`. OR within the list. Only instructions whose name the catalog could identify can match, so take names from `describeProgram`.

**accounts.include** `string[]`

Account addresses. An instruction matches if any of these appears in its account list. OR within the list. Works for every instruction, decoded or not. The program id itself does not count as an account here.

**accounts.roles** `object`

A map of decoded account role name to address, such as `{ "user_transfer_authority": "<pubkey>" }`. Every entry must hold (AND across entries), and the instruction must be decoded for this to apply. Role names match **exactly**, with no case folding, so copy them from `describeProgram` rather than guessing.

**includeFailed** `boolean` (default: false)

Include instructions from failed transactions.

**includeCpi** `boolean` (default: true)

Inner (CPI) instructions are eligible to match. Set `false` to match top-level instructions only.

Unknown fields anywhere in the filter or options are rejected with `-32602` rather than silently ignored, so typos fail loudly instead of matching nothing.

#### Options

The second param is optional.

**commitment** `string` (default: confirmed)

Only `confirmed` is supported.

**details** `string` (default: full)

What each notification carries. `full`: the whole transaction, every instruction, plus `matchedIndexes` pointing at the filter hits. `matched`: only the instructions that matched, no index list. `raw`: matched instructions only, each reduced to its position, `programId`, and base58 `data` blob, with no decoded fields and no `accountKeys` array. Use `matched` when bandwidth matters more than context (full payloads average roughly three times the size), and `raw` when you decode instruction data yourself and only need the bytes.

Concurrent connections per project depend on your plan: **5** on Free, **10** on Developer, and **50** on Business and Professional, shared across all of the project's API keys. See [Rate Limits](https://www.helius.dev/docs/billing/rate-limits.md#parsed-streams).

### Notifications

One notification per matching transaction per subscription. With the default `details: "full"`:

```json theme={"system"}
{
  "jsonrpc": "2.0",
  "method": "parsedTransactionNotification",
  "params": {
    "subscription": 23,
    "result": {
      "context": { "slot": 430172053 },
      "value": {
        "transaction": {
          "signature": "3riSYL4HTRxgQjLayt6L2JPaDR3oaEQg1H4v3fnjUxNU...",
          "slot": 430172053,
          "blockTime": null,
          "feePayer": "6jduWNCTQzG91JGBchfGGxd55Vi5FxJCCJEV18RkXzJX",
          "fee": 5000,
          "accountKeys": ["6jduWNCT...", "..."],
          "status": "ok",
          "error": null,
          "summary": {
            "type": "swap",
            "description": "6jduWNCTQzG91JGBchfGGxd55Vi5FxJCCJEV18RkXzJX swapped 0.001 SOL for 0.183985 EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v via Jupiter",
            "parsedData": {
              "type": "swap",
              "protocol": "jupiter",
              "kind": "swap",
              "in_amount": "1000000",
              "actual_out_amount": "183985",
              "input_mint": "So11111111111111111111111111111111111111112",
              "output_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
            }
          },
          "nativeTransfers": [
            { "fromUserAccount": "6jduWNCT...", "toUserAccount": "DfXygSm4...", "amount": 1000000 }
          ],
          "tokenTransfers": [
            {
              "fromUserAccount": "6jduWNCT...",
              "toUserAccount": "AeUfFU6L...",
              "fromTokenAccount": "HLaEoW1s...",
              "toTokenAccount": "G13P9kSY...",
              "rawTokenAmount": 183985,
              "decimals": 6,
              "tokenStandard": "Fungible",
              "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
            }
          ]
        },
        "instructions": [
          {
            "instructionIndex": 4,
            "innerInstructionIndex": null,
            "stackHeight": 1,
            "programId": "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4",
            "programName": "jupiter",
            "instructionName": "route",
            "summary": {
              "type": "swap",
              "description": "6jduWNCTQzG91JGBchfGGxd55Vi5FxJCCJEV18RkXzJX swapped 0.001 SOL for 0.183985 EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v via Jupiter",
              "parsedData": {
                "type": "swap",
                "protocol": "jupiter",
                "kind": "swap",
                "in_amount": "1000000",
                "actual_out_amount": "183985",
                "input_mint": "So11111111111111111111111111111111111111112",
                "output_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
              }
            },
            "decoded": {
              "args": { "in_amount": "1000000", "slippage_bps": 50 },
              "accounts": [
                { "name": "user_transfer_authority", "pubkey": "9xQe...", "isSigner": true, "isWritable": false }
              ]
            }
          }
        ],
        "matchedIndexes": [8, 13]
      }
    }
  }
}
```

Reading it:

* **`transaction`** is the full context. `fee` is in lamports. `accountKeys` is the complete key list, including keys loaded from address lookup tables, in the same order the chain reports them. `feePayer` is always `accountKeys[0]`. `error` carries the transaction error as structured JSON, for example `{"InstructionError": [2, {"Custom": 6001}]}`, when `status` is `"error"`.
* **`summary`** has one shape everywhere it appears: a `type` (such as `swap` or `transfer`), a human-readable `description`, and a structured `parsedData` payload when the parser recognizes the action — for a swap: the protocol, amounts, and mints. `transaction.summary` labels the transaction's headline action; each recognized instruction carries its own `summary` with the same shape. To collect every swap in a transaction, iterate `instructions` and read `summary.parsedData` where `summary.type` is `"swap"`.
* **Swap amounts are venue-reported.** `in_amount`, `actual_out_amount`, and inner swap amounts in `parsedData` are what the venue reported, not what the user's wallet actually sent or received. They do not account for Token-2022 transfer fees or other deductions applied outside the swap. For the exact amount received, compare the user's token account balances before and after the transaction.
* **`nativeTransfers`** and **`tokenTransfers`** list the SOL and token movements the parser extracted from the whole transaction, in the same shape the Parsed Events API returns, so stream and API consumers can share processing code. Both are always present, possibly empty.
* **`instructions`** is every instruction of the transaction in execution order: each top-level instruction followed by its inner instructions. Each entry carries its own position: `instructionIndex` is which top-level instruction it belongs to (starting at 0), `innerInstructionIndex` is its position among that instruction's inner calls (`null` means it is the top-level instruction itself), and `stackHeight` is the call depth (1 for top level). Use these, not the array position.
* **`matchedIndexes`** are indices into `instructions` telling you which ones your filter actually hit. The rest are there for context. With `details: "matched"` the array contains only the hits and `matchedIndexes` is absent.
* **`decoded` names are snake\_case** (`in_amount`, `user_transfer_authority`), as published in the program's IDL. Integer arguments are commonly strings (`"1000000"`) because u64 values do not fit in JavaScript numbers.
* **`blockTime`** is currently always `null`. Do not build on it.
* Expect a **mix of decoded and undecoded instructions** inside one transaction: a fully decoded swap can sit next to an unrecognized memo. Branch on `decoded`: when it is `null`, the instruction carries `rawData` (base58 bytes) and `rawAccounts` (plain pubkey list) instead, so you always have something to work with.

With `details: "raw"` the `value` shrinks to transaction meta and blobs. `accountKeys`, `nativeTransfers`, `tokenTransfers`, `matchedIndexes`, and all decoded fields are gone (the transaction `summary` is still included); each matched instruction is its position, its program, and its `data` bytes in base58, exactly as they appear on chain (present even for instructions the catalog could have decoded):

```json theme={"system"}
"value": {
  "transaction": {
    "signature": "3riSYL4HTRxgQjLayt6L2JPaDR3oaEQg1H4v3fnjUxNU...",
    "slot": 430172053,
    "blockTime": null,
    "feePayer": "6jduWNCTQzG91JGBchfGGxd55Vi5FxJCCJEV18RkXzJX",
    "fee": 5000,
    "status": "ok",
    "error": null,
    "summary": {
      "type": "swap",
      "description": "6jduWNCTQzG91JGBchfGGxd55Vi5FxJCCJEV18RkXzJX swapped 0.001 SOL for 0.183985 EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v via Jupiter",
      "parsedData": {
        "type": "swap",
        "protocol": "jupiter",
        "kind": "swap",
        "in_amount": "1000000",
        "actual_out_amount": "183985",
        "input_mint": "So11111111111111111111111111111111111111112",
        "output_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
      }
    }
  },
  "instructions": [
    { "instructionIndex": 4, "innerInstructionIndex": null, "stackHeight": 1, "programId": "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4", "data": "3Bxs4h24hBtQy9rw" }
  ]
}
```

### Unsubscribe

```json theme={"system"}
{ "jsonrpc": "2.0", "id": 2, "method": "parsedTransactionUnsubscribe", "params": [23] }
```

Returns `true` if the subscription existed and was yours. Notifications stop immediately. Closing the connection removes all its subscriptions.

### Discovery

The most common failure with this kind of API is a filter that is valid but matches nothing, usually a guessed instruction or role name. `describeProgram` prevents that by returning the exact names the matcher compares against. It is currently only available on `wss://fs-beta.helius-rpc.com/?api-key=<API_KEY>`, so send it over a separate connection from your subscriptions:

```json Request theme={"system"}
{ "jsonrpc": "2.0", "id": 1, "method": "describeProgram", "params": [{ "program": "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4" }] }
```

```json Response theme={"system"}
{
  "jsonrpc": "2.0", "id": 1,
  "result": {
    "id": "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4",
    "name": "jupiter",
    "instructions": ["route", "shared_accounts_route", "exact_out_route"],
    "events": ["SwapEvent"],
    "roles": ["user_transfer_authority", "destination_token_account"]
  }
}
```

You can pass a program address or a catalog name, but **prefer the address**: names can be ambiguous across program versions (more than one catalog entry is named `jupiter`, and a name lookup can resolve to the older one). If you do look up by name, check that `result.id` is the program you intend to subscribe to.

Recommended flow: `describeProgram` to get the exact instruction and role names, build the filter with those names, then subscribe. The [Track Jupiter Swaps](https://www.helius.dev/docs/parsed-streams/guides/track-jupiter-swaps.md) guide walks through this end to end.

### Limits

| Limit | Value |
| - | - |
| Concurrent connections per project | 5 (Free), 10 (Developer), 50 (Business and Professional) |
| Subscriptions per connection | 25 |
| Client messages | 10 per second, burst of 20 |
| Client message size | 64 KiB |
| `programs` per filter | 10 |
| `instructionNames` per filter | 50, each up to 64 chars |
| `accounts.include` per filter | 100 |
| `accounts.roles` per filter | 20, each name up to 64 chars |
| Outbound buffer per connection | 2048 notifications, then the connection is closed |

### Errors

Errors follow JSON-RPC 2.0: `{ "error": { "code": <int>, "message": "<text>" }, "id": <id> }`. Messages say exactly what was wrong and where.

| Code | Meaning |
| - | - |
| `-32700` | Parse error (invalid JSON) |
| `-32600` | Invalid request |
| `-32601` | Method not found |
| `-32602` | Invalid params: bad pubkey, unknown field, unsupported commitment or details value |
| `-32000` | Filter limit exceeded |
| `-32001` | Server not ready; retry with backoff |
| `-32002` | Rate limited (10 messages per second) |
| `-32006` | Too many subscriptions (25 per connection) |

Connections can also close with a WebSocket close code — see [Handling Reconnects](https://www.helius.dev/docs/parsed-streams/guides/handling-reconnects.md) for what each one means and how to recover.

## Client Examples

```bash wscat theme={"system"}
wscat -c "wss://beta.helius-rpc.com/?api-key=<API_KEY>"
# then send:
{"jsonrpc":"2.0","id":1,"method":"parsedTransactionSubscribe","params":[{"programs":["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"]}]}
```

```typescript TypeScript theme={"system"}
import WebSocket from "ws";

const ws = new WebSocket("wss://beta.helius-rpc.com/?api-key=<API_KEY>");

ws.on("open", () => {
  ws.send(JSON.stringify({
    jsonrpc: "2.0",
    id: 1,
    method: "parsedTransactionSubscribe",
    params: [{ programs: ["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"] }],
  }));
});

ws.on("message", (data) => {
  const msg = JSON.parse(data.toString());
  if (msg.method === "parsedTransactionNotification") {
    const { transaction, instructions, matchedIndexes } = msg.params.result.value;
    for (const i of matchedIndexes ?? instructions.keys()) {
      const ix = instructions[i];
      console.log(transaction.signature, ix.programName, ix.instructionName, ix.decoded?.args);
    }
  }
});
```

```python Python theme={"system"}
import asyncio, json, websockets

URL = "wss://beta.helius-rpc.com/?api-key=<API_KEY>"

async def main():
    async with websockets.connect(URL) as ws:
        await ws.send(json.dumps({
            "jsonrpc": "2.0", "id": 1, "method": "parsedTransactionSubscribe",
            "params": [{"programs": ["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"]}],
        }))
        async for raw in ws:
            msg = json.loads(raw)
            if msg.get("method") == "parsedTransactionNotification":
                value = msg["params"]["result"]["value"]
                for i in value.get("matchedIndexes") or range(len(value["instructions"])):
                    ix = value["instructions"][i]
                    print(ix.get("programName"), ix.get("instructionName"), (ix.get("decoded") or {}).get("args"))

asyncio.run(main())
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.