# Dbrij API Documentation

Full reference for the Dbrij public API: authentication, meetings, chat, messages and webhooks.

Canonical page: https://dbrij.com/developers/docs

## Introduction

The Dbrij Developer API lets you embed real time **Meetings** (audio/video on LiveKit) and **Chat** into your own product. You operate on behalf of your own end users, Dbrij provisions a managed identity for each, so meetings, chat authorship and participant lists all map back to your user ids.

Base URL`https://api.dbrij.com/api`

Format`JSON in, JSON out`

Auth`Authorization: Bearer <key>`

Every endpoint is relative to that base. All requests and responses are JSON. Authenticate with an API key (see Authentication).

### Create a meeting

Request

```
POST /meetings
{
  "title": "Sales demo",
  "mode": "video",
  "host": { "externalUserId": "user_42", "displayName": "Jane" },
  "participants": [{ "externalUserId": "user_99", "displayName": "Sam" }]
}
```

Response

```
{
  "success": true,
  "data": {
    "id": "b1f2…",
    "status": "live",
    "mode": "video",
    "title": "Sales demo",
    "code": "abc-defg-hij",
    "scheduledFor": null,
    "startedAt": "2026-06-29T12:00:00.000Z",
    "endedAt": null,
    "isRecorded": false,
    "recordingUrl": null,
    "createdAt": "2026-06-29T12:00:00.000Z"
  }
}
```

### Endpoints

[POST`/meetings`Create a meeting](https://dbrij.com/developers/docs#meetings)[POST`/conversations`Create a conversation](https://dbrij.com/developers/docs#chat)[POST`/sms`Send an SMS](https://dbrij.com/developers/docs#sms)[GET`/support/desks`List desks](https://dbrij.com/developers/docs#support)

Use the sections menu to browse each area, every endpoint lists its parameters, an example request, and an example response.

## Quickstart & SDK

The official client is a single, zero dependency file (Node 18+ and browsers). [Download dbrij-sdk.js](https://dbrij.com/dbrij-sdk.js) and drop it into your project.

1 · Initialise

```
import { DbrijClient } from './dbrij-sdk.js';

const dbrij = new DbrijClient({
  apiKey: process.env.DBRIJ_API_KEY,   // a key from your Developer API dashboard
  baseUrl: 'https://api.dbrij.com/api',
});
```

2 · Start a meeting + get a join token

```
const meeting = await dbrij.createMeeting({
  title: 'Onboarding call',
  mode: 'video',
  host: { externalUserId: 'user_42', displayName: 'Jane' },
});

const { url, token } = await dbrij.mintToken(meeting.id, {
  externalUserId: 'user_99', displayName: 'Sam',
});  // → hand url + token to the LiveKit client SDK
```

3 · Open a chat and send a message

```
const convo = await dbrij.createConversation({
  type: 'direct',
  members: [{ externalUserId: 'user_42' }, { externalUserId: 'user_99' }],
});

await dbrij.sendMessage(convo.id, {
  author: { externalUserId: 'user_42' },
  body: 'Welcome aboard! 👋',
});
```

Errors throw a `DbrijError` with `.status` + `.body`. The file also exports `verifyWebhook(rawBody, header, secret)`.

### Glass in a native app

Tracking a website with Glass is one script tag. For a React Native app, use the `@dbrij/glass-native` package instead: sessions, screen views, taps and custom events land in the same Glass dashboard, with device shown as iOS or Android. Session replay stays web only; native sessions appear without a recording.

React Native

```
import AsyncStorage from '@react-native-async-storage/async-storage';
import { AppState, Platform } from 'react-native';
import { createGlass } from '@dbrij/glass-native';

export const glass = createGlass({
  siteKey: 'gk_your_site_key',          // Glass -> Sites
  platform: { os: Platform.OS === 'ios' ? 'ios' : 'android', appVersion: '1.4.2' },
  storage: AsyncStorage,
});

AppState.addEventListener('change', (s) => {
  if (s === 'active') glass.onForeground();
  else if (s === 'background') glass.onBackground();
});

// then, anywhere:
glass.screen('Checkout');
glass.track('signup', { plan: 'pro' });
glass.identify('user_1234');
```

The SDK batches events, persists an offline queue, honors the site's quota kill switch, and never throws into your app. Nothing is collected beyond what you call.

### Glass in Swift, Kotlin or Flutter

Pure native apps get the same tracking through a single drop-in file per platform, zero dependencies each: [glass-tracker.swift](https://dbrij.com/glass-tracker.swift), [GlassTracker.kt](https://dbrij.com/GlassTracker.kt) or [glass_tracker.dart](https://dbrij.com/glass_tracker.dart). Same API everywhere: screen, track, identify, tap. All of them batch, keep a bounded offline queue, honor the quota kill switch, and never throw into your app.

Swift (iOS)

```
let glass = GlassTracker(siteKey: "gk_your_site_key", appVersion: "1.4.2")
// lifecycle is observed automatically on iOS
glass.screen("Checkout")
glass.track("signup", props: ["plan": "pro"])
glass.identify("user_1234")
glass.tap(xPct: 48, yPct: 88, label: "buy-button")
```

Kotlin (Android)

```
val glass = GlassTracker(application, GlassTracker.Config(
  siteKey = "gk_your_site_key", appVersion = "1.4.2",
))
// lifecycle is observed automatically via ActivityLifecycleCallbacks
glass.screen("Checkout")
glass.track("signup", mapOf("plan" to "pro"))
glass.identify("user_1234")
glass.tap(48.0, 88.0, "buy-button")
```

Flutter (Dart)

```
final glass = GlassTracker(GlassConfig(
  siteKey: 'gk_your_site_key',
  platformOs: Platform.isIOS ? 'ios' : 'android',
  appVersion: '1.4.2',
));
// wire a WidgetsBindingObserver: resumed -> glass.onForeground(),
// paused -> glass.onBackground(); a NavigatorObserver calls glass.screen(name)
glass.track('signup', {'plan': 'pro'});
```

Sessions from native apps show up in the Glass dashboard with device iOS or Android. Session replay is web only; native sessions appear without a recording.

## Authentication

Authenticate every request with an API key from your [Developer API dashboard](https://dbrij.com/developers) (sign in with your normal Dbrij account), sent as a Bearer token.

Authorization header

```
Authorization: Bearer dbrij_live_xxxxxxxxxxxxxxxxxxxxxxxx
```

**Test keys** (`dbrij_test_…`) are never billed. SMS and Ping sends are checked and priced like live ones, then stopped: nothing reaches a phone, and the message comes back delivered with `"test": true` (a one time code also returns its `testCode` so you can call verify). Chat messages and meetings are real so you can watch them work, and capped so a test key cannot run production: 500 chat messages and 25 meetings a day, and a test meeting holds 2 people for 10 minutes. Email sent with a test key is simulated too, see Emails.

Use `test` keys while building and `live` keys in production. Keys carry **scopes**; a request missing a required scope returns `403`. A key created with the `emails:send` scope is **bound to one sender address**, chosen at creation from the addresses your account can send as, see Emails below.

Every success response is wrapped in an envelope: `{ "success": true, "data": … }`. Read your payload from `data`, never from the top level, the Response examples throughout these docs show the envelope. Errors are wrapped the same way with `success: false` (see Errors and usage).

|  |  |  |
| --- | --- | --- |
| meetings:read | scope | List/read meetings, participants, summaries. |
| meetings:write | scope | Create/end/cancel meetings, mute/remove participants. |
| meetings:tokens | scope | Mint LiveKit join tokens. |
| chat:read | scope | List/read conversations and messages. |
| chat:write | scope | Create conversations, send messages, manage members, react. |
| emails:send | scope | Send / schedule / cancel emails from your granted domains. |
| emails:read | scope | List/read sent emails and your sendable domains. |

## Managed users

You never create Dbrij user accounts. Instead, every call carries an `externalUserId`, your own identifier for one of your end users. Dbrij transparently provisions and reuses a managed identity behind it.

Most endpoints take an **actor reference** wherever a person is needed (a host, message author, member or reactor):

|  |  |  |
| --- | --- | --- |
| externalUserId* | string | Your stable id for the end user. |
| displayName | string | The user's name (kept fresh on each call). |
| avatarUrl | string | URL to the user's avatar. |

Actor reference

```
{ "externalUserId": "user_42", "displayName": "Jane Doe", "avatarUrl": "https://…/jane.png" }
```

## Meetings

Standalone audio/video meetings, created with an arbitrary host + participants, instant or scheduled, not tied to a conversation. (For a call inside a chat, see Calls.) Tokens are minted per end user for the LiveKit client SDK.

An actor reference: { "externalUserId": string, "displayName"?: string, "avatarUrl"?: string }.

POST`/meetings`meetings:write

Create a meeting

Create an instant meeting (starts live) or schedule one for later. The host and any participants are your own end users.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| title | string | Display title. Defaults to a generated name. |
| mode | 'video' \| 'voice' | Camera call or audio only. Default 'video'. |
| scheduledFor | string (ISO-8601) | If set in the future, the meeting is scheduled rather than started now. |
| durationMinutes | number | Planned length cap (5 to 1440). |
| host | ActorRef | The hosting end user. Defaults to your app actor. |
| participants | ActorRef[] | End users invited up front (optional, you can mint tokens for anyone later). |

Request

```
curl -X POST https://api.dbrij.com/api/meetings \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "title": "Sales demo",
  "mode": "video",
  "host": { "externalUserId": "user_42", "displayName": "Jane" },
  "participants": [{ "externalUserId": "user_99", "displayName": "Sam" }]
}'
```

Request body

```
{
  "title": "Sales demo",
  "mode": "video",
  "host": { "externalUserId": "user_42", "displayName": "Jane" },
  "participants": [{ "externalUserId": "user_99", "displayName": "Sam" }]
}
```

Response 200

```
{
  "success": true,
  "data": {
    "id": "b1f2…",
    "status": "live",
    "mode": "video",
    "title": "Sales demo",
    "code": "abc-defg-hij",
    "scheduledFor": null,
    "startedAt": "2026-06-29T12:00:00.000Z",
    "endedAt": null,
    "isRecorded": false,
    "recordingUrl": null,
    "createdAt": "2026-06-29T12:00:00.000Z"
  }
}
```

GET`/meetings`meetings:read

List meetings

Returns the 50 most recent meetings created by your app, newest first.

Request

```
curl -X GET https://api.dbrij.com/api/meetings \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": [ { "id": "b1f2…", "status": "ended", "mode": "video", "title": "Sales demo", … } ]
}
```

GET`/meetings/:id`meetings:read

Get a meeting

Fetch a single meeting, including its live participant count.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The meeting id. |

Request

```
curl -X GET https://api.dbrij.com/api/meetings/:id \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": { "id": "b1f2…", "status": "live", "participantCount": 2, … }
}
```

POST`/meetings/:id/end`meetings:write

End a meeting

End a live meeting. Everyone is disconnected and the room is closed.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The meeting id. |

Request

```
curl -X POST https://api.dbrij.com/api/meetings/:id/end \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": { "id": "b1f2…", "status": "ended", "endedAt": "2026-06-29T12:30:00.000Z", … }
}
```

POST`/meetings/:id/cancel`meetings:write

Cancel a meeting

Cancel a scheduled meeting before it starts.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The meeting id. |

Request

```
curl -X POST https://api.dbrij.com/api/meetings/:id/cancel \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": { "id": "b1f2…", "status": "cancelled", … }
}
```

POST`/meetings/:id/tokens`meetings:tokens

Mint a join token

The embed primitive. Returns a LiveKit URL + token for one of your end users. Hand both to the LiveKit client SDK in your app to join the call.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The meeting id. |

Request

```
curl -X POST https://api.dbrij.com/api/meetings/:id/tokens \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "externalUserId": "user_99", "displayName": "Sam", "canPublish": true }'
```

Request body

```
{ "externalUserId": "user_99", "displayName": "Sam", "canPublish": true }
```

Response 200

```
{
  "success": true,
  "data": {
    "url": "wss://your-project.livekit.cloud",
    "token": "eyJhbGciOi…",
    "identity": "user_99",
    "meeting": { "id": "b1f2…", "status": "live", "mode": "video" }
  }
}
```

GET`/meetings/:id/participants`meetings:read

List participants

The end users currently connected to the call.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The meeting id. |

Request

```
curl -X GET https://api.dbrij.com/api/meetings/:id/participants \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": [ { "externalUserId": "user_99", "name": "Sam" } ]
}
```

POST`/meetings/:id/participants/mute`meetings:write

Mute a participant

Mute an end user's microphone on the server (host control). Returns 204 No Content.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The meeting id. |

Request

```
curl -X POST https://api.dbrij.com/api/meetings/:id/participants/mute \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "externalUserId": "user_99" }'
```

Request body

```
{ "externalUserId": "user_99" }
```

Returns `204` (no body).

DELETE`/meetings/:id/participants/:externalUserId`meetings:write

Remove a participant

Kick an end user from the call and block their rejoin. Returns 204 No Content.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The meeting id. |
| externalUserId* | string (path) | The end user to remove. |

Request

```
curl -X DELETE https://api.dbrij.com/api/meetings/:id/participants/:externalUserId \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Returns `204` (no body).

GET`/meetings/:id/summary`meetings:read

Meeting summary

Attendance and metrics after the call (who joined, durations).

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The meeting id. |

Request

```
curl -X GET https://api.dbrij.com/api/meetings/:id/summary \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": { "meetingId": "b1f2…", "attendees": [ … ], "durationMs": 1800000 }
}
```

POST`/meetings/:id/recording`meetings:write

Start / stop recording

Toggle recording on a live meeting. When you stop (or the call ends), a recording.ready webhook fires with the URL once processing completes. Requires the server to have recording configured.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The meeting id. |
| on* | boolean | true to start, false to stop. |

Request

```
curl -X POST https://api.dbrij.com/api/meetings/:id/recording \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "on": true }'
```

Request body

```
{ "on": true }
```

Response 200

```
{
  "success": true,
  "data": { "id": "b1f2…", "status": "live", "isRecorded": true, … }
}
```

## Calls

A **call** is a real time audio/video session that lives **inside a conversation**: ring a 1:1 chat, or start a group call. Unlike a standalone **meeting**, a call is bound to the chat: its audience is the conversation's members, and a "call started" card appears in the thread.

Typical flow: open a conversation → start a call in it → mint a join token for each member.

POST`/conversations/:id/calls`meetings:write

Start a call in a conversation

Rings the conversation members and returns a Meeting object. The initiator must be a member of the conversation.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The conversation id. |
| initiator* | ActorRef | The end user starting the call (a member of the conversation). |
| mode | 'video' \| 'voice' | Camera call or audio only. Default 'video'. |
| title | string | Optional title (defaults to the chat name). |

Request

```
curl -X POST https://api.dbrij.com/api/conversations/:id/calls \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "initiator": { "externalUserId": "user_42" },
  "mode": "video"
}'
```

Request body

```
{
  "initiator": { "externalUserId": "user_42" },
  "mode": "video"
}
```

Response 200

```
{
  "success": true,
  "data": { "id": "b1f2…", "status": "live", "mode": "video", "title": "Project room", … }
}
```

Then join + control it like any meeting

The response is a **Meeting** object. Use its `id` with the Meetings endpoints to join and manage the call: `POST /meetings/:id/tokens` (join), `/participants`, `/participants/mute`, `/recording`, `/end`.

SDK: call a chat, then join

```
// 1) An existing conversation (or create one)
const convo = await dbrij.createConversation({
  type: 'direct',
  members: [{ externalUserId: 'user_42' }, { externalUserId: 'user_99' }],
});

// 2) Start the call (rings the members)
const call = await dbrij.startCall(convo.id, {
  initiator: { externalUserId: 'user_42' },
  mode: 'video',
});

// 3) Mint a join token for each member → hand to the LiveKit SDK
const { url, token } = await dbrij.mintToken(call.id, { externalUserId: 'user_99' });
```

## Chat

Conversations and messages among your end users.

An actor reference: { "externalUserId": string, "displayName"?: string, "avatarUrl"?: string }.

POST`/conversations`chat:write

Create a conversation

Open a direct (1:1) or group conversation among your end users. For a direct chat pass exactly two members; for a group pass a title.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| type* | 'direct' \| 'group' | Conversation kind. |
| title | string | Required for a group. |
| members* | ActorRef[] | The end users in the conversation (exactly 2 for direct). |

Request

```
curl -X POST https://api.dbrij.com/api/conversations \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "type": "direct",
  "members": [
    { "externalUserId": "user_42" },
    { "externalUserId": "user_99" }
  ]
}'
```

Request body

```
{
  "type": "direct",
  "members": [
    { "externalUserId": "user_42" },
    { "externalUserId": "user_99" }
  ]
}
```

Response 200

```
{
  "success": true,
  "data": { "id": "c7a1…", "type": "direct", "title": "Sam" }
}
```

GET`/conversations`chat:read

List conversations

Your app's 50 most recent conversations.

Request

```
curl -X GET https://api.dbrij.com/api/conversations \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": [ { "id": "c7a1…", "type": "group", "title": "Project room" } ]
}
```

GET`/conversations/:id`chat:read

Get a conversation

Fetch a conversation with its member list (mapped back to your external user ids).

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The conversation id. |

Request

```
curl -X GET https://api.dbrij.com/api/conversations/:id \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": {
    "id": "c7a1…",
    "type": "group",
    "title": "Project room",
    "memberCount": 3,
    "members": [ { "externalUserId": "user_42", "name": "Jane", "avatarUrl": null } ]
  }
}
```

POST`/conversations/:id/members`chat:write

Add members

Add end users to a group conversation.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The conversation id. |

Request

```
curl -X POST https://api.dbrij.com/api/conversations/:id/members \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "members": [ { "externalUserId": "user_77", "displayName": "Alex" } ] }'
```

Request body

```
{ "members": [ { "externalUserId": "user_77", "displayName": "Alex" } ] }
```

Response 200

```
{
  "success": true,
  "data": { "id": "c7a1…", "type": "group", "memberCount": 4, "members": [ … ] }
}
```

DELETE`/conversations/:id/members/:externalUserId`chat:write

Remove a member

Remove an end user from a group. Returns 204 No Content.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The conversation id. |
| externalUserId* | string (path) | The end user to remove. |

Request

```
curl -X DELETE https://api.dbrij.com/api/conversations/:id/members/:externalUserId \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Returns `204` (no body).

POST`/conversations/:id/messages`chat:write

Send a message

Post a message authored by one of your end users, optionally with media attachments. Fires a message.created webhook so you can deliver it to other clients.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The conversation id. |
| author* | ActorRef | The end user sending the message. |
| body* | string | Message text (may be empty when sending media). |
| attachments | Attachment[] | Up to 10 media items (see the Media tab). Each: { name, kind, url, sizeLabel?, durationMs?, mimeType?, width?, height?, thumbnailUrl? }. |
| parentMessageId | string | Post as a threaded reply. |
| quotedMessageId | string | Quote another message. |

Request

```
curl -X POST https://api.dbrij.com/api/conversations/:id/messages \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "author": { "externalUserId": "user_42" },
  "body": "Here's the deck 👇",
  "attachments": [
    { "name": "deck.pdf", "kind": "file", "url": "https://…/deck.pdf", "sizeLabel": "2.1 MB" }
  ]
}'
```

Request body

```
{
  "author": { "externalUserId": "user_42" },
  "body": "Here's the deck 👇",
  "attachments": [
    { "name": "deck.pdf", "kind": "file", "url": "https://…/deck.pdf", "sizeLabel": "2.1 MB" }
  ]
}
```

Response 200

```
{
  "success": true,
  "data": {
    "id": "m9c2…",
    "conversationId": "c7a1…",
    "authorExternalUserId": "user_42",
    "body": "Hey, are we still on for 3pm?",
    "createdAt": "2026-06-29T12:05:00.000Z",
    "parentMessageId": null,
    "reactions": []
  }
}
```

GET`/conversations/:id/messages`chat:read

List messages

Paginated message history, oldest last. Use the timestamp of the earliest message as the next `before` cursor.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The conversation id. |

QUERY PARAMETERS

|  |  |  |
| --- | --- | --- |
| limit | number | Max messages (1 to 100, default 30). |
| before | string (ISO-8601) | Return messages created before this time. |

Request

```
curl -X GET https://api.dbrij.com/api/conversations/:id/messages \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": [ { "id": "m9c2…", "authorExternalUserId": "user_42", "body": "…", "createdAt": "…" } ]
}
```

POST`/messages/:id/reactions`chat:write

Toggle a reaction

Add or remove an emoji reaction on a message, as one of your end users.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The message id. |

Request

```
curl -X POST https://api.dbrij.com/api/messages/:id/reactions \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "user": { "externalUserId": "user_99" }, "emoji": "👍" }'
```

Request body

```
{ "user": { "externalUserId": "user_99" }, "emoji": "👍" }
```

Response 200

```
{
  "success": true,
  "data": [ { "emoji": "👍", "count": 1, "mine": true } ]
}
```

PATCH`/messages/:id`chat:write

Edit a message

Edit a message's text. Only the original author may edit.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The message id. |

Request

```
curl -X PATCH https://api.dbrij.com/api/messages/:id \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "user": { "externalUserId": "user_42" }, "body": "Edited text" }'
```

Request body

```
{ "user": { "externalUserId": "user_42" }, "body": "Edited text" }
```

Response 200

```
{
  "success": true,
  "data": { "id": "m9c2…", "body": "Edited text", … }
}
```

POST`/messages/:id/delete`chat:write

Delete a message

Redact (soft delete) a message. Returns 204 No Content.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The message id. |

Request

```
curl -X POST https://api.dbrij.com/api/messages/:id/delete \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "user": { "externalUserId": "user_42" } }'
```

Request body

```
{ "user": { "externalUserId": "user_42" } }
```

Returns `204` (no body).

POST`/conversations/:id/read`chat:write

Mark read

Mark a conversation read up to now, as one of your end users (emits a read receipt). Returns 204.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The conversation id. |

Request

```
curl -X POST https://api.dbrij.com/api/conversations/:id/read \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "user": { "externalUserId": "user_99" } }'
```

Request body

```
{ "user": { "externalUserId": "user_99" } }
```

Returns `204` (no body).

GET`/conversations/:id/receipts`chat:read

Read receipts

Where each member has read to.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The conversation id. |

Request

```
curl -X GET https://api.dbrij.com/api/conversations/:id/receipts \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": [ { "externalUserId": "user_42", "name": "Jane", "avatarUrl": null, "lastReadAt": "2026-06-29T12:06:00.000Z" } ]
}
```

## Emails

Email is **Dbrij Send**: its own product with its own volume plans and its own complete documentation. The same API keys work there; a key created with the `emails:send` scope is bound to the sender address it sends as.

Everything email lives in the Send docs: sending and batching, templates, audiences, broadcasts, automations, suppressions, receiving, webhooks, stats, and limits.

[Read the Dbrij Send documentation →](https://dbrij.com/products/send/docs)

## Sign in with Dbrij

Let people sign into **your** product with their Dbrij account: standard OAuth 2.0 authorization code flow, the same shape as any standard social sign in. Turn it on per app from the dashboard's Sign in tab: register your redirect URIs and you get a `client_id` plus a `client_secret` (shown once). Your app's name is what the consent screen shows.

**The flow.** 1 · Send the person to the authorize URL. 2 · They approve on the Dbrij consent screen and come back to your `redirect_uri` with a one time `code` (and your `state`). 3 · Exchange the code server side for tokens. 4 · Read the profile from userinfo. PKCE (S256) is supported and recommended.

1 · Send them to authorize

```
https://api.dbrij.com/api/oauth/authorize
  ?client_id=dbrij_client_xxxxxxxx
  &redirect_uri=https://yourapp.com/auth/dbrij/callback
  &response_type=code
  &scope=profile%20email
  &state=a-random-string-you-verify-later
```

2 · They come back with a code

```
https://yourapp.com/auth/dbrij/callback?code=dbrij_code_…&state=a-random-string-you-verify-later
```

3 · Exchange it (server side)

```
curl -X POST https://api.dbrij.com/api/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=authorization_code \
  -d code=dbrij_code_… \
  -d redirect_uri=https://yourapp.com/auth/dbrij/callback \
  -d client_id=dbrij_client_xxxxxxxx \
  -d client_secret=dbrij_cs_xxxxxxxx

# → { "access_token": "dbrij_cat_…", "token_type": "Bearer", "expires_in": 3600,
#     "refresh_token": "dbrij_crt_…", "scope": "profile email" }
```

4 · Read who signed in

```
curl https://api.dbrij.com/api/oauth/userinfo \
  -H "Authorization: Bearer dbrij_cat_…"

# → { "sub": "b1f2…", "name": "Jane Doe", "given_name": "Jane", "family_name": "Doe",
#     "preferred_username": "jane", "picture": "https://…",
#     "email": "jane@dmyil.com", "email_verified": true }
```

### Scopes

|  |  |  |
| --- | --- | --- |
| profile | scope | Name, username and photo. Included in the default scope. |
| email | scope | The verified email on the account. Included in the default scope. |

### Refresh and revoke

Access tokens live one hour. Refresh with `grant_type=refresh_token` at the same token endpoint; each refresh token works once and the response carries its replacement. A rotated out refresh token presented again is treated as a leak: every token for that account and app is revoked on the spot. Revoke a token yourself with `POST /oauth/revoke` (body field `token`).

### The rules that keep it safe

|  |  |  |
| --- | --- | --- |
| redirect_uri | exact match | Must equal a registered URI character for character, at authorize AND at exchange. https only (localhost may use http). |
| code | one minute, one use | Exchange it immediately, server side. A second exchange fails and always verify state. |
| client_secret | server only | Never ship it in a browser or app binary. For public clients add PKCE: send code_challenge (S256) on authorize and code_verifier on exchange. |
| errors | RFC 6749 | The token endpoint answers the spec shapes: { "error": "invalid_grant", "error_description": "…" } with 400/401, not the platform envelope. |

People manage what they have connected under Settings, then Security, then Connected apps. Disconnecting there revokes every token your app holds for them, so treat a 401 from userinfo as "signed out".

## SMS

Send SMS from your own product. Sends work **immediately** as the platform sender, no registration, no waiting. Sending as **your own name** (what shows on the phone) is a per-name carrier registration and opens up from the dashboard's SMS tab when available there; until yours is approved, simply leave `senderId` out.

**Routes matter in Nigeria.** A large share of SIMs are DND-flagged and silently drop marketing-route messages. Messages sent as the platform sender always ride the DND-safe route, so OTPs, alerts and receipts reach every number. The `route` choice takes effect when a message goes out as your own approved sender ID: `transactional` (default) keeps the DND-safe route, `promotional` uses the standard marketing route.

Authenticate with an API key carrying the `sms:send` / `sms:read` scopes. Sends are metered per app per month as `sms_sent`, counted in **segments**: the free tier has a hard monthly allotment (a send past it returns `402`) while paid plans bill overage instead, and every request counts toward your plan's per minute rate ceiling (`429`).

POST`/sms`sms:send

Send an SMS

Send one SMS. Omit senderId to send as the platform default sender (works immediately); pass one of your approved sender IDs to send as your own name. Supports an Idempotency-Key header.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| to* | string | Destination number: +2348012345678, 2348012345678, or the 11-digit local 0-form (treated as Nigerian). |
| body* | string | The message. Up to 6 segments; you are billed per segment (see Segments below). |
| senderId | string | An approved sender ID on this app. Omit for the platform sender (which is always available). |
| route | 'transactional' \| 'promotional' | Default 'transactional', which reaches DND-flagged numbers (most Nigerian SIMs). Applies when sending as your own sender ID; the platform sender always rides the DND-safe route. |

Request

```
curl -X POST https://api.dbrij.com/api/sms \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "to": "2348012345678",
  "body": "Your order 1042 has shipped."
}'
```

Request body

```
{
  "to": "2348012345678",
  "body": "Your order 1042 has shipped."
}
```

Response 200

```
{
  "success": true,
  "data": {
    "id": "s7a1…",
    "to": "2348012345678",
    "senderId": null,
    "body": "Your order 1042 has shipped.",
    "segments": 1,
    "route": "transactional",
    "status": "sent",
    "otp": false,
    "sentAt": "2026-08-07T09:00:00.000Z",
    "deliveredAt": null,
    "lastError": null,
    "createdAt": "2026-08-07T09:00:00.000Z"
  }
}
```

POST`/sms/otp/send`sms:send

Send a one-time code

We generate the code, send it over the DND-safe route, and keep only a hash of it. Verify with the returned otpId. 5 wrong attempts void the code.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| to* | string | Destination number. |
| length | number | Code length, 4 to 8 digits. Default 6. |
| expiryMinutes | number | Minutes before it expires, 1 to 30. Default 10. |
| template | string | Message wording with {code} where the code goes. Default: "123456 is your <app> code. It expires in 10 minutes." |
| senderId | string | An approved sender ID. Omit for the platform default. |

Request

```
curl -X POST https://api.dbrij.com/api/sms/otp/send \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "to": "2348012345678" }'
```

Request body

```
{ "to": "2348012345678" }
```

Response 200

```
{
  "success": true,
  "data": {
    "otpId": "o2b4…",
    "to": "2348012345678",
    "expiresAt": "2026-08-07T09:10:00.000Z"
  }
}
```

POST`/sms/otp/verify`sms:send

Verify a one-time code

Check the code the person typed against its otpId. A code verifies once; after that (or after expiry, or 5 wrong attempts) it is dead and you send a new one.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| otpId* | string | From /sms/otp/send. |
| code* | string | What the person typed. |

Request

```
curl -X POST https://api.dbrij.com/api/sms/otp/verify \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "otpId": "o2b4…", "code": "482913" }'
```

Request body

```
{ "otpId": "o2b4…", "code": "482913" }
```

Response 200

```
{
  "success": true,
  "data": { "verified": true }
}
```

GET`/sms`sms:read

List SMS

Your app's sent messages, newest first. One-time code bodies are redacted.

QUERY PARAMETERS

|  |  |  |
| --- | --- | --- |
| limit | number | Max messages (1 to 100, default 20). |
| before | string | Cursor: an ISO-8601 time or a message id. |

Request

```
curl -X GET https://api.dbrij.com/api/sms \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": [ { "id": "s7a1…", "to": "2348012345678", "status": "delivered", "segments": 1, … } ]
}
```

GET`/sms/:id`sms:read

Get an SMS

Fetch one message, including its delivery status and any error.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The message id. |

Request

```
curl -X GET https://api.dbrij.com/api/sms/:id \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": { "id": "s7a1…", "status": "delivered", "deliveredAt": "2026-08-07T09:00:04.000Z", … }
}
```

GET`/sms/senders`sms:read

List sender IDs

The sender IDs registered on this app and where each one's carrier approval stands. Registration happens on the dashboard's SMS tab, when own-name sending is open.

Request

```
curl -X GET https://api.dbrij.com/api/sms/senders \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": [ { "senderId": "Acme", "status": "active" } ]
}
```

### Segments

An SMS is billed in carrier segments, and the response tells you what each message cost. Plain (GSM) text fits **160 characters in one segment**, or 153 per segment when the message spans several. Any character outside the GSM set, an emoji for instance, switches the whole message to Unicode, which fits only **70 characters per segment** (67 when concatenated). A message can be at most 6 segments.

### Idempotency

Pass an `Idempotency-Key` header (max 190 chars) on `POST /sms`. Retrying with the same key returns the original message instead of sending, and billing, a duplicate.

Idempotent send

```
curl -X POST https://api.dbrij.com/api/sms \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042-shipped" \
  -d '{ "to": "2348012345678",
        "body": "Your order 1042 has shipped." }'
```

### Webhook events

`sms.sent`, `sms.delivered` and `sms.failed` fire to your app's webhook URL in the same signed envelope as every other event. Delivery reports arrive from the carriers asynchronously, so `delivered` typically lands seconds after `sent`.

sms.delivered delivery

```
POST https://yourapp.com/webhooks/dbrij
X-Dbrij-Signature: sha256=7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069

{
  "id": "evt_…",
  "type": "sms.delivered",
  "createdAt": "2026-08-07T09:00:04.000Z",
  "data": {
    "smsId": "s7a1…",
    "to": "2348012345678",
    "senderId": null,
    "segments": 1,
    "otp": false
  }
}
```

## Support

Run customer support from inside your own product on **Dbrij Resolve**. Your app acts with its owner's standing: it sees the desks of the companies they belong to, opens tickets on a customer's behalf, reads the honest state (status, public timeline, the desk score) and relays replies and confirmations. Set the desk up in Resolve first; everything here rides on it.

Authenticate with an API key carrying the `support:read` / `support:write` scopes. Each ticket you open is metered per app per month as `support_tickets` against your plan: the free tier has a hard monthly allotment while paid plans bill overage instead, and every request counts toward your plan's per minute rate ceiling (`429`).

GET`/support/desks`support:read

List desks

Every Resolve desk your app can act on, across the companies its owner belongs to.

Request

```
curl -X GET https://api.dbrij.com/api/support/desks \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": [ { "id": "d4e5…", "name": "Acme Support", "code": "ACME", "organizationId": "o3b4…" } ]
}
```

GET`/support/desks/:deskId/score`support:read

Desk score

The desk’s honest numbers, machine readable: promises kept, confirmed closes, first reply speed, reopens and CSAT.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| deskId* | string (path) | The desk id. |

Request

```
curl -X GET https://api.dbrij.com/api/support/desks/:deskId/score \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

GET`/support/desks/:deskId/tickets`support:read

List tickets

Tickets on a desk, newest activity first (max 100). Filter to one customer with the email query.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| deskId* | string (path) | The desk id. |

QUERY PARAMETERS

|  |  |  |
| --- | --- | --- |
| email | string | Only tickets belonging to this customer email. |

Request

```
curl -X GET https://api.dbrij.com/api/support/desks/:deskId/tickets \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": [ { "id": "t9d1…", "reference": "ACME-42", "subject": "Payment failed on checkout", "status": "open", "awaitingReply": true, … } ]
}
```

POST`/support/desks/:deskId/tickets`support:write

Open a ticket

Open a ticket on a customer’s behalf from inside your own product. The customer is upserted by email on the desk. Metered as support_tickets, one per ticket created.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| deskId* | string (path) | The desk id. |
| email* | string | The customer’s email. |
| name | string | The customer’s name. |
| externalId | string | Your own user id for the customer, for joining on your side. |
| subject* | string | What the ticket is about. |
| body* | string | The customer’s first message. |

Request

```
curl -X POST https://api.dbrij.com/api/support/desks/:deskId/tickets \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "jane@example.com",
  "name": "Jane",
  "externalId": "user_42",
  "subject": "Payment failed on checkout",
  "body": "My card was charged but the order shows unpaid."
}'
```

Request body

```
{
  "email": "jane@example.com",
  "name": "Jane",
  "externalId": "user_42",
  "subject": "Payment failed on checkout",
  "body": "My card was charged but the order shows unpaid."
}
```

Response 200

```
{
  "success": true,
  "data": {
    "id": "t9d1…",
    "reference": "ACME-42",
    "subject": "Payment failed on checkout",
    "status": "open",
    "statusLabel": "Open",
    "awaitingReply": true,
    "firstReplyDueAt": "2026-07-01T10:00:00.000Z",
    "resolvedAt": null,
    "customerConfirmedAt": null,
    "createdAt": "2026-07-01T09:00:00.000Z",
    "lastMessageAt": "2026-07-01T09:00:00.000Z"
  }
}
```

GET`/support/tickets/:ticketId`support:read

Get a ticket

One ticket with its public conversation and timeline. Internal team notes never appear here.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| ticketId* | string (path) | The ticket id. |

Request

```
curl -X GET https://api.dbrij.com/api/support/tickets/:ticketId \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": {
    "id": "t9d1…",
    "reference": "ACME-42",
    "status": "resolved",
    "messages": [ { "id": "m1…", "from": "customer", "body": "…", "createdAt": "…" }, { "id": "m2…", "from": "team", "body": "…", "createdAt": "…" } ],
    "timeline": [ { "kind": "opened", "summary": "Ticket opened", "createdAt": "…" } ],
    …
  }
}
```

POST`/support/tickets/:ticketId/messages`support:write

Relay a customer reply

A reply the customer typed inside your product, landed on the ticket exactly like a portal reply.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| ticketId* | string (path) | The ticket id. |
| body* | string | The customer’s message. |

Request

```
curl -X POST https://api.dbrij.com/api/support/tickets/:ticketId/messages \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": { "ok": true }
}
```

POST`/support/tickets/:ticketId/confirm`support:write

Confirm the fix

The customer confirmed the fix inside your product: the honest close. Only a ticket in status resolved can be confirmed; it moves to closed and any linked task closes with it.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| ticketId* | string (path) | The ticket id. |

Request

```
curl -X POST https://api.dbrij.com/api/support/tickets/:ticketId/confirm \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": { "id": "t9d1…", "status": "closed", "customerConfirmedAt": "2026-07-02T08:30:00.000Z", … }
}
```

### Webhook events

Tickets opened through the API carry your app id, so `support.ticket.created`, `support.ticket.replied`, `support.ticket.resolved`, `support.ticket.confirmed` and `support.ticket.closed` fire to your webhook URL in the same signed envelope as every other event. Verify `X-Dbrij-Signature` exactly as described on the Webhooks tab.

### The honest close

A ticket never closes because the team says so. The team marks it `resolved`; it only becomes `closed` when the customer confirms, either in the portal or through `POST /support/tickets/:id/confirm` from your product. Build your UI around that: show the fix, ask the customer, send the confirmation.

## Media & uploads

Attach **images, video, audio and files** to any message. You have two options:

**1 · Bring your own URL.** If you already host the file, just include it in `attachments` when sending a message, no upload step needed.

**2 · Upload to Dbrij storage.** Request a signed direct upload, send the file straight to storage (the bytes never touch your server), then attach the returned URL.

POST`/uploads/sign`chat:write

Sign an upload

Returns a one time signed payload to upload a single file directly to Dbrij storage.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| kind* | 'image' \| 'video' \| 'audio' \| 'file' | The media kind. |

Request

```
curl -X POST https://api.dbrij.com/api/uploads/sign \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "kind": "image" }'
```

Request body

```
{ "kind": "image" }
```

Response 200

```
{
  "success": true,
  "data": {
    "cloudName": "dbrij",
    "apiKey": "8123…",
    "timestamp": 1751200000,
    "signature": "a1b2…",
    "folder": "dbrij/apps/<app>/attachments",
    "resourceType": "image",
    "uploadUrl": "https://api.cloudinary.com/v1_1/dbrij/image/upload"
  }
}
```

With the SDK it's one call:

Upload + send (browser)

```
// 'file' is a browser File (e.g. from an <input type="file">)
const attachment = await dbrij.uploadMedia('image', file);

await dbrij.sendMessage(convo.id, {
  author: { externalUserId: 'user_42' },
  body: 'Check this out',
  attachments: [attachment],
});
```

Or upload yourself, then attach the URL:

Manual upload (any language)

```
# 1) POST the file to uploadUrl with the signed fields:
curl -X POST "$UPLOAD_URL" \
  -F "file=@./photo.jpg" \
  -F "api_key=$API_KEY" -F "timestamp=$TIMESTAMP" \
  -F "signature=$SIGNATURE" -F "folder=$FOLDER"
# → response has "secure_url", "width", "height", "bytes"

# 2) Send a message referencing it:
curl -X POST https://api.dbrij.com/api/conversations/$CONVO_ID/messages \
  -H "Authorization: Bearer $DBRIJ_API_KEY" -H "Content-Type: application/json" \
  -d '{ "author": { "externalUserId": "user_42" }, "body": "",
        "attachments": [{ "name": "photo.jpg", "kind": "image", "url": "<secure_url>" }] }'
```

ATTACHMENT FIELDS

|  |  |  |
| --- | --- | --- |
| name* | string | File name shown in the UI. |
| kind* | 'image'\|'video'\|'audio'\|'file' | Media kind (drives how clients render it). |
| url* | string | The media URL (yours or from the upload). |
| sizeLabel | string | e.g. "2.1 MB". |
| durationMs | number | Length for audio/video. |
| mimeType | string | e.g. "image/png". |
| width / height | number | Pixels (image/video). |
| thumbnailUrl | string | Poster image (video). |

## Realtime

Deliver messages, typing, read receipts and presence to your end users' devices live, without building your own fan out. Mint a short lived token for a user, connect to Dbrij over Socket.IO, and join their conversation rooms.

POST`/realtime/token`chat:read

Mint a realtime token

Returns a 1 hour token for one of your end users to connect to realtime.

Request

```
curl -X POST https://api.dbrij.com/api/realtime/token \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "externalUserId": "user_99" }'
```

Request body

```
{ "externalUserId": "user_99" }
```

Response 200

```
{
  "success": true,
  "data": {
    "token": "eyJhbGciOi…",
    "expiresIn": 3600,
    "events": ["message", "message:update", "typing", "conversation:read", "presence"]
  }
}
```

Connect from your client

Browser (socket.io-client)

```
import { io } from 'socket.io-client';

const { token } = await dbrij.realtimeToken({ externalUserId: 'user_99' });

// Connect to the Dbrij API host (same host as your REST base).
const socket = io('https://api.dbrij.com', { auth: { token } });

socket.on('connect', () => {
  socket.emit('join', { channel: 'conversation', id: CONVERSATION_ID });
});

socket.on('message', (m) => console.log('new message', m));
socket.on('typing', (t) => console.log('typing', t));
socket.on('conversation:read', (r) => console.log('read', r));
socket.on('presence', (p) => console.log('presence', p));

// Tell others this user is typing:
socket.emit('typing', { conversationId: CONVERSATION_ID, typing: true });
```

EVENTS YOU RECEIVE

|  |  |  |
| --- | --- | --- |
| message | Message | A new message in a joined conversation. |
| message:update | Message | A message was edited or its reactions changed. |
| typing | { conversationId, userId, typing } | Someone is typing. |
| conversation:read | { conversationId, userId, at } | A read receipt. |
| presence | { conversationId, userId, online } | A member came online / went offline. |

The token authenticates exactly one of your end users and expires in 1 hour, so mint a fresh one per session. Joining a conversation requires that user to be a member.

## Webhooks

Set a webhook URL on your app in the dashboard. Dbrij POSTs a signed JSON envelope for each event. Because your end users don't connect to Dbrij directly, `message.created` is how you deliver chat to their clients.

Envelope

```
{
  "id": "evt_…",
  "type": "message.created",
  "createdAt": "2026-06-29T12:05:00.000Z",
  "data": { … }
}
```

### Events

`meeting.started`A meeting goes live.

data

```
{ "meetingId": "b1f2…" }
```

`meeting.ended`A meeting ends (host ended it, or the room emptied).

data

```
{ "meetingId": "b1f2…" }
```

`meeting.participant.joined`An end user joins the call.

data

```
{ "meetingId": "b1f2…", "externalUserId": "user_99" }
```

`meeting.participant.left`An end user leaves the call (carries metered minutes).

data

```
{ "meetingId": "b1f2…", "externalUserId": "user_99", "minutes": 12 }
```

`recording.ready`A recording finished processing.

data

```
{ "meetingId": "b1f2…", "recordingUrl": "https://…" }
```

`message.created`A message is sent in one of your conversations.

data

```
{ "conversationId": "c7a1…", "message": { "id": "m9c2…", "authorExternalUserId": "user_42", "body": "…" } }
```

`support.ticket.created`A ticket your app opened was created.

data

```
{ "ticketId": "t9d1…", "reference": "ACME-42", "status": "open" }
```

`support.ticket.replied`The team (or Deputy) replied to the customer.

data

```
{ "ticketId": "t9d1…", "reference": "ACME-42", "status": "open" }
```

`support.ticket.resolved`The team marked the fix in place; the customer is asked to confirm.

data

```
{ "ticketId": "t9d1…", "reference": "ACME-42", "status": "resolved" }
```

`support.ticket.confirmed`The customer confirmed the fix.

data

```
{ "ticketId": "t9d1…", "reference": "ACME-42", "status": "closed" }
```

`support.ticket.closed`The ticket closed.

data

```
{ "ticketId": "t9d1…", "reference": "ACME-42", "status": "closed" }
```

### Verify the signature

Each delivery includes `X-Dbrij-Signature: sha256=…`, an HMAC of the raw request body using your app's signing secret (rotate it in the dashboard). Verify before trusting a payload.

Node

```
import { createHmac } from 'crypto';

function verify(rawBody, header, secret) {
  const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
  return header === expected; // compare to X-Dbrij-Signature
}
```

## Objects

The response shapes you'll work with.

### Meeting

shape

```
{
  "id": "string",
  "status": "scheduled | live | ended | cancelled",
  "mode": "video | voice",
  "title": "string",
  "code": "string | null",          // short join code people type to join
  "scheduledFor": "string | null",
  "startedAt": "string | null",
  "endedAt": "string | null",
  "participantCount": "number?",    // present on GET /meetings/:id
  "isRecorded": "boolean",
  "recordingUrl": "string | null",
  "createdAt": "string"
}
```

### Conversation

shape

```
{
  "id": "string",
  "type": "direct | group",
  "title": "string",
  "memberCount": "number?",         // present on GET /conversations/:id
  "members": "Member[]?",
}
// Member: { "externalUserId": string, "name": string, "avatarUrl": string | null }
```

### Message

shape

```
{
  "id": "string",
  "conversationId": "string",
  "authorExternalUserId": "string | null",
  "body": "string",
  "attachments": "Attachment[]",
  "createdAt": "string",
  "parentMessageId": "string | null",
  "reactions": "{ emoji: string, count: number }[]"
}
// Attachment: { id, name, kind: 'image'|'video'|'audio'|'file', url, sizeLabel?, durationMs?, mimeType?, width?, height?, thumbnailUrl? }
```

### Token response

shape

```
{ "url": "string (wss://…)", "token": "string (JWT)", "identity": "string", "meeting": Meeting }
```

## Errors & limits

Errors use standard HTTP status codes with a JSON body:

|  |  |  |
| --- | --- | --- |
| 401 | Unauthorized | Missing, invalid, revoked or expired API key. |
| 403 | Forbidden | Key missing a required scope, acting on another app, or email-unverified (live keys / billing). |
| 404 | Not Found | Unknown meeting, conversation, message, or external user. |
| 402 | Payment Required | Monthly request allotment exhausted on the free tier. Upgrade in the dashboard. |
| 429 | Too Many Requests | Exceeded your plan's per minute rate ceiling. |
| 422 / 400 | Validation | Malformed body or invalid field. |

Error shape

```
{
  "success": false,
  "statusCode": 403,
  "message": "API key is missing required scope(s): meetings:write",
  "error": "Forbidden",
  "path": "/api/emails",
  "timestamp": "2026-08-06T22:56:17.969Z"
}
```

`message` is a sentence for a person; `error` is the stable status name, useful for branching. `path` and `timestamp` identify the request when you report a problem.

Usage (requests, meeting minutes, meetings created, messages, support tickets) is metered per app per month against your plan. Track it and upgrade plans in the Developer API dashboard.
