# Open Mic > Voice Q&A you can drop into any site. Visitors record or type a question; you > answer by voice; the pair is spliced into a short podcast-style episode with a > transcript, published on your own domain. ## Install Paste one tag. No other attribute is required — the defaults render a working, themed widget. ## Optional attributes data-mode="inline|modal|bubble" how the ask widget appears (default: inline) data-archive="list|grid|latest|featured" render published episodes instead of the ask form data-limit="10" how many episodes to show data-episode="" embed one published episode's player data-view="full" on an episode embed, render the whole episode rather than the player data-episode-base="/questions" where YOUR site keeps episode pages, so a click in an embedded archive goes there ## Recommended layout Three pages on YOUR site plus a link from wherever you already have one. This is the shape the first production install arrived at after trying the alternatives, and each piece of it is a mistake somebody already made. /ask/ /questions/ /questions// data-view="full" is what makes that third page worth landing on. Without it the embed is a title and a play button, while the same episode on the space's own host carries who asked, what the answer gets into, its themes and both transcripts — so the journey the archive encourages ends somewhere thinner than it started. With it, the two pages hold the same content. It does NOT make that content yours for search: everything inside a frame is noindexed, deliberately, or it would compete with the page embedding it. A verified CNAME is what makes an episode page genuinely yours. On the home page: your own button linking to /ask/, and optionally data-archive="featured" is the home-page one. It shows the episodes the host picked, in the order they put them in, chosen on the console's Featured page — up to six. Until they pick any it shows their most recent, so the tag works from the day it is pasted and starts obeying them the day they choose. Use data-archive="list" data-limit="3" instead if you want strictly-most-recent and never the host's selection. Keep your own header, footer, nav, fonts and colours on all of these. Only the widget comes from us; the page is yours. ### Why it is shaped like that * **Give asking a page, not a modal.** data-mode="modal" exists and is the wrong choice for this: a recording form is tall — record, email, consent, a photo, a disclaimer — and an overlay fights the viewport and has no obvious way out. Use inline on a page of its own. * **Use your own button to reach it.** The modal launcher styles itself from your SPACE's accent, so it will not match a button in your own stylesheet. A plain link you style yourself always matches. * **data-episode-base is not optional on an archive.** Without it, clicking an episode navigates the IFRAME: your page's URL never changes, and the episode cannot be linked, shared or bookmarked. It looks like it worked and quietly is not a page. With it, the click lands on your own /questions//. * **Link to your own pages, never to .openmic.dev.** Sending a visitor to our hostname is the opposite of an embed. * **Do not try to style the widget from your CSS.** It is a cross-origin iframe and your rules cannot reach inside it, which is deliberate — it also means your global `button` and `box-sizing` rules cannot break it. Change how it looks in the console's Branding step instead: colours, fonts, radius and copy all come from there. * **Set the space's accent to match your primary button**, not to your brand's loudest colour. The accent paints the widget's buttons and play control, so matching what a visitor already sees on your site is what makes it read as part of the page. ## Host page requirements The widget records audio from a cross-origin iframe. Two headers on YOUR page can block it, and both fail silently unless you set them. Name YOUR SPACE'S host in them, not openmic.dev — that is the origin the iframe is actually loaded from, and it becomes your own domain the moment you verify one. The console's Install page shows the exact hostname to use. Permissions-Policy: microphone=(self "https://") Content-Security-Policy: frame-src https://; connect-src https://openmic.dev Measured browser behaviour, so you can predict failures: * Chromium enforces Permissions-Policy AND exposes document.featurePolicy, so a blocked microphone is detectable before the visitor taps record. * Safari ignores the Permissions-Policy response header entirely and exposes no featurePolicy API. A failure there is almost always a real user denial. * A policy denial, a missing allow attribute, and a user clicking "Block" all raise the identical NotAllowedError. The error name alone never tells you why. ## API Everything the console can do, for a server or an agent. curl https://openmic.dev/v1/space \ -H "Authorization: Bearer sk_..." * Full reference: https://openmic.dev/docs/api/ * OpenAPI 3.1: https://openmic.dev/v1/openapi.json * MCP server: https://openmic.dev/mcp — also at https://mcp.openmic.dev Streamable HTTP. Speaks both the stateless 2026-07-28 revision and the older initialize-based ones, so any client era connects. If you are an agent, prefer the MCP server: the same operations arrive as tools, with their scopes and their consequences in the descriptions. Read endpoints today: /v1/space, /v1/questions, /v1/questions/, /v1/episodes, /v1/episodes/, /v1/themes. ## Two ways to authenticate A secret key, or an OAuth authorization made by a person. Both work on the REST API and on the MCP server, and they answer different questions — "which space is this server allowed to touch" versus "which person is this client acting for". **A secret key.** Create one in the console under Keys. Shown once, scoped (read < write < admin), belongs to exactly one space, revocable. Right for a server with nobody at a keyboard. This is NOT the pk_ key in your embed snippet — that one is public, only tells a browser where to point the widget, and does not authenticate here. **OAuth, so the client acts as a person.** The MCP server answers an unauthenticated tools/call with 401 and a WWW-Authenticate header naming its RFC 9728 document, which is the entry point: https://mcp.openmic.dev/.well-known/oauth-protected-resource https://openmic.dev/.well-known/oauth-authorization-server Authorization code with PKCE (S256 only), scopes openmic:read, openmic:write, openmic:admin. The person signs in to Open Mic, picks ONE space, and the token carries no more than their own membership allows — an editor cannot grant admin however much the client asked for. It can be withdrawn from the same Keys page, by an owner or by whoever approved it. initialize, tools/list, ping and server/discover need no credential, so a client can see what is on offer before deciding to authorize. Only tools/call challenges — which means a client will connect successfully and be sent through the browser on its first actual tool call. **Register yourself.** RFC 7591 dynamic client registration is open at https://openmic.dev/o/register/, advertised in the metadata above, so a client nobody has met can get a client_id without asking anyone. Ten an hour per address. A registration is a name, not access. It reaches nothing until a person signs in and approves it, for one space, and they can withdraw it again from the same Keys page. Every error, including 404 and 405, has one shape. Branch on `code`: {"error": {"code": "invalid_credential", "message": "That key is not valid."}} Lists take limit (max 100) and offset and return `total`. Out-of-range values are clamped rather than refused, so a paging loop cannot be broken by asking for too much. No response carries an asker's email address. Names and locations appear only where the asker consented to them being shown. ## Notes * The recorder needs a secure context (https, or localhost). * iOS Safari records audio/mp4; Chrome records audio/webm;codecs=opus. Both are transcoded server-side to MP3, so you do not need to care. * Published episodes are also served on your own domain via CNAME, which is what makes their transcripts count as your SEO rather than ours. * Episodes can be filed under themes — a curated per-space vocabulary, drafted with the rest of the metadata and confirmed by the host. Each theme is a page on the space's own host: /themes/ lists them, /themes// holds the episodes filed under one, and both are in the sitemap. * Themes are a hosted-site feature and the embed deliberately renders no theme links. A chip inside a frame either loads a whole site page into somebody's widget or navigates their visitor off their own site. If you want theme browsing on YOUR pages, link to the space's /themes// URLs yourself, or send visitors to the hosted archive.