Dictum SDK
Voice input infrastructure for product interfaces.
Dictum adds WAV speech capture, explicit transcription states, secure model routing, and final transcript insertion to desktop and mobile web products. The SDK is browser-only and headless: your product always owns the UI.
Browser only
Desktop web and mobile web
7 states
One explicit state machine
17 events
Distinct stable names, at most 16 characters
0 provider keys
No speech vendor secrets in the browser
Quickstart
Ship voice input in one integration pass.
Start with a public project key, a server-issued identity token, and one stable source label for the app surface where users will speak.
Install
npm
# Registry publication is prepared but not active yet.
# Once released:
npm install dictum-sdk- 1. Create a Dictum project and copy the public key.
- 2. Mint a signed identity from your backend under the global, non-configurable seven-day maximum.
- 3. Check browser compatibility, then listen only for provider-confirmed final transcript segments.
Headless SDK
Use this when the voice control must feel native.
The SDK owns recording, state transitions, edge routing, and transcript events. Your app owns placement, styling, editor insertion, and recovery UI.
Client integration
TypeScript
import { checkCompatibility, createClient } from "dictum-sdk";
const compatibility = checkCompatibility();
if (!compatibility.supported) {
showUnsupportedBrowser(compatibility.missing);
} else {
const client = createClient({
projectKey: "dpk_live_xxx",
identityToken: await getDictumIdentityToken(),
target: document.querySelector("#message")
});
client.on("state_changed", event => renderVoiceState(event.state));
client.on("transcript_delta", event => renderFinalSegment(event.data.text));
client.on("sdk_error", event => showVoiceError(event.data));
startButton.onclick = () => client.start().catch(showVoiceError);
pauseButton.onclick = () => client.pause().catch(showVoiceError);
resumeButton.onclick = () => client.resume().catch(showVoiceError);
stopButton.onclick = () => client.stop().catch(showVoiceError);
retryButton.onclick = () => client.retry().catch(showVoiceError);
const result = await client.waitForResult();
renderFinalTranscript(result.text, result.transport);
inspectLatency(result.timing); // Separate monotonic client and Worker timelines.
}checkCompatibility()Reports required browser capabilities without changing state or requesting microphone access.
createClient()Creates a reusable client, binds its target, and preloads remote project configuration.
start()Requests microphone access and starts a fresh dictation on the client.
pause()Pauses capture while keeping the session and UI mounted.
resume()Continues capture on the same session.
stop()Ends capture and completes transcription on the session transport.
retry()Explicitly retries a retryable network failure over POST with retained WAV audio.
destroy()Idempotently releases microphone, transport, listeners, retained audio, and the client.
waitForResult()Observes the active dictation result without stopping capture. Success includes deeply frozen client and Worker timing timelines.
Browser support
Detect capabilities before showing voice UI.
Compatibility is capability-based, never user-agent based. Unsupported browsers expose the exact missing APIs so the host can disable voice input without requesting the microphone. Non-browser consumers use the Dictum API directly.
Capability preflight
Browser
const report = checkCompatibility();
if (!report.supported) {
voiceButton.disabled = true;
showMessage("Please update your browser to use voice input.");
console.debug({ capture: report.capture, missing: report.missing });
}Identity
Sign users server-side, then capture in the browser.
Signed identities are scoped to a project and stable customer subject. Their global, non-configurable maximum lifetime is seven days. The browser receives the identity; the signing key stays on your server.
Backend token route
Server
import { createHmac } from "node:crypto";
const MAX_IDENTITY_LIFETIME_SECONDS = 7 * 24 * 60 * 60;
const encodeJson = value => Buffer.from(JSON.stringify(value)).toString("base64url");
function createDictumIdentity({ keyId, signingSecret, projectKey, userId, userName }) {
const iat = Math.floor(Date.now() / 1000);
const header = { alg: "HS256", typ: "JWT", kid: keyId };
const payload = {
sub: String(userId),
...(userName ? { name: String(userName) } : {}),
aud: projectKey,
iat,
exp: iat + MAX_IDENTITY_LIFETIME_SECONDS
};
const unsignedToken = [encodeJson(header), encodeJson(payload)].join(".");
const signature = createHmac("sha256", signingSecret)
.update(unsignedToken)
.digest("base64url");
return [unsignedToken, signature].join(".");
}
app.post("/api/dictum/identity", requireUser, (request, response) => {
const identityToken = createDictumIdentity({
keyId: process.env.DICTUM_SIGNING_KEY_ID,
signingSecret: process.env.DICTUM_SIGNING_SECRET,
projectKey: process.env.DICTUM_PROJECT_KEY,
userId: request.user.id
});
response.json({ identityToken });
});Project public key
Allowed in the browser. Identifies the project and routing profile.
Signed identity
Generated server-side, with one global non-configurable maximum lifetime of seven days, and binds the customer user to the project.
Signing key
Server-only. Never place this in frontend code, HTML, logs, or analytics payloads.
Provider API keys
Stored in the Dictum dashboard vault or your backend. Never expose them to users.
Webhooks
Receive completed transcripts on your backend.
Optionally configure one public HTTPS URL in the dashboard. After usage is successfully recorded, Dictum sends a signed JSON POST containing the transcript, audio duration in milliseconds, word count, stable user subject, and optional user name from the signed identity. Clear the URL and save to disable delivery.
Delivery is at least once. Deduplicate with the completion UUID, return a 2xx quickly, verify the HMAC-SHA256 signature against the raw body, and reject stale timestamps. Non-2xx responses are retried through a durable queue.
Verify a delivery
Server
import { createHmac, timingSafeEqual } from "node:crypto";
app.post("/webhooks/dictum", rawJsonBody, (request, response) => {
const timestamp = request.get("X-Dictum-Timestamp");
const received = request.get("X-Dictum-Signature");
const expected = "v1=" + createHmac("sha256", process.env.DICTUM_WEBHOOK_SECRET)
.update(timestamp + "." + request.rawBody)
.digest("hex");
if (!received || !timingSafeEqual(Buffer.from(received), Buffer.from(expected))) {
return response.sendStatus(401);
}
const event = JSON.parse(request.rawBody);
// Deduplicate event.id before processing an at-least-once delivery.
console.log(event.data.audio_duration_ms, event.data.word_count);
response.sendStatus(204);
});Events
Render UI from states. Persist from final output.
Every `transcript_delta` segment is provider-confirmed, final, immutable, and append-only. The SDK never exposes interim hypotheses. Usage, duration, and transcription counts are measured by the Worker rather than submitted by the browser. Successful results expose separate monotonic client and Worker timing timelines; compare offsets only inside one timeline. During real-time streaming, the first transcript can legitimately arrive before the Worker accepts the final audio chunk.
Insertion helper
DOM
client.on("transcript_delta", event => {
// Every segment is provider-confirmed, final, immutable and append-only.
renderFinalSegment(event.data.text);
});
client.on("text_inserted", event => {
showInsertedState(event.data.insertMode);
});
client.waitForResult().then(result => {
renderFinalTranscript(result.text);
const { client, server } = result.timing;
console.table({
audioIngressSpanMs: server.lastAudioAcceptedOffsetMs - server.firstAudioAcceptedOffsetMs,
transcriptSpanMs: server.lastTranscriptOffsetMs - server.firstTranscriptOffsetMs,
stopToResultMs: client.stopRequestedOffsetMs === null
? null
: client.resultResolvedOffsetMs - client.stopRequestedOffsetMs,
firstTranscriptMinusLastAudioMs:
server.firstTranscriptOffsetMs - server.lastAudioAcceptedOffsetMs
});
});Event payloads
state_changed{ previousState, nextState }A valid state-machine transition completed.
volume_changed{ level, rms?, peak? }Opt-in local volume feedback while listening.
transcript_delta{ text, transport }Provider-confirmed final append-only transcript segment.
text_inserted{ text, insertMode }The SDK inserted final text into the locked target.
mic_deniedpublic errorThe browser denied microphone permission.
no_transcript{ reason, error }No transcript was produced because audio was empty or contained no speech.
network_errorpublic errorA retryable network, timeout, or stream loss occurred.
audio_invalidpublic errorCaptured audio violated the WAV contract.
compat_failed{ code, action, details }Required browser capabilities are unavailable.
limit_reached{ reason, durationMs, maxSeconds, bytes, maxBytes, action }The Worker reached its streaming audio boundary and forced transcription.
fallback_started{ from, to, reason }WebSocket recovery actually started over POST.
quota_exceededpublic errorThe account or user exhausted its usage quota.
auth_requiredpublic errorA signed customer identity is missing.
identity_expiredpublic errorThe signed identity expired; the host must reload it.
auth_invalidpublic errorThe signed identity or project authorization was rejected.
retry_started{ transport: "post" }An explicit user retry started.
sdk_error{ code, message, retryable, userActionRequired, action? }General safe public error event emitted for every session failure.
State machine
idleThe one-shot session is ready; no microphone capture is active.
requesting_micThe browser is resolving permission and capture startup.
listeningWAV capture is active.
pausedCapture is paused while the same session and target remain locked.
retryingAn explicit POST retry is running from retained WAV audio.
transcribingCapture ended and the final transcript is completing.
failedA typed public failure occurred; retry is offered only when the error says it is retryable.
Errors
Every voice UI needs recovery states.
Treat Dictum errors as product states, not console messages. Keep user text intact, expose retry where safe, and show account or permission actions when retrying cannot fix the issue.
microphone_permission_deniedUser or browser denied microphone permission.
Explain browser permission settings, then create a new session after access is granted.
microphone_not_foundNo microphone is available to the browser.
Ask the user to connect or select a microphone, then create a new session.
browser_unsupportedThe selected capture path needs browser capabilities that are unavailable.
Disable voice input and ask the user to update their browser.
sdk_disabledDictum is disabled for this project.
Keep voice input disabled and ask the project owner to enable the SDK.
network_errorThe browser could not reach the Dictum edge service.
Offer retry() only while retryable is true.
stream_lostThe active transcription connection was interrupted.
Keep the retained audio and offer the explicit POST retry.
transcription_failedThe provider could not complete the transcription.
Preserve the host input and let the user start a new session.
transport_response_invalidThe transcription transport returned an invalid success response.
Do not retry automatically; report the protocol failure to the service owner.
empty_audioThe completed session contains no usable audio.
Keep the target intact and let the user start a new session.
no_speech_detectedThe Worker completed without a transcript.
Keep the target intact and let the user start a new session.
quota_exceededThe project has reached its current usage limit.
Disable capture and link to billing or usage controls.
unsupported_audio_typeThe capture adapter supplied audio outside the WAV contract.
Fix the adapter to emit audio/wav and create a new session.
audio_too_largeThe transcription audio payload exceeded the accepted request size.
Keep the target intact and start a shorter dictation.
rate_limitedThe service is temporarily rate limited.
Offer retry() only while retryable is true.
timeoutThe transcription request stopped making network progress.
Offer retry() only while retryable is true.
aborted_by_userThe user or host stopped the session.
Treat it as a deliberate cancellation and leave the target intact.
client_not_authenticatedThe signed customer identity is missing.
Sign the user in, fetch a signed identity, and create a new session.
identity_expiredThe signed customer identity has expired.
Reload the page with a freshly signed identity.
config_response_invalidThe project configuration response is incomplete or invalid.
Keep voice input disabled and report the server configuration failure.
session_response_invalidThe session service returned an invalid success response.
Do not retry automatically; report the protocol failure to the service owner.
auth_config_errorThe signed identity or its project/signing-key configuration was rejected.
Issue a new identity with the correct kid and aud, then create a new session.
Security
Keep speech vendors and signing material behind your backend.
Browser integrations should identify the project and current user, not carry secret credentials. Provider keys and signing keys belong in controlled server or dashboard storage.
Service surfaces
Dashboard API
https://dictum-dashboard-api.agentcore.workers.devProject, sites, models, provider keys, signing keys, account, and usage.
SDK API
https://dictum-sdk-api.agentcore.workers.devBrowser-facing facade. Its config check confirms that the project is recognized and active so the client page can enable its Dictum integration; WebSocket is always attempted and is not a configurable feature.
Billing API
/api/billing/dictumflowCheckout, billing status, and customer portal.
Dashboard account API
/api/dashboard/accountPublic dashboard proxy for account usage and quota state; the usage Worker remains private behind a service binding.
Voice UI skill
Build product-native voice interfaces.
The complete Dictum voice UI design contract is included here in Docs. It covers the workflow, exact SDK states and events, design rules, recovery requirements, and output checklist.
AI agents can still install the raw machine-readable file from/skill.md.
Complete voice UI skill
Markdown
---
name: dictum-voice-ui-design
description: Design or review product-native Dictum speech-to-text interfaces using the public SDK states and events.
---
<!-- Generated from DICTUM_STT_EVENT_CONTRACT (fe3fb49b2593dbc7de4b6a7d8a0dfe5cd433447b370f782b5459b2dd19313473). Do not edit public/skill.md directly. -->
# Dictum Voice UI Design
Use this skill when building or reviewing Dictum voice interfaces for websites
and apps. Dictum should feel native inside the host product rather than looking
like a generic recorder.
## Workflow
1. Inspect the host product first: input shape, density, brand colors,
interaction style, mobile constraints, and where speech should appear.
2. Design against the SDK's exact closed public contract:
- States (7): `idle`, `requesting_mic`, `listening`, `paused`, `retrying`, `transcribing`, `failed`.
- Events (17): `state_changed`, `volume_changed`, `transcript_delta`, `text_inserted`, `mic_denied`, `no_transcript`, `network_error`, `audio_invalid`, `compat_failed`, `limit_reached`, `fallback_started`, `quota_exceeded`, `auth_required`, `identity_expired`, `auth_invalid`, `retry_started`, `sdk_error`.
- `text_inserted` is an event after successful insertion, never a lifecycle
state.
3. Keep the control visible but compact. Use a microphone, waveform, spinner,
check, stop, pause, retry, or warning icon only when the state requires it.
4. Show progress where users need confidence:
- `listening`: show live audio signal or an active capture affordance.
- `transcribing`: show a spinner with concise status text.
- `text_inserted`: place the transcript in the target or preview, not in a
detached debug surface.
- `failed`: provide short recovery copy and one clear next action based on
the typed public error.
5. Preserve keyboard, touch, and pointer ergonomics. The voice control must be
tappable on mobile and must not interfere with normal typing.
6. Match the host design tokens. Use Tailwind tokens or declared theme
variables and isolate provider-specific colors inside provider-inspired
mockups.
## Design rules
- Do not add SDK-owned overlays, floating panels, hidden debug cards, or generic
audio-upload UI.
- Do not expose raw SDK internals, transport frames, provider names, or
implementation details to end users.
- Do not invent, rename, title-case, or space-separate SDK state or event names.
- Do not make state labels look clickable unless they are controls.
- Do not rely on color alone for state. Pair color with an icon, motion, text,
or shape.
- Keep animations short and consistent with the host motion system. Animate
transform and opacity when possible.
- For provider-inspired examples, reproduce the interaction pattern rather than
presenting a misleading fake product integration.
- For marketing demos, show the transcript landing in the real target surface
users understand.
## Output checklist
- Every public SDK state listed above has a deliberate UI path.
- Event handlers use the exact public names listed above.
- Successful insertion is handled through `text_inserted`, not an `inserted`
state.
- The UI can recover from permission denied, no speech, empty audio, network
error, authentication error, quota exceeded, and audio too long.
- Mobile and desktop layouts keep the control aligned with the input.
- The design avoids competitor claims and focuses on voice input integrated
into sites or apps.
- The implementation uses no private Worker assumptions.
AI Readers
Implementation contract for agents.
If an AI agent reads this page to implement Dictum, it should use the browser-only headless SDK for web UI, send non-browser integrations to the API, never expose server-only secrets, and consume only provider-confirmed final transcript segments.
Machine-readable summary
JSON
{
"product": "Dictum",
"capability": "headless voice-to-text for desktop and mobile web browsers",
"browserOnly": true,
"nonBrowserIntegration": "Use the Dictum API directly",
"package": "dictum-sdk",
"packagePublished": false,
"browserSecrets": [
"project public key",
"signed customer identity under the global non-configurable seven-day maximum"
],
"serverOnlySecrets": [
"Dictum signing key",
"speech provider API keys"
],
"states": [
"idle",
"requesting_mic",
"listening",
"paused",
"retrying",
"transcribing",
"failed"
],
"events": [
"state_changed",
"volume_changed",
"transcript_delta",
"text_inserted",
"mic_denied",
"no_transcript",
"network_error",
"audio_invalid",
"compat_failed",
"limit_reached",
"fallback_started",
"quota_exceeded",
"auth_required",
"identity_expired",
"auth_invalid",
"retry_started",
"sdk_error"
],
"transport": "WebSocket first; explicit bounded recovery to POST",
"transcriptRule": "Every transcript_delta segment is provider-confirmed, final, immutable, and append-only.",
"usageRule": "Usage is measured by the Worker, never submitted by the browser SDK.",
"privacyRule": "Never log raw audio, transcript text, signed identity, or provider secrets. Transcript text leaves Dictum only through a project-owner-configured signed webhook."
}Checklist
Production readiness before launch.
Create a project and copy the public project key.
Create a server-side signing key for identity tokens.
Configure and verify a signed HTTPS webhook if your backend needs completed transcripts.
Add one source label per website, app surface, or workflow.
Choose provider/model routing and save provider keys in the vault.
Use the authorized SDK bundle until the prepared npm release is activated.
Run checkCompatibility() before enabling the voice control.
Handle state_changed, transcript_delta, sdk_error, and relevant dedicated events.
Verify insertion behavior in every target input, textarea, and editor.
Offer retry() only for retryable network failures and destroy the session on unmount.
Monitor Worker-owned usage and error codes from the dashboard.