# Juke Audio
> Juke is live participatory audio for Farcaster. Developers can embed live
> Juke spaces on any website, let visitors listen anonymously, and optionally
> upgrade listeners into authenticated Farcaster participants.
Primary site: https://juke.audio
API base URL: https://api.juke.audio
Hosted embed base URL: https://juke.audio/embed/{spaceId}
Developer dashboard: https://juke.audio/developers
LLM implementation guide: https://juke.audio/SKILL.md
Release feed (JSON, CORS-open): https://juke.audio/changelog.json
## What To Build
Use Juke when a user asks for:
- embedding a live Juke audio space
- adding anonymous live listening to a webpage
- adding "sign in to participate" for Farcaster users
- adding reactions, replies, hand raise, or host-approved speaking to a space
- creating custom Juke apps or server integrations with developer API keys
- sharing a canonical Juke space URL
Agent participation is a separate join path (paid via x402, or free for
approved developer apps joining their own rooms — see "Agents" below).
It's not part of the normal embed integration.
## Key Rule: When Keys Are Required
Hosted iframe embeds do not need API keys. They are the default path for public
websites, landing pages, blogs, community dashboards, and fast MVP embeds.
Juke developer keys are only for custom SDK or server integrations that call
protected Juke developer endpoints, such as creating apps, minting rooms from a
server, or managing custom integration state. Never ask developers for Neynar
API keys, LiveKit keys, or Juke backend credentials for ordinary embeds.
Developers manage Juke apps and keys at:
```txt
https://juke.audio/developers
```
Developer access states:
- signed out: prompt Farcaster SIWF before loading developer apps
- pending: no keys yet; hosted iframe still works without keys
- approved: create apps, create keys, rotate keys, revoke keys
- suspended: custom API key calls are disabled; hosted public embeds can remain
available
## Fastest Integration: Hosted Iframe
Use this when the developer wants spaces live quickly and accepts Juke-owned UI.
```html
```
Behavior:
- no auth is required to render public metadata
- visitors can listen anonymously
- visitors sign in with Farcaster/Neynar only when they want to participate
- speaking always requires host/co-host promotion inside the Juke permission model
- "Powered by Juke" attribution must remain visible
Third-party iframe integrations do not need a Neynar API key, LiveKit key, or
Juke backend credentials. Juke hosts the auth, audio token issuance, and UI.
## Custom Integration: SDK Shape
Use this when the developer wants custom UI.
```ts
import { createJukeEmbedSdk } from "@juke/audio-sdk";
const juke = createJukeEmbedSdk();
const space = await juke.getSpace(spaceId);
// 1. Anonymous listening — no sign-in required.
const anonymousJoin = await juke.joinAnonymousListener(spaceId);
await juke.connectAudio(anonymousJoin);
// 2. When the visitor opts into participation, run Sign In With Farcaster.
// The SDK fetches a single-use nonce from the backend, opens a SIWF
// channel via the Farcaster auth relay, and returns a deeplink the
// user opens in their Farcaster client.
const { channelToken, url } = await juke.startSiwfFlow();
// 3. Render `url` (a `farcaster://connect?...` deeplink) as a QR code
// on desktop or as a tap button on mobile. The user approves in
// their Farcaster client, which displays the signing domain on the
// approval screen.
const approved = await juke.pollSiwfStatus(channelToken);
// 4. Finish login and join the room as an authenticated participant.
// The backend re-verifies the signature, the domain, and the nonce
// before issuing a Juke JWT.
await juke.completeSiwfLogin(approved);
const authedJoin = await juke.joinAuthenticated(spaceId);
await juke.connectAudio(authedJoin);
await juke.raiseHand(spaceId, true);
await juke.sendReaction("clap");
```
SDK capabilities:
- `getSpace(id)`
- `joinAnonymousListener(id)`
- `startSiwfFlow()` — returns `{channelToken, url}`; render `url` as a
QR for desktop scan or as a tap button on mobile.
- `pollSiwfStatus(channelToken, { signal? })` — resolves to
`{message, signature, fid}` when the user signs; rejects on abort.
- `completeSiwfLogin({message, signature})` — finalizes the Juke session.
- `joinAuthenticated(id, token?)`
- `leaveSpace(id)`
- `refreshToken(id)`
- `sendReaction(id, reaction)`
- `raiseHand(id, raised)`
- `connectAudio(joinResponse)`
- `enableMicrophone()` only after host approval
Do not open the SIWF `url` in a desktop popup — it is a Farcaster
deeplink (`farcaster://connect?...`) and renders blank in a normal
browser tab. Show a QR code so the user can scan it with their
Farcaster app, plus a tap-to-deeplink button for visitors already on
mobile.
If building custom UI, keep visible Juke attribution and link to the canonical
space page: `https://juke.audio/space/{spaceId}`.
## Custom Server Integration: Developer Keys
Use Juke developer keys only from a trusted server environment. Do not place a
developer key in browser JavaScript, mobile app bundles, iframe params, public
environment variables, public repos, logs, analytics events, crash reports, or
client-side error monitoring.
End-to-end flow:
1. Open `https://juke.audio/developers`.
2. Sign in with Farcaster using SIWF.
3. Request developer access with the app/use-case details.
4. Wait for Juke admin approval.
5. Create an app with the production origins where the integration runs.
6. Create a key and copy the one-time secret immediately.
7. Store the secret in a private server environment variable such as
`JUKE_API_KEY`.
8. Embed hosted spaces without a key when you only need public listening.
9. Call protected developer APIs from your backend only.
Two distinct auth paths:
- **`/v1/developer/spaces`** (room creation from a server) — **key only**.
Send `X-Juke-Api-Key: `; do not send a bearer JWT. The
room owner is derived from the key's owning developer app
(`app.owner_fid`). This is the real machine credential.
- **`/v1/developer/apps/*`** (dashboard routes: list apps, list keys,
create app, etc.) — **JWT only**, scoped to the developer's signed-in
session. Dangerous mutations (rotate, revoke, reveal, delete-app)
additionally require a recent SIWF within the last 5 minutes; the
server signals this with `401 "Recent sign-in required."` and a
`WWW-Authenticate: ReAuth` header. The dashboard handles this for you
— server integrations rarely need these endpoints.
Example server-side call:
```ts
const response = await fetch("https://api.juke.audio/v1/developer/spaces", {
method: "POST",
headers: {
"X-Juke-Api-Key": process.env.JUKE_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
title: "Weekly builder room",
scheduled_at: null,
announce_cast: false,
allow_agents: true,
}),
});
if (!response.ok) {
throw new Error("Juke developer API request failed");
}
```
The room host is derived from the API key — specifically, the key's owning
developer app's `owner_fid`. Do not send any host identifier or host
override in developer API requests.
If the `Origin` header is sent (browser-initiated requests) and the
developer app has `allowed_origins` configured, the Origin must match
one of the listed origins. Server-to-server requests with no `Origin`
header are not subject to this check.
Secret handling:
- Juke shows a key secret only once at creation or rotation time
- lost secrets cannot be revealed again; rotate the key instead
- rotate keys from the dashboard before replacing production env vars
- revoke keys immediately if they are exposed or no longer used
- never expose Juke API keys in browser JS, mobile bundles, iframe URLs,
`NEXT_PUBLIC_*`/`EXPO_PUBLIC_*` variables, logs, analytics, or screenshots
Developer API wrapper contract:
```http
GET /v1/developer/status
POST /v1/developer/application
GET /v1/developer/apps
POST /v1/developer/apps
GET /v1/developer/apps/{appId}/keys
POST /v1/developer/apps/{appId}/keys
POST /v1/developer/apps/{appId}/keys/{keyId}/reveal
POST /v1/developer/apps/{appId}/keys/{keyId}/rotate
POST /v1/developer/apps/{appId}/keys/{keyId}/revoke
POST /v1/developer/spaces
POST /v1/developer/spaces/{roomId}/end
POST /v1/developer/partner-tokens
POST /v1/developer/rooms/{roomId}/agent-join
POST /v1/developer/webhooks
GET /v1/developer/webhooks
DELETE /v1/developer/webhooks/{webhookId}
```
### Ending a room from your server
Force-end an active room your app created. The room must have been
minted via `POST /v1/developer/spaces` (cross-app and iOS-native rooms
404 — same response shape so partners can't enumerate other apps'
UUIDs).
```http
POST /v1/developer/spaces/{room_id}/end
X-Juke-Api-Key:
```
200 returns `{"status": "ended"}`. Outbound `room.finished` fires
immediately on success — no waiting on LiveKit's 5-minute empty-room
timeout. The webhook payload carries `"ended_via": "api"` so you can
distinguish it from a human host pressing End Space (`"ended_via":
"host"`). Idempotent: a second call against an already-ended room
returns 200 without re-firing.
## Auth Ladder
1. Public metadata: no auth.
2. Anonymous listen: `POST /v1/rooms/{spaceId}/anonymous-join`.
3. Human participation on ordinary web: Sign In With Farcaster (SIWF).
SDK calls `startSiwfFlow()` → renders the resulting
`farcaster://connect` deeplink as a QR for desktop scan or a tap
button on mobile → polls `pollSiwfStatus()` until signed →
`completeSiwfLogin()`. The signing domain is bound into the SIWE
message and displayed to the user at approval.
4. Farcaster miniapps: Quick Auth via `@farcaster/miniapp-sdk`.
5. Native Juke iOS app: on-device secp256k1 auth address registered via
the developer-managed signed key API (separate from the web SDK).
6. Speaking: only after host/co-host promotion grants LiveKit publish
permission. Promotion + mic publish works the same on web (SDK / hosted
iframe via `livekit-client`) and native iOS — desktop speakers are
first-class once promoted.
Anonymous listener tokens are short-lived and subscribe-only:
- `can_subscribe=true`
- `can_publish=false`
- `can_publish_data=false`
## Public API Reference
Read space metadata:
```http
GET https://api.juke.audio/v1/rooms/{spaceId}
```
Anonymous listen-only join:
```http
POST https://api.juke.audio/v1/rooms/{spaceId}/anonymous-join
```
Authenticated listener join:
```http
POST https://api.juke.audio/v1/rooms/{spaceId}/join
Authorization: Bearer {jukeJwt}
```
Raise/lower hand:
```http
POST https://api.juke.audio/v1/rooms/{spaceId}/raise-hand
Authorization: Bearer {jukeJwt}
Content-Type: application/json
{"raised": true}
```
Refresh LiveKit token:
```http
POST https://api.juke.audio/v1/rooms/{spaceId}/token
Authorization: Bearer {jukeJwt}
```
## Reading Participants
`GET /v1/rooms/{spaceId}` returns the active participant list in the same
response as room metadata — no separate endpoint needed.
```jsonc
{
"room": {
"id": "…",
"title": "Weekly builder room",
"host_fid": 12345,
"host": { "fid": 12345, "username": "host", "display_name": "Host",
"pfp_url": "https://…" },
"status": "active", // "scheduled" | "active" | "ended"
"started_at": "2026-05-23T16:00:00Z",
"ended_at": null,
"scheduled_at": null,
"speaker_count": 3,
"listener_count": 27,
"recording": false,
"allow_agents": true
},
"participants": [
{ "fid": 12345, "display_name": "…", "pfp_url": "https://…",
"role": "host", "is_muted": false, "hand_raised": false },
{ "fid": 23456, "display_name": "…", "pfp_url": "https://…",
"role": "speaker", "is_muted": false, "hand_raised": false },
{ "fid": 34567, "display_name": "…", "pfp_url": "https://…",
"role": "listener", "is_muted": true, "hand_raised": true }
],
"hand_queue": [34567]
}
```
`role` is one of `host`, `co_host`, `speaker`, `listener`. Use this for
"who's in the space" UIs (member badges, pfp stacks, counts).
## Recording
Hosts toggle recording from the native app or via the developer API:
```http
POST https://api.juke.audio/v1/rooms/{spaceId}/recording/start
POST https://api.juke.audio/v1/rooms/{spaceId}/recording/stop
```
After the space ends and the LiveKit egress completes, the recording is
addressable two ways:
- `GET /v1/recordings/{spaceId}` — returns
`{ recording_url, started_at, ended_at, duration_seconds, cast_hash, title }`.
`recording_url` is a short-lived presigned URL — re-fetch when expired.
- Hosted web player: `https://juke.audio/r/{spaceId}` (own OG image, embeddable
in casts and tweets).
There is no clip / segment API in v1.
## Scheduled Spaces
Pre-create a recurring space with `scheduled_at`:
```ts
await fetch("https://api.juke.audio/v1/developer/spaces", {
method: "POST",
headers: { "X-Juke-Api-Key": process.env.JUKE_API_KEY!,
"Content-Type": "application/json" },
body: JSON.stringify({
title: "Fractal Call",
scheduled_at: "2026-05-25T22:00:00Z", // ISO 8601, UTC
allow_agents: true,
}),
});
```
Before `scheduled_at`, `GET /v1/rooms/{spaceId}` returns `status: "scheduled"`
with `title`, `host`, `scheduled_at`, and `allow_agents`, but no LiveKit token
or join URL. The hosted embed (`/embed/{spaceId}`) renders a "starts in …"
countdown over the same metadata.
When the host opens the space (or the scheduler auto-starts it), `status`
transitions to `active` and join becomes available.
## Agents
Agents are a separate join path — not part of the normal embed flow.
Two ways in: pay per-join via x402 (any agent, any room with
`allow_agents=true`), or use a developer API key for free joins
scoped to rooms your own app created. Agents join as data-publishing
participants (transcription, side-channel metadata) and do not publish
audio in v1.
The room must be created with `allow_agents: true`. To join an active room:
```http
POST https://api.juke.audio/v1/rooms/{spaceId}/agent-join
Content-Type: application/json
{
"agent_name": "ZOE",
"agent_pfp_url": "https://…"
}
```
The endpoint is gated by x402 payment on first call and returns a
`session_token` you reuse via `X-Session-Token` for the duration of the
room. Audio-publishing agents (speaker role) are on the v1.x roadmap.
### Partner agents (free, scoped to your own rooms)
If you're an approved developer and want to run your own bot in rooms
your app created, skip the x402 path and use the partner-scoped variant.
Same request/response shape; auth swaps to your developer API key:
```http
POST https://api.juke.audio/v1/developer/rooms/{spaceId}/agent-join
X-Juke-Api-Key:
Content-Type: application/json
{ "agent_name": "ZOE", "agent_pfp_url": "https://…" }
```
Constraints:
- `room.created_by_app_id` must equal your app id; cross-app rooms
and unknown rooms both 404 (same response, so the endpoint can't be
used to enumerate which UUIDs belong to other developers)
- `room.status == "active"` and `room.allow_agents == true` (same gates
as the public path)
- iOS-native rooms (no owning developer app) are not reachable here
- Per-room agent cap: 5 concurrent. Hitting it returns 429.
- Rate limit: 10/min and 100/day per key. Rejoin via `X-Session-Token`
on the same endpoint works identically to the paid path.
The returned `session_token` is interchangeable with the paid path's, so
the same token-refresh / leave / rejoin flow applies. Sessions spawned
this way are tagged `payer_address="partner:{app_id}"` for audit.
## Partner SSO Bridge
When the embedding site has already authenticated the visitor (SIWN, SIWE,
custom session), pre-mint a short-lived Juke JWT from your server and
pass it on the iframe URL so the visitor doesn't repeat SIWF inside the
embed.
Server-side mint (key-only auth):
```http
POST /v1/developer/partner-tokens
X-Juke-Api-Key:
Content-Type: application/json
{
"fid": 12345,
"ttl_seconds": 300
}
```
`fid` is the Farcaster ID of the visitor you've already verified.
`ttl_seconds` is `[60, 600]`, defaults to 300.
Response:
```json
{
"token": "eyJ...",
"fid": 12345,
"expires_at": "2026-05-23T17:05:00Z",
"partner_app_id": ""
}
```
Pass the token on the iframe URL:
```html
```
The iframe adopts the session client-side, strips `?token=` from the
URL via `history.replaceState` (so the JWT doesn't leak via Referer
headers on outbound asset requests), and renders as already-authenticated
— no QR. The backend re-verifies the JWT on every authed call (`/join`,
`/raise-hand`, `/leave`, `/token`) so a forged or expired token still
fails at use.
Trust model + caveats:
- Possession of an active Juke API key + the partner's assertion of `fid`.
We trust the partner because they're an approved developer app.
- Damage from a leaked API key is bounded by: the TTL cap (≤ 10 min),
the `partner_app_id` + `source="partner"` claims baked into each JWT
for audit attribution and downstream gating, per-key rate limiting
on the mint endpoint (60/min + 5,000/day), and the first-party gate
described below.
- Partner-minted JWTs are deliberately limited to **room participation**:
`/join`, `/leave`, `/raise-hand`, `/token`. Sensitive endpoints that
act on the user's account — developer-dashboard ops (apps, keys,
webhooks, application status), room creation, recording start/stop —
reject any JWT carrying `source="partner"` with a 401 telling the
caller to sign in directly on juke.audio. So a leaked partner token
cannot be used to manage the user's developer apps or attribute new
rooms/recordings to them.
- No refresh token is issued. When the JWT expires, re-mint from your
server or let the visitor sign in via SIWF inside the embed.
## Native App Deeplinks
The Juke iOS app advertises two link forms:
- Custom scheme: `juke://space/{spaceId}` (and `juke://recording/{spaceId}`)
- Universal link: `https://juke.audio/space/{spaceId}`
Use the **universal link** form for desktop CTAs ("Open in Juke") and shared
links. iOS resolves it to the native app when installed; everywhere else
falls back to the hosted web page. `{spaceId}` is a UUID — the handler
rejects anything else.
## Lifecycle Webhooks
Outbound webhooks fire only for rooms created via the developer API
(rooms with `created_by_app_id` set). iOS-native rooms do not emit
developer webhooks.
Available events:
- `room.started` — scheduled→active transition (and immediate room create)
- `room.finished` — the room ended. Two trigger paths:
- **Explicit end**: a host called End Space (iOS) or your server hit
`POST /v1/developer/spaces/{id}/end`. Payload's `ended_via` is
`"host"` or `"api"` respectively. Fires immediately.
- **Empty-room timeout**: LiveKit reports the room emptied for 5
minutes. Payload omits `ended_via`.
- `participant.joined` / `participant.left` — real humans + agents only
(anonymous listeners and LiveKit virtual participants are filtered out)
- `recording.ready` — egress completed; payload includes a presigned URL
Register from your server:
```http
POST /v1/developer/webhooks
X-Juke-Api-Key:
Content-Type: application/json
{
"url": "https://your-app.example/juke-webhooks",
"events": ["room.finished", "recording.ready"]
}
```
The 201 response includes a `secret` (prefixed `whsec_`) — store it; it
is never re-revealable. Rotate by deleting + re-creating.
Also available: `GET /v1/developer/webhooks` (list, with `last_delivery_at`
+ `last_status` + `last_error` for debugging) and
`DELETE /v1/developer/webhooks/{id}`.
Delivery format:
```http
POST
Content-Type: application/json
User-Agent: JukeWebhooks/1
X-Juke-Signature: t=,v1=
{
"event_id": "uuid-v4",
"event_type": "room.finished",
"occurred_at": "2026-05-23T16:42:00.000+00:00",
"data": { "room_id": "…", "host_fid": 12345, "started_at": "…", "ended_at": "…" }
}
```
Verify by recomputing HMAC-SHA256 of `f"{timestamp}.{raw_body}"` with your
stored secret and comparing in constant time. Reject events older than ~5
minutes. Use `event_id` for idempotency.
Retries: 4 attempts at t=0, +10s, +60s, +300s for non-2xx or transport
failures, then stop. The last status / error is exposed on
`GET /v1/developer/webhooks` so you can diagnose silently failing
endpoints. `last_error` is one of a fixed vocabulary — never a raw
exception or response body — so the field cannot be used as a network
probe:
```
"" | timeout | connection_failed
http_3xx | http_4xx | http_5xx | http_other
blocked_ip | dns_failure | invalid_url | no_attempts
```
After 10 consecutive failed deliveries we auto-disable the subscription
(stamp `disabled_at` and stop dispatching). Re-enable by deleting and
re-creating it.
The webhook URL must resolve to a globally-routable public IP at delivery
time. Loopback, link-local (including AWS/GCP IMDS at 169.254.169.254),
private RFC1918, and other non-public ranges are rejected per-attempt with
`last_error: blocked_ip`. Redirects are not followed.
Max 5 subscriptions per app, and the same URL cannot be registered twice
for the same app.
## Release Feed
Structured changelog of shipped developer-facing changes, machine-readable
and CORS-open for partner integration manifests:
```http
GET https://juke.audio/changelog.json
```
Schema:
```jsonc
{
"version": 1,
"generated_at": "2026-05-23T...",
"canonical_spec": "https://juke.audio/llms.txt",
"entries": [
{
"id": "partner-agent-join", // globally unique, never reused
"shipped_at": "2026-05-23",
"category": "developer-api", // | "embed" | "webhooks" | "docs"
"title": "…",
"summary": "…",
"endpoints": ["POST /v1/developer/rooms/{room_id}/agent-join"],
"docs": "https://juke.audio/llms.txt",
"docs_section": "Partner agents (free, scoped to your own rooms)",
"resolves": ["agents"], // opaque partner slugs
"breaking": false // optional
}
]
}
```
If you maintain a structured integration manifest (e.g.
`/api/juke/status` listing your open asks against Juke), match each
entry's `resolves[]` against your `open_asks[].id` to flip resolved
items deterministically. Diff on `shipped_at` to incrementally update.
## What You Actually Need (SDK / iframe integration)
The hosted Juke backend runs all the infrastructure for you. SDK and
iframe integrators do **not** need to provision Neynar API keys, LiveKit
keys, JWT secrets, databases, Redis, or anything else.
- **Hosted iframe**: no env vars, no secrets, no setup.
- **Server-side integrations** calling protected developer APIs: exactly
one env var on your server — `JUKE_API_KEY`, the `secret_key` shown
once when you create a key in the Juke developer dashboard. Store it
somewhere private (cloud secret manager, vault, sealed env). It is
never used in the browser.
That's it. Skip the next section unless you are running your own Juke
backend stack.
## Self-Hosting (rare — only if you're running the Juke backend yourself)
You only need this if you are forking Juke and operating your own
backend, e.g. for compliance, air-gapped deployment, or contributing
to the project. SDK consumers integrating Juke into their own site do
not need any of these.
Backend env keys (the Juke server stack):
- `DATABASE_URL` (Postgres 16)
- `REDIS_URL` (Redis 7)
- `JWT_SECRET`, `JWT_ALGORITHM`, `JWT_EXPIRY_HOURS`,
`JWT_REFRESH_EXPIRY_DAYS`
- `NEYNAR_API_KEY`, `NEYNAR_CLIENT_ID`
- `LIVEKIT_API_KEY`, `LIVEKIT_API_SECRET`, `LIVEKIT_WS_URL`
- `FARCASTER_APP_FID`, `FARCASTER_APP_MNEMONIC` (used by the miniapp
auth-address registration and snap signing — SIWF login does not
require these; the user's own Farcaster custody signs)
- `SIWF_DOMAIN` (the domain the backend will require in SIWF signed
messages; e.g. `juke.audio`)
- `JUKE_API_KEY_PEPPER` (≥32 bytes random; peppers stored API key
hashes)
- `JUKE_API_KEY_ENCRYPTION_KEY` (exactly 32 bytes base64; AES-256-GCM
key for the once-only revealable secret blob)
- `JUKE_API_AUDIT_SECRET` (≥32 bytes random; HMAC key for audit
trail entries — optional but recommended)
- `QUICKAUTH_ALLOWED_AUDIENCES`, `CORS_ORIGINS`
- `ENVIRONMENT` (`development` or `production`; the backend enforces
stricter pepper/key length and Secure-cookie/Domain checks when set
to `production`)
Pepper and encryption key rotation is destructive — rotating the pepper
invalidates every stored key hash; rotating the encryption key makes
every un-revealed secret undecryptable. Generate once per environment
and back up in a secret manager.
Landing env keys:
- `NEXT_PUBLIC_API_BASE_URL`
- `NEXT_PUBLIC_SITE_URL`
Native app env keys:
- `EXPO_PUBLIC_NEYNAR_CLIENT_ID`
- `EXPO_PUBLIC_NEYNAR_API_KEY`
- `EXPO_PUBLIC_API_BASE_URL`
- `EXPO_PUBLIC_LIVEKIT_WS_URL`
Local dev commands:
```bash
cd backend && docker-compose up
cd landing && npm run dev
```
## Design Rules
Juke embeds should feel like a small live listening bar:
- dark navy base `#0f0f23`
- terracotta live/action accent `#D85A30`
- purple secondary/action accent `#855DCD`
- warm, social copy
- rounded, polished controls
- visible "Powered by Juke"
Never remove attribution, bypass auth for participation, bypass host permission
for speaking, or hide the canonical Juke space/source identity.