## Summary
Partners can now mint their own embed key: **Settings → Embeds → Create
Embed Key**, on any paid plan. Until this, `createEmbedKey` existed with
no interface, so the docs had to say "ask us for one" and the paid tier
was effectively unreachable.
It also finishes the credential migration. `chokepoint-strip` and
`fear-greed` now accept `wme_` keys, so a partner running a keyed panel
finally has something safe to publish instead of the `wm_` or enterprise
key the docs used to recommend.
Stacked on #7706.
## The brief for this was wrong, and the fix is wider
Accepting `wme_` in `evaluateEmbedEntitlement` alone does not make those
panels work. They do not read their data through the entitlement
endpoint — they call `get-chokepoint-status` and `get-fear-greed-index`
through `server/gateway.ts` with the same credential, where `wme_` fails
every shape test. The handshake would answer 200 and the panel would
still 401.
So the gateway accepts `wme_` too, and the containment is worth stating
precisely:
- Only on RPC paths a paid panel **declares in the registry**.
`EMBED_KEY_RPC_PATHS` is derived from those declarations, so the surface
cannot widen without a panel owning the path.
- **Not an escalation.** Neither declared path is tier-gated or in
`PREMIUM_RPC_PATHS`, so both already answer an anonymous `wms_` session.
This reaches no new data; it gives a frame with no session a credential
shape the gateway understands, replacing the over-powered key it is
migrating off.
- `isUserApiKey` stays false, so the credential never enters the
per-account REST meter, never hits the `apiAccess` gate, and never
becomes the rate-limit principal — an embed is read by many viewers on
many IPs, so the per-IP bucket is the correct one.
- Same fail-closed IP guard as the `wm_` branch against rotating-key
lookup amplification, and a transient billing failure returns a
retryable 503 rather than collapsing into "invalid key".
This also fixes an inconsistency #7705 introduced: moving the
entitlement endpoint onto `hasEmbedAccess` admitted Pro accounts, but
the gateway still required `apiAccess` for the `wm_` key those panels
carry, so a Pro partner got 200 from the handshake and 403 from the data
read.
## Design decisions
**A separate Settings tab, not a section of API Keys.** That tab is
gated on `apiAccess`, which is false for both Pro tiers, so embed keys
placed there would be invisible to the customers this feature exists
for. The Connected MCP Clients tab already solves the same split for the
same reason. Keeping them apart also matters because they are different
kinds of secret: one is *meant* to be published in page HTML, the other
carries your REST allowance.
**`wm_` and enterprise keys keep working, with no removal date.**
Partners are running them right now. The response marks them deprecated
and the frame logs a migration warning naming the risk, but nothing
breaks. Retiring the path would need telemetry that does not exist — the
gateway emits these as `anon` today, so nobody can count who would
break.
**The dialog offers the keyed snippet without gating the free one.** A
keyless snippet is now the free tier, so it stays available to everyone
including signed-out visitors. Users with embed access additionally get
the loader form; users with the entitlement but no key yet are routed to
the new tab rather than dead-ending.
**Revocation states both windows.** New reads with a revoked key are
denied within about 60 seconds. An already rendered one-shot paid panel
remains visible until reload, while a live map frame that already holds
a grant can keep its paid tier for up to 30 more minutes before falling
back to the free tier. The earlier copy promised only the shorter
number, which is exactly wrong for the person most likely to read it —
someone revoking a key they believe has leaked.
## Deviation
The dialog emits a placeholder rather than the user's actual key. It
cannot do otherwise: plaintext is shown once at mint and `listEmbedKeys`
returns only a 9-character display prefix, by design.
## Validation
`npm run test:convex` 92 files / 1,778 tests, `npm run test:dom` 108
files / 861 tests, focused embed and entitlement suites 161 tests, and
`npm run typecheck:all` all pass. The full `npm run test:data` run
passed 30,671 of 30,689 tests, skipped 17, and had one unrelated 40 ms
timing failure in `five-factor-scorecard-seed`; its isolated rerun
passed all 26 tests.
The entitlement crosswalk is green with 333 gates, zero unmapped gates,
and zero count drift. Architecture boundaries and the 44-policy
rate-limit audit also pass. The local
`/embed.html?panel=map&layers=conflicts,earthquakes,weather` runtime
mounts the attributed keyless map without a credential error.
## Post-Deploy Monitoring & Validation
For 24 hours after deployment, monitor `AUTH_REQUIRED`,
`EMBED_ACCESS_REQUIRED`, `account_verification_unavailable`,
`embed_not_entitled`, `key_validation_unavailable`,
`/api/embed/session`, and `/api/embed/map-frame`.
Healthy behavior:
- A verified PRO account can mint a persistent `wme_` key.
- The key exchange returns a panel-scoped `wmg_` grant with a 30-minute
maximum lifetime.
- Paid frames use private, no-store responses and rate-limit by verified
account ID before source reads.
- The keyless public map continues to show conflicts, earthquakes, and
weather with the hourly shared-cache profile.
Treat a rise in PRO 403 or 503 responses, a missing `plan` claim in the
active Clerk `convex` JWT template, or paid frames that stop renewing as
a release failure. Roll back the #7707 head or hide the self-serve key
UI while keeping the free keyless map available. The WorldMonitor
maintainer owns this validation window.
---
[](https://github.com/EveryInc/compound-engineering-plugin)
