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. 1. Create a Dictum project and copy the public key.
  2. 2. Mint a signed identity from your backend under the global, non-configurable seven-day maximum.
  3. 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.
}
Method
Contract
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 error

The browser denied microphone permission.

no_transcript{ reason, error }

No transcript was produced because audio was empty or contained no speech.

network_errorpublic error

A retryable network, timeout, or stream loss occurred.

audio_invalidpublic error

Captured 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 error

The account or user exhausted its usage quota.

auth_requiredpublic error

A signed customer identity is missing.

identity_expiredpublic error

The signed identity expired; the host must reload it.

auth_invalidpublic error

The 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

idle

The one-shot session is ready; no microphone capture is active.

requesting_mic

The browser is resolving permission and capture startup.

listening

WAV capture is active.

paused

Capture is paused while the same session and target remain locked.

retrying

An explicit POST retry is running from retained WAV audio.

transcribing

Capture ended and the final transcript is completing.

failed

A 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.

Code
Meaning
Product response
microphone_permission_denied

User or browser denied microphone permission.

Explain browser permission settings, then create a new session after access is granted.

microphone_not_found

No microphone is available to the browser.

Ask the user to connect or select a microphone, then create a new session.

browser_unsupported

The selected capture path needs browser capabilities that are unavailable.

Disable voice input and ask the user to update their browser.

sdk_disabled

Dictum is disabled for this project.

Keep voice input disabled and ask the project owner to enable the SDK.

network_error

The browser could not reach the Dictum edge service.

Offer retry() only while retryable is true.

stream_lost

The active transcription connection was interrupted.

Keep the retained audio and offer the explicit POST retry.

transcription_failed

The provider could not complete the transcription.

Preserve the host input and let the user start a new session.

transport_response_invalid

The transcription transport returned an invalid success response.

Do not retry automatically; report the protocol failure to the service owner.

empty_audio

The completed session contains no usable audio.

Keep the target intact and let the user start a new session.

no_speech_detected

The Worker completed without a transcript.

Keep the target intact and let the user start a new session.

quota_exceeded

The project has reached its current usage limit.

Disable capture and link to billing or usage controls.

unsupported_audio_type

The capture adapter supplied audio outside the WAV contract.

Fix the adapter to emit audio/wav and create a new session.

audio_too_large

The transcription audio payload exceeded the accepted request size.

Keep the target intact and start a shorter dictation.

rate_limited

The service is temporarily rate limited.

Offer retry() only while retryable is true.

timeout

The transcription request stopped making network progress.

Offer retry() only while retryable is true.

aborted_by_user

The user or host stopped the session.

Treat it as a deliberate cancellation and leave the target intact.

client_not_authenticated

The signed customer identity is missing.

Sign the user in, fetch a signed identity, and create a new session.

identity_expired

The signed customer identity has expired.

Reload the page with a freshly signed identity.

config_response_invalid

The project configuration response is incomplete or invalid.

Keep voice input disabled and report the server configuration failure.

session_response_invalid

The session service returned an invalid success response.

Do not retry automatically; report the protocol failure to the service owner.

auth_config_error

The 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.dev

Project, sites, models, provider keys, signing keys, account, and usage.

SDK API

https://dictum-sdk-api.agentcore.workers.dev

Browser-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/dictumflow

Checkout, billing status, and customer portal.

Dashboard account API

/api/dashboard/account

Public 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.

1

Create a project and copy the public project key.

2

Create a server-side signing key for identity tokens.

3

Configure and verify a signed HTTPS webhook if your backend needs completed transcripts.

4

Add one source label per website, app surface, or workflow.

5

Choose provider/model routing and save provider keys in the vault.

6

Use the authorized SDK bundle until the prepared npm release is activated.

7

Run checkCompatibility() before enabling the voice control.

8

Handle state_changed, transcript_delta, sdk_error, and relevant dedicated events.

9

Verify insertion behavior in every target input, textarea, and editor.

10

Offer retry() only for retryable network failures and destroy the session on unmount.

11

Monitor Worker-owned usage and error codes from the dashboard.

Dashboard

Or use your email