> ## 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](/docs/parsed-streams#the-mental-model) first — it explains why filters look the way they do.
</Tip>

## Quickstart

<Steps>
  <Step title="Get Access">
    Parsed Streams is in closed beta. The Helius team whitelists your project id and shares the connection endpoint with you. To join the closed beta, [apply here](https://form.typeform.com/to/BlFWKbC9).

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

  <Step title="Connect">
    ```bash wscat theme={"system"}
    wscat -c "wss://<ENDPOINT>/?api-key=YOUR_API_KEY"
    ```

    A missing, invalid, or non-whitelisted key is rejected with HTTP 401. A project at its connection cap gets HTTP 429.
  </Step>

  <Step title="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 }
    ```
  </Step>

  <Step title="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.
  </Step>

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

    Or just close the connection — it removes all its subscriptions.
  </Step>
</Steps>

## Guides

<CardGroup cols={2}>
  <Card title="Track Jupiter Swaps" icon="arrow-right-arrow-left" href="/docs/parsed-streams/guides/track-jupiter-swaps">
    Use `describeProgram` to build a filter you can trust before you subscribe.
  </Card>

  <Card title="Track Pump.fun Mints" icon="rocket" href="/docs/parsed-streams/guides/track-pumpfun-mints">
    A reconnect-safe listener that logs every new Pump.fun token deploy.
  </Card>

  <Card title="Handling Reconnects" icon="rotate" href="/docs/parsed-streams/guides/handling-reconnects">
    Survive idle timeouts and deploys, then backfill exactly what you missed.
  </Card>
</CardGroup>

## 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                                      |
| `ping`                         | Liveness check; returns the current slot                 |
| `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.

<ParamField body="programs" type="string[]">
  Program IDs to match (base58 addresses, not names). An instruction matches if its program is in this list. OR within the list.
</ParamField>

<ParamField body="instructionNames" type="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`.
</ParamField>

<ParamField body="accounts.include" type="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.
</ParamField>

<ParamField body="accounts.roles" type="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.
</ParamField>

<ParamField body="includeFailed" type="boolean" default="false">
  Include instructions from failed transactions.
</ParamField>

<ParamField body="includeCpi" type="boolean" default="true">
  Inner (CPI) instructions are eligible to match. Set `false` to match top-level instructions only.
</ParamField>

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.

<ParamField body="commitment" type="string" default="confirmed">
  Only `confirmed` is supported.
</ParamField>

<ParamField body="details" type="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.
</ParamField>

A project may hold up to **100 concurrent connections**, shared across all of its API keys.

### 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": [
          {
            "topIndex": 4,
            "innerIndex": 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"`.
* **`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: `topIndex` is which top-level instruction it belongs to (starting at 0), `innerIndex` 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": [
    { "topIndex": 4, "innerIndex": 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:

```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](/docs/parsed-streams/guides/track-jupiter-swaps) guide walks through this end to end.

### Limits

| Limit                              | Value                                             |
| ---------------------------------- | ------------------------------------------------- |
| Concurrent connections per project | 100                                               |
| 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](/docs/parsed-streams/guides/handling-reconnects) for what each one means and how to recover.

## Client Examples

<CodeGroup>
  ```bash wscat theme={"system"}
  wscat -c "wss://<ENDPOINT>/?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://<ENDPOINT>/?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://<ENDPOINT>/?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())
  ```
</CodeGroup>
