# 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)

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

## Quickstart & SDK

Dbrij ships SDKs and packages for every stack below. Each one, with what it does, how to install it and a quick start, is on [the apps page](https://dbrij.com/apps#sdks).

- TypeScript 10 packages
- JavaScript 8 packages
- Node.js 5 packages
- Browsers 11 packages
- React Native 4 packages
- Capacitor 3 packages
- Python 1 package
- Go 3 packages
- Swift 2 packages
- iOS 6 packages
- Kotlin 2 packages
- Android 6 packages
- Dart 4 packages
- Flutter 4 packages
- Docker 1 package
- Kubernetes 1 package
- Command line 1 package

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

Use `test` keys while building and `live` keys in production. Keys carry **scopes**; a request missing a required scope returns `403`. Keys for email, SMS, search and support are made in Send, Ping, Search and Resolve, each with its own docs.

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

## 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" } ]
}
```

## Sign in with Dbrij

### Quick start

1. **Get a client id.** In Dbrij Auth, under Sign in with Dbrij, add your product and its redirect address. Copy the secret: it shows once.
2. **Put the button on your sign in page.** Any link or button that opens your start route works.
3. **Add the two routes below to your server.** Set `DBRIJ_CLIENT_ID` and `DBRIJ_CLIENT_SECRET` in its environment.
4. **Try it.** Press the button, approve on Dbrij, and you land back signed in.

The button

```
<a href="/auth/dbrij/start" class="dbrij-signin">
  Sign in with Dbrij
</a>
```

server.js (Node): the whole integration

```
// npm install express express-session
import crypto from 'node:crypto';
import express from 'express';
import session from 'express-session';

const DBRIJ = 'https://api.dbrij.com/api';
const CLIENT_ID = process.env.DBRIJ_CLIENT_ID;
const CLIENT_SECRET = process.env.DBRIJ_CLIENT_SECRET; // server only, never in the browser
const REDIRECT_URI = 'https://yourapp.com/auth/dbrij/callback'; // registered exactly

const app = express();
app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false }));

// 1. Your button lands here: remember a state and a PKCE verifier, then go to Dbrij.
app.get('/auth/dbrij/start', (req, res) => {
  const state = crypto.randomBytes(16).toString('base64url');
  const verifier = crypto.randomBytes(32).toString('base64url');
  const challenge = crypto.createHash('sha256').update(verifier).digest('base64url');
  req.session.dbrij = { state, verifier };
  const url = new URL(DBRIJ + '/oauth/authorize');
  url.search = new URLSearchParams({
    client_id: CLIENT_ID,
    redirect_uri: REDIRECT_URI,
    response_type: 'code',
    scope: 'openid profile email',
    state,
    code_challenge: challenge,
    code_challenge_method: 'S256',
  }).toString();
  res.redirect(url.toString());
});

// 2. Dbrij sends them back with a one time code: check the state, swap the code.
app.get('/auth/dbrij/callback', async (req, res) => {
  const saved = req.session.dbrij;
  if (!saved || req.query.state !== saved.state) return res.status(400).send('Sign in expired, try again');
  if (req.query.error) return res.redirect('/login?cancelled=1'); // they pressed Cancel

  const token = await fetch(DBRIJ + '/oauth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code: String(req.query.code),
      redirect_uri: REDIRECT_URI,
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET,
      code_verifier: saved.verifier,
    }),
  }).then((r) => r.json());
  if (token.error) return res.status(401).send(token.error_description);

  // 3. Who signed in: sub is their permanent Dbrij id, key your user record on it.
  const me = await fetch(DBRIJ + '/oauth/userinfo', {
    headers: { Authorization: 'Bearer ' + token.access_token },
  }).then((r) => r.json());

  req.session.dbrij = undefined;
  req.session.user = { dbrijId: me.sub, name: me.name, email: me.email, picture: me.picture };
  res.redirect('/');
});

app.listen(3000);
```

### How it works

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 in Dbrij Auth, under Sign in with Dbrij: 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

|  |  |  |
| --- | --- | --- |
| openid | scope | OpenID Connect: the token response also carries an ID token saying who signed in. Ask for it with profile and email to have those claims in it. |
| profile | scope | Name, username and photo. Included in the default scope. |
| email | scope | The verified email on the account. Included in the default scope. |

### OpenID Connect

Ask for `openid` and the code exchange returns an `id_token` as well: a JWT signed RS256 by Dbrij, carrying `iss`, `sub` (the account id), `aud` (your client id), `auth_time`, `at_hash`, the `nonce` you sent to authorize, and the profile and email claims your scopes name. Any OpenID Connect library sets itself up from the discovery document, and verifies tokens with the keys in the JWKS. The signing key changes every 90 days: the next key is in the JWKS a day before it signs, and the old one stays two days after, so a library that refetches the JWKS on an unknown `kid` never misses a beat. Refreshing does not return a new ID token.

Discovery and keys

```
https://api.dbrij.com/.well-known/openid-configuration
https://api.dbrij.com/.well-known/jwks.json

# authorize with:  &scope=openid%20profile%20email&nonce=a-random-string
# the exchange then returns:  { "access_token": "…", "id_token": "eyJhbGciOiJSUzI1NiIs…", … }
```

### 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".

## 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. Send your app’s push from here: it names the customer and carries the opening line.

data

```
{ "ticketId": "t9d1…", "reference": "ACME-42", "status": "waiting_on_customer", "channel": "widget", "contact": { "externalId": "user_42", "email": "ada@example.com" }, "preview": "Clear your cache and…" }
```

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