# Pipedream

**Wiring agent identity into a no-code workflow usually means a generic HTTP node, a hand-built JSON body, and a prayer that you got the escaping right.**

One unescaped quote in an agent label and your Cypher call breaks; one leaked bearer in a step export and it's in your run history forever. Whisper's Pipedream app removes all of that: a real app with typed props, a Cypher-literal builder that can't be broken by user input, and a response decoder that turns the control-plane envelope into a plain array of records - 43 actions in total: 12 identity/control actions plus egress-IP lookup and raw Cypher, split into a keyless tier anyone can drag onto a canvas and a keyed control tier that provisions and governs your fleet, and 29 more that put the whisper.security graph itself on the canvas (see [The graph actions](#the-graph-actions-29-more) below).

## Two tiers, one app

The `whisper` app follows the same Postel's-Law shape as every Whisper integration ([see the integration standard](/docs/integrations)): the credential is what unlocks the second tier, not a separate product.

- **Tier 1 - keyless.** Reads Whisper's public, anonymous identity API at `rdap.whisper.online`. No connected account. Same data anyone on the internet can query.
- **Tier 2 - control plane.** Connect a Whisper account (one `whisper_live_…` key) and the fleet-management actions unlock: register agents, allocate identities, read logs, set policy, wire up egress, revoke.

The app registers **one** optional auth field, `api_key`. Leave it blank and the five keyless actions still work; every keyed action checks for it up front and throws a `ConfigurationError` - a clear message, not a bare 401 three steps into a workflow - if it's missing.

## The identity & control actions

| # | Action | Tier | Op / endpoint |
|---|---|---|---|
| 1 | Verify Agent Identity | keyless | `GET /verify-identity?ip=` |
| 2 | Lookup RDAP Record | keyless | `GET /ip/<addr>` |
| 3 | Get Transparency Log | keyless | `GET /ip/<addr>/transparency` |
| 4 | Get Inbound Lookups | keyless | `GET /ip/<addr>/lookups` |
| 5 | Get Egress IP | keyless | `GET /egress-ip` |
| 6 | Register Agent | control | `op:register` |
| 7 | Allocate Identity | control | `op:identity` |
| 8 | List Agents | control | `op:list` |
| 9 | Get Agent | control | `op:agent` |
| 10 | Set Policy | control | `op:policy` |
| 11 | Get Logs | control | `op:logs` |
| 12 | Connect Egress | control | `op:connect` |
| 13 | Revoke Agent | control | `op:revoke` |
| 14 | Run Cypher | control | raw `whisper.agents`-style graph query |

All eight identity-mutating actions carry the same `admin:dns` (write) or read scope that a matching `whisper` CLI subcommand carries, and every one is confined to the caller's own tenant - there is no cross-tenant argument to smuggle through `args`.

## The graph actions (29 more)

The same app also ships one action per entry in the whisper.security graph catalog - the identical 14 direct procedures and 15 multi-step flows that back the [n8n Graph resource](/docs/n8n#beyond-agent-control-the-graph-resource) and the [MCP server](/docs/connect-mcp)'s recipe tools, generated from the same catalog so the action list can't drift from what the graph actually offers. `whisper-graph-assess`, `whisper-graph-identify`, `whisper-graph-variants`, `whisper-graph-typosquat`, `whisper-graph-subdomain-takeover`, `whisper-graph-bgp-hijack-exposure` and 23 others each drop onto the canvas as one typed action with the recipe's own input fields - no raw Cypher required unless you reach for the general-purpose **Run Cypher** action above it. These 29 are keyed the same way as the control tier: attach the `whisper` account and they run against the graph directly.

## Under the hood: one Cypher verb

Every control action is a thin wrapper over a single call: `CALL whisper.agents({op:'<op>', args:{…}})`, POSTed as `{"query": "..."}` to `https://graph.whisper.online/api/query` with the caller's key in `X-API-Key` - never in the body. This is the exact contract documented in the [Graph API reference](/docs/graph-api) and implemented once, correctly, in the `whisper` CLI's reference client.

**With stock tools** - build and send the same call by hand with `curl` and `jq`:

```bash
curl -s https://graph.whisper.online/api/query \
  -H "X-API-Key: whisper_live_…" \
  -H 'content-type: application/json' \
  -d '{"query":"CALL whisper.agents({op:'"'"'register'"'"', args:{label:'"'"'scout'"'"'}})"}' \
  | jq '.rows[0].result'
```

Get the string-escaping wrong - an unescaped `'` in a label like `O'Brien's bot` - and that quote closes the Cypher literal early and the query fails, or worse, is misparsed. Doing this safely by hand means writing your own doubled-quote escaper and a two-shape response decoder (the control endpoint can return either a procedure-row table or a flat `{ok,status,result,error}` envelope - see the reference for both shapes).

**With Whisper** - the app does the escaping and decoding for you; the action just takes typed props:

```text
Register Agent
  Name:            scout
  Contact Email:   (optional)
→ $summary: "Registered agent scout at 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4"
→ returns: { agent, address, fqdn, ptr, state, api_key }
```

That `api_key` field is the new agent's own credential and is returned **once** - capture it in the same run (write it to a connected secrets step, not to a public output).

The app's Cypher builder mirrors the CLI's exactly: a string value is single-quoted with `'` and `\` each doubled (`Tim O'Reilly` → `'Tim O''Reilly'`), map keys are emitted in **sorted order** so the wire query is byte-stable, and the response decoder accepts both envelope shapes plus a bare problem object - surfacing `detail` (or `title`, or `type`) verbatim on failure rather than an opaque 500. That's the Robustness Principle (RFC 761) applied to a no-code canvas: conservative in what the action emits on the wire, liberal in what it accepts back.

## A worked example: gate a Slack alert on agent identity

Say a workflow ingests inbound webhook traffic and you want to flag anything not coming from a genuine Whisper agent before it reaches a downstream step.

**With stock tools** (a Pipedream Node code step, no Whisper software):

```javascript
import { axios } from "@pipedream/platform";

export default defineComponent({
  async run({ steps, $ }) {
    const ip = steps.trigger.event.headers["x-forwarded-for"];
    const r = await axios($, {
      url: "https://rdap.whisper.online/verify-identity",
      params: { ip },
      headers: { Accept: "application/json" },
      validateStatus: () => true,
    });
    if (!r.is_whisper_agent) throw new Error(`${ip} is not a verified Whisper agent`);
    return r;
  },
});
```

**With Whisper** - drop in the **Verify Agent Identity** action (no code, no auth needed), then branch on its `is_whisper_agent` export directly in the workflow's conditional path - one action, typed output, no hand-rolled HTTP client.

## Connect Egress - the one action with secrets in it

`op:connect` is the odd one out: its result carries live transport secrets (an `et_…` bearer embedded in `http_proxy`/`connection_string` for the `socks5`/`anyip` tiers, or a `client_private_key` for a zero-key WireGuard setup). Because a hosted Pipedream step's output can end up in run history and downstream step data, the action follows the same bearer-hygiene rule as every other Whisper integration: don't wire the raw secret fields into a Slack message, a database row, or anything outside the same run's next HTTP step. If your workflow just needs a routed egress endpoint to hand to an HTTP-request node's proxy setting, that's exactly what this action is for - use it as a proxy config, not a value to persist.

## Installing it

The app is contributed as source against [`PipedreamHQ/pipedream`](https://github.com/PipedreamHQ/pipedream) under `components/whisper/`, submitted as an open pull request ([`#21299`](https://github.com/PipedreamHQ/pipedream/pull/21299)) rather than merged into the main repo yet - the same self-serve model as the Whisper `n8n`, [`Make`](/docs/make), and [`Power Platform`](/docs/powerplatform) connectors, just earlier in Pipedream's own review queue. Until it merges, the code is readable straight from the PR branch, with no vendor account required. Once it lands and goes live in the workspace picker, search "Whisper" when adding an app to a workflow; the keyless actions need nothing further, and the control and graph actions prompt you to connect an account with your `whisper_live_…` key.

> The key-gated DNS resolver (`/dns-query`) is intentionally not wrapped here - it's a stateful per-tenant `:53`/DoH endpoint, not a request/response action. Point your own resolver config at it directly; see [DoH](/docs/dns-over-https).

## Next

[Graph API - the `whisper.agents` control verb](/docs/graph-api) · [Integrations catalog](/docs/integrations)
