# SDK Guide

> Integrate Synento into your application using the JavaScript/TypeScript SDK.

Integrate Synento into your application using the JavaScript/TypeScript SDK.

## Installation

```bash
npm install @synento/node
```

```typescript
import { createSynentoClient } from "@synento/node";
import type { Session, Project, ConnectionToken } from "@synento/node";
```

## Initialization

```typescript
const synento = createSynentoClient({
  apiKey: "sk_live_xxxxx",
  baseUrl: "https://api.synento.com" // optional: SYNENTO_API_URL, then this
});
```

This client holds your secret API key, so it belongs on the server only. In the
browser, use [`@synento/client`](https://www.npmjs.com/package/@synento/client) and have your server
mint short-lived connection tokens for it.

## Retry Configuration

Enable automatic retry for transient failures (network errors, 5xx responses):

```typescript
synento.withRetry({ maxRetries: 3, baseDelayMs: 500 });
```

The retry uses exponential backoff with jitter. Check the current config:
```typescript
const options = synento.getRetryOptions(); // { maxRetries: 2, baseDelayMs: 500 }
```

## Session Methods

### Create a Session
```typescript
const session = await synento.createSession({ name: "My Meeting" });
console.log(session.id); // sess_abc123
```

### List Sessions
```typescript
const sessions = await synento.listSessions();
for (const s of sessions) {
  console.log(`${s.name} — ${s.startedAt ? "Started" : "Not started"} at ${s.createdAt}`);
}
```

### Get a Single Session
```typescript
const session = await synento.getSession("sess_abc123");
if (session) {
  console.log(`Session ended at: ${session.endedAt || "not yet"}`);
} else {
  console.log("Session not found"); // returns null for 404
}
```

### Toggle Session Visibility
```typescript
await synento.setSessionVisibility("sess_abc123", true); // make public
```

## Connection Token Methods

### Get a Live Streaming Token
```typescript
const token = await synento.createConnectionToken("sess_abc123", { userId: "alice" });
// → { accessToken: "...", expiresIn: 300, session: "sess_abc123" }
```

### Get a Replay Token
```typescript
const token = await synento.createConnectionToken("sess_abc123", {
  userId: "viewer",
  replay: true,
  fromTs: 0,     // start from beginning (use timeline API to find the right offset)
  speed: 1.0,    // playback speed (0.25 to 4)
});

// Connect WebSocket for replay
const wsUrl = synento.getWebSocketUrl("sess_abc123", token.accessToken, "viewer");
const ws = new WebSocket(wsUrl);

ws.onmessage = (event) => {
  const data = JSON.parse(event.data as string);
  if (data.stream_type.startsWith("media/")) {
    // Decode and render video frame from data.body (base64 JPEG)
  }
};
```

### Generate WebSocket URL
```typescript
const wsUrl = synento.getWebSocketUrl(sessionId, accessToken, "alice");
// → wss://host/ws?session=sess_abc123&access_token=...&user_id=alice
```

## Project Methods

### Create a Project
```typescript
const project = await synento.createProject("My App", "production");
// → { id: "...", name: "My App", orgId: "...", environment: "production" }
```

### List Projects
```typescript
const projects = await synento.listProjects();
// → [{ id, name, orgId, environment }, ...]
```

## Webhook Methods

### Create a Webhook
```typescript
const webhook = await synento.createWebhook(
  "https://example.com/webhooks/synento",
  ["session.created", "session.started", "session.ended"]
);

// Events: session.created, session.started, session.ended, recording.ready, export.completed
```

### List Webhooks
```typescript
const webhooks = await synento.listWebhooks();
// → [{ id, projectId, url, events: string[], isActive }]
```

## Usage & Billing Methods

### Check Billing Limits
```typescript
const limits = await synento.getBillingLimits();
if (limits.isBlocked) {
  console.error(`Blocked: ${limits.statusMessage}`);
}

// Access individual fields
console.log(`Minutes used: ${limits.streamingMinutesUsed}`);
console.log(`Has subscription: ${limits.hasActiveSubscription}`);
```

### Get Billing Summary with Cost Breakdown
```typescript
const summary = await synento.getBillingSummary();
// → { streamingMinutesUsed, storageBytesUsed, costBreakdown?: { streamingCost, storageCost, totalDue } }
```

### Get Usage Records
```typescript
const records = await synento.getUsage();
// → [{ id, projectId, sessionId, usageType: "streaming_minutes"|"storage_bytes", value, recordedAt }]
```

## API Key Management

### List Keys (metadata only — no secrets)
```typescript
const keys = await synento.listApiKeys();
// → [{ id, projectId, createdAt }]
```

### Create a New Key (returns the raw secret)
```typescript
const result = await synento.createApiKey();
// → { id: "...", secret: "sk_live_xxxxx" } ← show this ONCE
```

### Revoke a Key (deletes it)
```typescript
await synento.revokeApiKey("key_id_here"); // deletes the key permanently
// → true (on success)
```

### Restricted keys

A restricted key (`rk_live_…`) works with the same client for everything an
integration does — `createSession`, `createConnectionToken` and
`getSessionManifest` — and nothing else. It is what a coding agent connected
over [MCP](https://synento.com/docs/guides/mcp.md) writes into your environment:

```bash
SYNENTO_API_KEY=rk_live_…
SYNENTO_API_URL=https://api.synento.com
SYNENTO_PROJECT_ID=…
```

### Check that media arrived
```typescript
const manifest = await synento.getSessionManifest(session.id);
// A participant whose tracks include "cam" and "mic" really published both.
const alice = manifest.participants.find((p) => p.user_id === "alice");
const kinds = alice?.tracks.map((t) => t.kind) ?? [];
```

## Advanced Patterns

### Full Session Lifecycle in One Block
```typescript
const project = await synento.createProject("Live Q&A");
const session = await synento.createSession({ name: "Q&A Session #5" });
await synento.setSessionVisibility(session.id, true); // make it public

const hostToken = await synento.createConnectionToken(session.id, { userId: "host" });
const guestToken = await synento.createConnectionToken(session.id, { userId: "guest-1" });

// Share URLs with participants (host token for broadcasting, guest for joining)
const hostWsUrl = synento.getWebSocketUrl(session.id, hostToken.accessToken, "host");
const guestWsUrl = synento.getWebSocketUrl(session.id, guestToken.accessToken, "guest-1");
```

### Error Handling
```typescript
import { SynentoError } from "@synento/node";

try {
  const session = await synento.createSession({ name: "Test" });
} catch (err) {
  if (err instanceof SynentoError && err.statusCode === 401) {
    console.error("Invalid or expired API key");
  } else if (err instanceof SynentoError && err.statusCode === 403) {
    console.error("Project access denied");
  } else if (err instanceof SynentoError && err.statusCode === 429) {
    console.error("Rate limited — retry after delay");
  } else {
    console.error("Unexpected error:", err.message);
  }
}
```

### Reconnecting After Token Expiry
```typescript
async function getReconnectToken(client: SynentoClient, sessionId: string): Promise<ConnectionToken> {
  const token = await client.createConnectionToken(sessionId, { userId: "reconnect-user" });
  
  // Token expires after `token.expiresIn` seconds — regenerate before expiry
  setTimeout(async () => {
    const newToken = await getReconnectToken(client, sessionId);
    // Update your WebSocket URL or reconnect...
  }, token.expiresIn * 800); // refresh at 80% of TTL
  
  return token;
}
```
