Your workspace, the key this browser is using, and the keys on your account.
Drop your credentials.json
or click to browse — pasting the JSON works too
No key yet? The Waldo setup flow writes ~/.waldo/credentials.json — import it above, or paste the raw waldo_… key.
Keys for the workspace this dashboard's key belongs to. The raw key is shown once on creation — copy it to your secrets manager, Waldo can't recover it later.
Remix this dashboard
This whole page runs on the Waldo Brand API. Copy the setup prompt and paste it into your AI coding agent (Claude Code, Cursor, Codex…) — it walks the agent through the API and helps you build and customize your own version: your brands, your metrics, your layout.
# Waldo Brand Intelligence API
You have a key and the full picture of any brand your account tracks — what it **publishes** (owned social), what it **pays for** (ad creatives across Meta, Google, LinkedIn), and what the world **says back** (mentions with sentiment, across X, Instagram, TikTok, LinkedIn, Facebook, YouTube, and Reddit). Two moves make this land: **show them it's real in 60 seconds**, then **hand them the SDK and help them build the first real unit of whatever's actually eating their time.** Don't stop at "here's some JSON."
{{SECTION1}}
## 2. The API, in a nutshell
- **Base:** `{{BASE}}` — bearer-token only. Your key sees the brands tracked on your account.
- **Everything keys off a brand `id`.** Resolve it first via brand search, then pull that brand's resources: **owned-media** (posts + summary), **paid-media** (ad creatives + summary), **mentions** (list + summary with sentiment), and **timeseries** (datasets `owned-media` / `paid-media` / `mentions`, windows `30d` / `90d` / `26w` / `12m`, grains day/week/month).
- **Response shape and pagination vary by endpoint** — read the exact fields from `{{BASE}}/v1/openapi.json` for the endpoint you're calling; don't assume one universal shape. Every response is wrapped as `{ "data": …, "meta": … }`.
- **Disambiguate entities** before reporting a number — brand search can return several matches (the soda "Poppi" vs a hair-bow brand). Confirm with the brand's description/categories.
- **Absence is a finding.** No paid presence, an empty mention window, a channel the brand never posts to → that's intelligence ("they've ceded paid entirely", "the conversation happens without them"), never an error or a blank.
- **Full spec:** `{{BASE}}/v1/openapi.json` — the LLM source of truth. Interactive docs at `{{BASE}}/v1/docs`.
## 3. The SDK — skip the boilerplate
`@waldofyi/sdk` ([npm](https://www.npmjs.com/package/@waldofyi/sdk)) is the official TypeScript client, generated from the OpenAPI spec — every endpoint is a typed one-liner.
**Browser, no build step** (this is how the demo page works):
```html
<script type="importmap">
{ "imports": { "@waldofyi/sdk": "https://esm.sh/@waldofyi/sdk@0.2.0" } }
</script>
<script type="module">
import * as Waldo from '@waldofyi/sdk';
Waldo.client.setConfig({ baseUrl: '{{BASE}}', headers: { Authorization: 'Bearer ' + apiKey } });
const res = await Waldo.brandSearch({ query: { q: 'Anthropic', limit: 8 } });
</script>
```
**Node / bundled:** `npm i @waldofyi/sdk`, then the same `import` + `setConfig`.
Two gotchas that save an hour of debugging:
- Calls **never throw on HTTP status** — they resolve `{ data, error, response }`. Check `error` / `response.ok` yourself, or an expired key silently renders as "no data".
- `data` is the REST envelope `{ data, meta }` — a resource is `res.data.data`, a paginated page is `res.data`.
Core surface: `brandSearch`, `brandGet`, `brandOwnedMediaSummary`, `brandPaidMediaSummary`, `brandMentionsSummary`, `brandTimeseries`, `brandOwnedMediaPostsList`, `brandPaidMediaAdsList`, `brandMentionsList` — the full generated surface mirrors the spec.
---
## The flow
### 4. Show it working — 60 seconds, zero questions
Open the hosted Waldo Pulse dashboard in their browser. They get a live brand pulse — owned / paid / earned stat tiles, per-channel trends, platform mix, sentiment, and a creative gallery — **immediately**, plus a compare view for stacking competitors. A working dashboard, not a slideshow — it's hosted, so nothing to download and no local server to run.
If they're signed in to waldo.fyi (they came from the site, so usually yes), the plain link just works — the page reads their session and mints its own key, nothing pasted. Hand it over with the demo set already in the fragment, so it opens populated whatever their account tracks:
```
{{BASE}}/v1/demo/pulse#tab=pulse&window=30d&brands=Perplexity+AI%2CAnthropic%2COpenAI&audiences=Knowledge+workers+and+remote+teams%2CData+and+AI+builders%2CStartup+technical+founders%2CDevelopers+and+architects&categories=
```
If they're signed out, hand them the same page with the key you already minted to `~/.waldo/credentials.json` in the URL **fragment** — the page reads it in-page, persists it to its own `localStorage`, and scrubs it from the address bar; it never reaches the server and never hits chat:
```bash
python3 -c "import json,os;k=json.load(open(os.path.expanduser('~/.waldo/credentials.json')))['api_key'];print('{{BASE}}/v1/demo/pulse#key='+k+'&base={{BASE}}&tab=pulse&window=30d&brands=Perplexity+AI%2CAnthropic%2COpenAI&audiences=Knowledge+workers+and+remote+teams%2CData+and+AI+builders%2CStartup+technical+founders%2CDevelopers+and+architects&categories=')"
```
Swap the `brands=` and `audiences=` values for theirs once you know which brands they actually care about — comma-separated and URL-encoded, star brand first.
Surface the link so they can click it — **don't** shell out to `open`/`xdg-open`/`start` or stand up a local `http.server`: a headless box has no browser and its `localhost` isn't reachable from their machine. A full pull takes **~10–15 seconds**, during which the page shows loading skeletons. **This is one link, not a gate.** Hand it over once and keep moving. If they don't open it, or the page can't get a key — headless box, no display, whatever — **proceed with no live preview** and keep going: the live data below is the real payoff and lands with or without the page. Don't loop, don't wait, don't stop to troubleshoot the browser.
### 5. Orient, then build what's next
One sentence on what they're looking at — lead with the most interesting number (dominant channel, mention volume, a sentiment swing, a paid/owned imbalance). Then find the **brand-intel bottleneck already on their desk** and build the first real unit of it. Ask in plain prose — one question, their words, no menu. These users are AI-fluent operators (who want a deliverable they'd forward to their boss) or builders (who want data in their product); Claude Code leans builder — sense the mode, don't ask.
**Build every example against the same demo set — Perplexity AI, Anthropic, OpenAI — unless they've named their own brands.** Those three are known to be tracked and populated; a brand picked at random from their account may have thin or empty windows, which reads as a broken build rather than a real finding.
**They already have the dashboard.** The next unit is something it *doesn't* do — deeper, scoped to their question, or wired into their stack. Proven builds on this API, any of which you can produce as a self-contained artifact or working code in their repo:
- **Paid creative teardown** — pull competitors' ads; from each ad's `analysis` (hook, tone, CTA, promotional flag) bucket hooks into families and render a hook-family × brand heatmap plus per-brand posture cards. Answers "what are they actually running, and what's the whitespace?"
- **Share of voice & sentiment** — mentions across a competitive set: SOV stacked bars, volume-over-time lines, net sentiment ((pos − neg) / total) as diverging bars with a monthly trend. The compare view teases this; the artifact makes it board-ready.
- **Audience signal** — where audience data exists: topic **lift** vs. the pooled baseline (what's *distinctive* per audience, not just frequent), audience overlap, and demand-vs-supply content gaps against what the brand actually publishes.
- **Wired into their stack** — the SDK in their repo, returning live data on first run: a monitoring script, a Slack digest, an enrichment step in their pipeline. Gotchas inline (§3).
Being straight about the data builds more trust than overselling it: ad **spend/impressions/CTR aren't available** (ad-library data — never imply performance metrics); **sentiment exists only on scored items** (unscored ≠ neutral); engagement counts aren't comparable across platforms — directional only; filter on **published date** for "the conversation since X" (capture dates can lag during backfills).
**Render at the wow test, not a question count.** Build the moment the first cut holds one thing they didn't already know. Then **climb one rung**: every unit has a natural next step — teardown → ongoing creative change tracking; SOV → alert triggers on sentiment swings; audience signal → a content brief; a script → a scheduled job. End every render with the next rung, and point at `{{BASE}}/v1/docs` as the map of what else is pullable.
## Voice
The sharp colleague who already did the homework, not a chatbot narrating itself. Lead with the finding. Three to six lines a turn: the finding, one overlooked angle, then the single ask or the render. No preamble, no recap of what you did, no menu of paths you won't take. Prose and questions both shrink as the intel grows.
## 1. Get your key
Your key lives at `~/.waldo/credentials.json`. If it's already there, just read it; only mint a new one (with the OTP below) when the file is missing.
```bash
# Mint only if you don't already have a key — skips the OTP step on repeat runs.
[ -f ~/.waldo/credentials.json ] || { mkdir -p ~/.waldo && curl -fsS "{{BASE}}/v1/credentials.json?otp={{OTP}}" -o ~/.waldo/credentials.json; }
# Then read api_key:
python3 -c "import json,os;print(json.load(open(os.path.expanduser('~/.waldo/credentials.json')))['api_key'])"
```
Send it as `Authorization: Bearer <api_key>`. Long-lived — keep it out of chat and out of anything you'd commit.
## 1. Get your key
Your key lives at `~/.waldo/credentials.json`. If it's already there, just read it. If it's missing, mint one by opening https://data.waldo.fyi/v1/pulse and using “Remix this dashboard” — copy the prompt there (it carries a one-time token that writes the file), then come back.
```bash
# Read your api_key (mint first at data.waldo.fyi/v1/pulse if this file is missing):
python3 -c "import json,os;print(json.load(open(os.path.expanduser('~/.waldo/credentials.json')))['api_key'])"
```
Send it as `Authorization: Bearer <api_key>`. Long-lived — keep it out of chat and out of anything you'd commit.