npm package: @scribeberry/sdk
Description: Official Scribeberry SDK — Medical transcription, AI note generation, and realtime speech-to-text. Works in Node.js and browsers. Includes React hooks.
Author: Scribeberry
Latest version: 0.3.0

# @scribeberry/sdk

Official Scribeberry SDK for Node.js and browsers. AI-powered medical
transcription, note generation, and realtime speech-to-text.

## Installation

```bash
npm install @scribeberry/sdk
# or
pnpm add @scribeberry/sdk
# or
yarn add @scribeberry/sdk
```

## Quick Start — Server-Side (Node.js)

```typescript
import { Scribeberry } from '@scribeberry/sdk';

const sb = new Scribeberry({
  apiKey: 'sk_live_...',
  baseUrl: 'https://sandbox.api.scribeberry.com', // omit for production
});

// List templates
const { items } = await sb.templates.list();
console.log(items.map((t) => t.name));

// Generate a note from text
const result = await sb.notes.generate({
  templateId: items[0].id,
  conversationText: 'Patient presents with chest pain...',
});
console.log(result.note.markdown);
```

## Quick Start — Browser (Realtime Transcription)

Realtime transcription in the browser uses **temporary tokens** so your API key
is never exposed client-side. The SDK manages token lifecycle automatically.

### Step 1: Server creates a token endpoint

```typescript
// server.ts (Node.js)
import { Scribeberry } from '@scribeberry/sdk';

const sb = new Scribeberry({ apiKey: 'sk_live_...' });

app.post('/api/realtime-token', async (req, res) => {
  const { token, expiresAt } = await sb.realtime.createToken({
    expiresInSeconds: 3600, // 1 hour max
  });
  res.json({ token, expiresAt });
});
```

### Step 2: Browser uses a token callback

```typescript
// client.ts (browser)
import { Scribeberry } from '@scribeberry/sdk';

// Provide a callback — SDK fetches tokens automatically and refreshes before expiry
const sb = new Scribeberry({
  getRealtimeToken: async () => {
    const res = await fetch('/api/realtime-token', { method: 'POST' });
    return res.json(); // { token, expiresAt }
  },
});

// Start a realtime transcription session
const session = sb.realtime.transcribe({
  language: 'en-US',
  enableDiarization: true,
});

// Listen for events
session.on('partial', (text) => {
  // Interim text (updates rapidly as you speak)
  document.getElementById('interim').textContent = text;
});

session.on('final', (segment) => {
  // Confirmed text (stable, won't change)
  document.getElementById('transcript').textContent += segment.text + ' ';
});

session.on('error', (err) => console.error(err));

// Stream audio from microphone
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const audioCtx = new AudioContext({ sampleRate: 16000 });
const source = audioCtx.createMediaStreamSource(stream);
const processor = audioCtx.createScriptProcessor(4096, 1, 1);

processor.onaudioprocess = (e) => {
  const float32 = e.inputBuffer.getChannelData(0);
  const int16 = new Int16Array(float32.length);
  for (let i = 0; i < float32.length; i++) {
    int16[i] = Math.max(
      -32768,
      Math.min(32767, Math.round(float32[i] * 32767)),
    );
  }
  session.sendAudio(int16.buffer);
};

source.connect(processor);
processor.connect(audioCtx.destination);

// When done
const result = await session.stop();
console.log(result.transcript);
console.log(`Duration: ${result.durationSeconds}s`);
```

## API Reference

### `new Scribeberry(config)`

| Parameter                 | Type       | Required | Description                                                       |
| ------------------------- | ---------- | -------- | ----------------------------------------------------------------- |
| `config.apiKey`           | `string`   | \*       | API key (`sk_test_*`/`sk_live_*`) or temp token (`sb_rt_*`)       |
| `config.getRealtimeToken` | `function` | \*       | Async callback returning `{ token, expiresAt }` — for browser use |
| `config.baseUrl`          | `string`   |          | API URL. Default: `https://api.scribeberry.com`                   |
| `config.timeout`          | `number`   |          | Request timeout in ms. Default: `30000`                           |

\* Provide either `apiKey` or `getRealtimeToken` (or both). For browser
realtime, `getRealtimeToken` is recommended.

---

### Templates

#### `sb.templates.list(options?)`

List all templates (paginated).

```typescript
const { items, meta } = await sb.templates.list({ page: 1, pageSize: 20 });
```

| Option      | Type              | Description             |
| ----------- | ----------------- | ----------------------- |
| `page`      | `number`          | Page number (1-indexed) |
| `pageSize`  | `number`          | Items per page          |
| `sortBy`    | `string`          | Field to sort by        |
| `sortOrder` | `'asc' \| 'desc'` | Sort direction          |

#### `sb.templates.get(id)`

Get a single template by ID.

#### `sb.templates.create(options)`

Create a new template.

#### `sb.templates.delete(id)`

Delete a template.

---

### Notes

#### `sb.notes.generate(options)`

Generate a medical note from text or audio.

```typescript
const result = await sb.notes.generate({
  templateId: 'template-id',
  conversationText: 'Patient presents with...',
});

console.log(result.note.markdown);
console.log(result.note.structured); // { "Chief Complaint": "...", ... }
```

| Option                 | Type                          | Required | Description                      |
| ---------------------- | ----------------------------- | -------- | -------------------------------- |
| `templateId`           | `string`                      | ✅       | Template to use                  |
| `conversationText`     | `string`                      |          | Text to generate from            |
| `audioUrl`             | `string`                      |          | Audio URL to transcribe first    |
| `sourceLanguage`       | `string`                      |          | Language code (default: `en-US`) |
| `transcriptionQuality` | `'low' \| 'medium' \| 'high'` |          | Quality (default: `high`)        |
| `context`              | `Record<string, string>`      |          | Additional context               |

---

### Realtime Transcription

#### `sb.realtime.createToken(options?)` _(server-side only)_

Create a temporary token for browser-side WebSocket access.

```typescript
const { token, wsUrl, expiresAt } = await sb.realtime.createToken({
  expiresInSeconds: 3600,
});
```

| Option             | Type     | Description                               |
| ------------------ | -------- | ----------------------------------------- |
| `expiresInSeconds` | `number` | Token lifetime (default: 3600, max: 3600) |

**Returns:** `{ token: string, wsUrl: string, expiresAt: string }`

#### `sb.realtime.transcribe(config?)`

Start a realtime transcription session via WebSocket.

```typescript
const session = sb.realtime.transcribe({
  language: 'en-US',
  enableDiarization: true,
  templateId: 'template-id', // optional: auto-generate note on stop
});
```

| Option              | Type      | Description                              |
| ------------------- | --------- | ---------------------------------------- |
| `language`          | `string`  | Language code (default: `en-US`)         |
| `enableDiarization` | `boolean` | Speaker identification (default: `true`) |
| `templateId`        | `string`  | Auto-generate note on stop               |

#### Session Events

```typescript
session.on('partial', (text: string, speaker?: number) => {});
session.on('final', (segment: TranscriptSegment) => {});
session.on('endpoint', () => {});
session.on('started', (sessionId: string) => {});
session.on('stopped', (result: RealtimeSessionResult) => {});
session.on('note', (note: Note) => {});
session.on('error', (error: Error) => {});
```

| Event      | Description                                             |
| ---------- | ------------------------------------------------------- |
| `partial`  | Interim transcript (updates rapidly, replaces previous) |
| `final`    | Confirmed segment (stable text, accumulate these)       |
| `endpoint` | Natural speech pause detected                           |
| `started`  | Session ready to receive audio                          |
| `stopped`  | Session ended with final results                        |
| `note`     | Generated note (if `templateId` was set)                |
| `error`    | An error occurred                                       |

#### Session Methods

```typescript
session.sendAudio(data); // Send PCM audio chunk
session.sendStream(asyncIterable); // Stream from async source
session.pause(); // Pause (connection stays alive)
session.resume(); // Resume streaming
session.finalize(); // Flush pending audio
session.getTranscript(); // Full transcript text so far
session.getSegments(); // All confirmed segments
const result = await session.stop(); // Stop and get final result
```

---

## React Hooks

The SDK includes optional React bindings via `@scribeberry/sdk/react`. These
handle microphone access, audio processing, and WebSocket streaming with a
simple hook interface.

> **Peer dependency:** Requires `react >= 18`.

### `useTranscription(options)`

```tsx
import { useTranscription } from '@scribeberry/sdk/react';

function Recorder() {
  const {
    status,     // 'idle' | 'connecting' | 'recording' | 'error'
    transcript, // Full confirmed transcript string
    partial,    // Interim text (updates rapidly, not yet confirmed)
    segments,   // All confirmed TranscriptSegment objects
    error,      // Error message or null
    durationSeconds, // Session duration (available after stop)
    start,      // Start recording from microphone
    stop,       // Stop recording
    clear,      // Clear transcript and reset to idle
  } = useTranscription({
    getRealtimeToken: async () => {
      const res = await fetch('/api/realtime-token', { method: 'POST' });
      return res.json(); // { token, expiresAt }
    },
    language: 'en-US',           // optional, default: 'en-US'
    enableDiarization: true,     // optional, default: true
  });

  return (
    <div>
      <button onClick={status === 'recording' ? stop : start}>
        {status === 'recording' ? '⏹ Stop' : '🎙 Record'}
      </button>
      <p>
        {transcript}
        <span style={{ opacity: 0.5 }}>{partial}</span>
      </p>
      {transcript && <button onClick={clear}>Clear</button>}
    </div>
  );
}
```

| Option              | Type       | Required | Description                                                  |
| ------------------- | ---------- | -------- | ------------------------------------------------------------ |
| `getRealtimeToken`  | `function` | ✅       | Async callback returning `{ token, expiresAt }` from your server |
| `language`          | `string`   |          | Language code (default: `en-US`)                             |
| `enableDiarization` | `boolean`  |          | Speaker identification (default: `true`)                     |

The hook manages the full lifecycle: microphone permissions → AudioWorklet
processing → WebSocket streaming → React state updates. Your API key never
leaves the server — only short-lived `sb_rt_*` tokens are used in the browser.

---

## Audio Format Requirements

For realtime transcription, audio must be:

- **Format:** PCM 16-bit signed little-endian (`pcm_s16le`)
- **Sample rate:** 16,000 Hz
- **Channels:** 1 (mono)

---

## Error Handling

```typescript
import {
  AuthenticationError,
  RateLimitError,
  Scribeberry,
  ScribeberryError,
} from '@scribeberry/sdk';

try {
  await sb.templates.list();
} catch (error) {
  if (error instanceof AuthenticationError) {
    // Invalid or expired API key / token
  } else if (error instanceof RateLimitError) {
    // Too many requests
  } else if (error instanceof ScribeberryError) {
    console.error(`[${error.code}] ${error.message}`);
  }
}
```

---

## Environments

| Environment | Base URL                              | Key Prefix  |
| ----------- | ------------------------------------- | ----------- |
| Production  | `https://api.scribeberry.com`         | `sk_live_*` |
| Sandbox     | `https://sandbox.api.scribeberry.com` | `sk_test_*` |

---

## License

MIT © [Scribeberry](https://scribeberry.com)