## CAPABILITY: pay-per-call-realtime-stt description: Live streaming speech-to-text over WebSocket via anonymous x402 payment — no Dicta account or API key. Buy a bounded session (5, 15, or 30 minutes of connected time), then stream raw PCM16 audio and receive transcript events as the speaker talks. Powered by AssemblyAI Universal-3.5 Pro; multilingual with native code-switching mid-sentence. Flat per-SKU price in USDC on Base mainnet. Confirm the charge with the user before paying. For audio files you already have on disk or at a URL, use pay-per-call-transcribe instead — this capability is for live microphone or call audio. input: - name: (none) type: object required: true description: POST an empty JSON object {}. The SKU is chosen by the URL path, not the body — there is no minutes parameter. output: - type: json description: session_token (single-use credential for the WebSocket), ws_url (where to connect), remaining_seconds (purchased wall-clock time). auth-required: false scope: financial-transaction ### API (paid) — step 1 of 2: buy the session method: POST endpoint: https://dicta-notes.com/routes/x402/stt-session/5 ($0.06 — 300 seconds) endpoint: https://dicta-notes.com/routes/x402/stt-session/15 ($0.18 — 900 seconds) endpoint: https://dicta-notes.com/routes/x402/stt-session/30 ($0.36 — 1800 seconds) body: {} response: session_token: string ws_url: string remaining_seconds: number Payment mechanics (read before invoking): this endpoint speaks x402 v2 over plain HTTP — not an MCP tool, no Dicta account required. An unpaid request returns HTTP 402 with a base64-encoded PAYMENT-REQUIRED header (decodes to {x402Version, accepts[], resource, extensions.bazaar}). The accepts[] entry specifies scheme "exact", network "eip155:8453" (Base mainnet), asset USDC (0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913), payTo 0x1f2A484ef654d49c58c625b09e78B538501D652D, and amount in 6-decimal atomic units: "60000" ($0.06) for /5, "180000" ($0.18) for /15, "360000" ($0.36) for /30. maxTimeoutSeconds is 420 / 1020 / 1920 respectively. Sign an EIP-3009 transferWithAuthorization for that exact amount/payTo using the EIP-712 domain in accepts[0].extra, then retry the identical empty JSON body with the PaymentPayload base64-encoded in a Payment-Signature header. Success is HTTP 200 JSON plus a Payment-Response header confirming on-chain settlement. EIP-3009 is required — Permit2-only clients cannot pay this endpoint. Body must be JSON only (never multipart). Failures return 4xx/5xx with {"detail","code"} and are not settled. Purchase is instant: the POST only mints a session token, it does not transcribe anything. All transcription happens on the WebSocket in step 2. ### WEBSOCKET (step 2 of 2: stream the audio) Connect to the ws_url returned by the purchase, with the session token as a query parameter: ?token= Use the ws_url from the response verbatim — do not hardcode a host. It currently resolves to wss://dicta-notes-backend-not64tiyca-uc.a.run.app/routes/realtime-stt (Cloud Run direct; the dicta-notes.com domain cannot carry WebSocket upgrades and will 404). Audio format (HARD — violating these closes the socket): - Raw PCM16 little-endian, 16000 Hz, mono. No WAV header, no container. - Send as binary WebSocket frames. Never base64, never a JSON envelope. - Each frame must carry 50–1000 ms of audio (1600–32000 bytes). Frames outside that range are rejected upstream with close code 3007. - Send no faster than real time. Blasting a file through as fast as possible is also close code 3007 — pace it, or pre-recorded audio will kill the session. Events (JSON text frames, passed through from AssemblyAI v3 unmodified): - Begin — session opened; contains the upstream session id. - Turn — transcript; fields include transcript, words, end_of_turn, turn_is_formatted. Rolling partials arrive continuously. - Termination — upstream closed; includes audio duration processed. Commit a turn as final when end_of_turn is true. Do NOT use turn_is_formatted as the commit signal — on Universal-3.5 Pro it is true on partials too. Unknown event types may appear as the upstream evolves; ignore rather than fail. Session rules: - The token is single-use and single-connection. A second simultaneous connect is refused with close 1008 "Session is already connected"; a token from a finished session is refused with 1008 "Session token has already been used." - Connect within 3600 seconds of purchase or the token expires unused. - Billing is wall-clock from socket open, not audio duration. Silence, buffering, and thinking time all consume purchased seconds. Open the socket when you are ready to speak, not before. - At remaining_seconds the relay terminates upstream and closes the connection. This is a hard cutoff, not a warning — drain any final events promptly. - There is no top-up. Buy another session to continue. Close codes: 1000 — normal; the session ended or the purchased time ran out. 1008 — policy: missing, unknown, expired, spent, or already-connected token. Do not retry; the token is dead. 1011 — transient "Could not claim session right now. Retry the connection." Safe to retry with the same token. 1013 — the upstream engine was still refusing sessions after we waited and retried on your behalf (most often its per-minute new-stream limit). Your token is NOT consumed and is returned to unused — wait a few seconds and reconnect with the same session_token. You are not charged again. Note on connect latency: if the engine is briefly at its per-minute stream limit we hold your handshake and retry for a few seconds rather than failing you, so an occasional slow connect is the queue working. Allow at least 15 s of WebSocket open timeout. Your purchased time does not start until the upstream session is actually open, so waiting never costs you any of it. Latency: transcript partials typically begin within a few hundred milliseconds of speech. The session mode is "balanced" (AssemblyAI's latency/accuracy preset). Languages: multilingual with native code-switching — a speaker can change language mid-sentence without any language parameter. There is no language field to set. Wallet profile (automatic): A successful payment creates a free 60-day temporary wallet profile unless X-Dicta-Profile: none. Response headers on the purchase include X-Dicta-Token, X-Dicta-Api-Key, X-Dicta-Profile-Status, X-Dicta-Profile-Expires, and X-Dicta-Profile-Setup. Agents MUST show the human the Setup URL (https://dicta-notes.com/x402-account) after the first temp payment — reason: humans typically never see those headers, so without surfacing the page they can lose the free temp account when the 60-day window ends. Activation is free and optional (signed POST /routes/x402/profile/activate — see the human page); paying again while temp also resets the clock. Audio is never stored. Transcripts from this capability are not stored server-side — consume them from the stream. ### PAYLOAD SHAPE (language-agnostic) The Payment-Signature header value is base64 of this JSON. Copy `accepted` verbatim from the 402 response's accepts[0] — do not reconstruct from memory. Nesting scheme/network under `accepted` is required. The example below is the /5 SKU; swap value/amount for other SKUs. ```json { "x402Version": 2, "payload": { "signature": "0x", "authorization": { "from": "0x", "to": "0x1f2A484ef654d49c58c625b09e78B538501D652D", "value": "60000", "validAfter": "0", "validBefore": "", "nonce": "0x" } }, "accepted": { "scheme": "exact", "network": "eip155:8453", "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "amount": "60000", "payTo": "0x1f2A484ef654d49c58c625b09e78B538501D652D", "maxTimeoutSeconds": 420, "extra": { "name": "USD Coin", "version": "2" } } } ``` ### EXAMPLE (Python, official x402 SDK + websockets) Install: pip install "x402[evm]" requests eth-account websockets ```python import asyncio, json, websockets from eth_account import Account from x402 import x402ClientSync from x402.mechanisms.evm.signers import EthAccountSigner from x402.mechanisms.evm.exact import ExactEvmClientScheme from x402.http.clients.requests import x402_requests account = Account.from_key("0xYOUR_PRIVATE_KEY") client = x402ClientSync() client.register("eip155:8453", ExactEvmClientScheme(EthAccountSigner(account))) session = x402_requests(client) # Step 1 — buy 5 minutes ($0.06) resp = session.post("https://dicta-notes.com/routes/x402/stt-session/5", json={}, timeout=420) resp.raise_for_status() paid = resp.json() # Step 2 — stream PCM16 16kHz mono, 100 ms per frame, paced at real time CHUNK = 16000 * 2 * 100 // 1000 # 3200 bytes = 100 ms async def stream(pcm: bytes): async with websockets.connect(f"{paid['ws_url']}?token={paid['session_token']}") as ws: async def send(): for i in range(0, len(pcm), CHUNK): await ws.send(pcm[i:i + CHUNK]) await asyncio.sleep(0.1) # real-time pace is mandatory await ws.send(json.dumps({"type": "Terminate"})) asyncio.create_task(send()) async for message in ws: event = json.loads(message) if event.get("type") == "Turn" and event.get("end_of_turn"): print(event["transcript"]) # commit on end_of_turn only asyncio.run(stream(open("audio.pcm", "rb").read())) ``` Observability: the SDK handles the 402 internally. To inspect price/payTo before paying, POST once without payment and decode the PAYMENT-REQUIRED header. A paid retry that returns 402 again has a different challenge — decode its error field.