Dbrij API Documentation

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

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 URLhttps://api.dbrij.com/api

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

Use the tabs on the left 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 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, GlassTracker.kt or 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 (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:readscopeList/read meetings, participants, summaries.
meetings:writescopeCreate/end/cancel meetings, mute/remove participants.
meetings:tokensscopeMint LiveKit join tokens.
chat:readscopeList/read conversations and messages.
chat:writescopeCreate conversations, send messages, manage members, react.
emails:sendscopeSend / schedule / cancel emails from your granted domains.
emails:readscopeList/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*stringYour stable id for the end user.
displayNamestringThe user's name (kept fresh on each call).
avatarUrlstringURL 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/meetingsmeetings: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

titlestringDisplay title. Defaults to a generated name.
mode'video' | 'voice'Camera call or audio only. Default 'video'.
scheduledForstring (ISO-8601)If set in the future, the meeting is scheduled rather than started now.
durationMinutesnumberPlanned length cap (5 to 1440).
hostActorRefThe hosting end user. Defaults to your app actor.
participantsActorRef[]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/meetingsmeetings: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/:idmeetings: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/endmeetings: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/cancelmeetings: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/tokensmeetings: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/participantsmeetings: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/mutemeetings: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/:externalUserIdmeetings: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/summarymeetings: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/recordingmeetings: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*booleantrue 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/callsmeetings: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*ActorRefThe end user starting the call (a member of the conversation).
mode'video' | 'voice'Camera call or audio only. Default 'video'.
titlestringOptional 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/conversationschat: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.
titlestringRequired 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/conversationschat: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/:idchat: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/memberschat: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/:externalUserIdchat: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/messageschat: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*ActorRefThe end user sending the message.
body*stringMessage text (may be empty when sending media).
attachmentsAttachment[]Up to 10 media items (see the Media tab). Each: { name, kind, url, sizeLabel?, durationMs?, mimeType?, width?, height?, thumbnailUrl? }.
parentMessageIdstringPost as a threaded reply.
quotedMessageIdstringQuote 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/messageschat: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

limitnumberMax messages (1 to 100, default 30).
beforestring (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/reactionschat: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/:idchat: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/deletechat: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/readchat: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/receiptschat: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 →

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 Sign in with Google. 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

profilescopeName, username and photo. Included in the default scope.
emailscopeThe 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_uriexact matchMust equal a registered URI character for character, at authorize AND at exchange. https only (localhost may use http).
codeone minute, one useExchange it immediately, server side. A second exchange fails and always verify state.
client_secretserver onlyNever ship it in a browser or app binary. For public clients add PKCE: send code_challenge (S256) on authorize and code_verifier on exchange.
errorsRFC 6749The 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/smssms: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*stringDestination number: +2348012345678, 2348012345678, or the 11-digit local 0-form (treated as Nigerian).
body*stringThe message. Up to 6 segments; you are billed per segment (see Segments below).
senderIdstringAn 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/sendsms: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*stringDestination number.
lengthnumberCode length, 4 to 8 digits. Default 6.
expiryMinutesnumberMinutes before it expires, 1 to 30. Default 10.
templatestringMessage wording with {code} where the code goes. Default: "123456 is your <app> code. It expires in 10 minutes."
senderIdstringAn 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/verifysms: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*stringFrom /sms/otp/send.
code*stringWhat 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/smssms:read

List SMS

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

QUERY PARAMETERS

limitnumberMax messages (1 to 100, default 20).
beforestringCursor: 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/:idsms: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/senderssms: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/deskssupport: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/scoresupport: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/ticketssupport: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

emailstringOnly 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/ticketssupport: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*stringThe customer’s email.
namestringThe customer’s name.
externalIdstringYour own user id for the customer, for joining on your side.
subject*stringWhat the ticket is about.
body*stringThe 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/:ticketIdsupport: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/messagessupport: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*stringThe 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/confirmsupport: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/signchat: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*stringFile name shown in the UI.
kind*'image'|'video'|'audio'|'file'Media kind (drives how clients render it).
url*stringThe media URL (yours or from the upload).
sizeLabelstringe.g. "2.1 MB".
durationMsnumberLength for audio/video.
mimeTypestringe.g. "image/png".
width / heightnumberPixels (image/video).
thumbnailUrlstringPoster 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/tokenchat: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

messageMessageA new message in a joined conversation.
message:updateMessageA 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.startedA meeting goes live.
data
{ "meetingId": "b1f2…" }
meeting.endedA meeting ends (host ended it, or the room emptied).
data
{ "meetingId": "b1f2…" }
meeting.participant.joinedAn end user joins the call.
data
{ "meetingId": "b1f2…", "externalUserId": "user_99" }
meeting.participant.leftAn end user leaves the call (carries metered minutes).
data
{ "meetingId": "b1f2…", "externalUserId": "user_99", "minutes": 12 }
recording.readyA recording finished processing.
data
{ "meetingId": "b1f2…", "recordingUrl": "https://…" }
message.createdA message is sent in one of your conversations.
data
{ "conversationId": "c7a1…", "message": { "id": "m9c2…", "authorExternalUserId": "user_42", "body": "…" } }
support.ticket.createdA ticket your app opened was created.
data
{ "ticketId": "t9d1…", "reference": "ACME-42", "status": "open" }
support.ticket.repliedThe team (or Deputy) replied to the customer.
data
{ "ticketId": "t9d1…", "reference": "ACME-42", "status": "open" }
support.ticket.resolvedThe team marked the fix in place; the customer is asked to confirm.
data
{ "ticketId": "t9d1…", "reference": "ACME-42", "status": "resolved" }
support.ticket.confirmedThe customer confirmed the fix.
data
{ "ticketId": "t9d1…", "reference": "ACME-42", "status": "closed" }
support.ticket.closedThe 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",          // Google-Meet-style join code
  "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:

401UnauthorizedMissing, invalid, revoked or expired API key.
403ForbiddenKey missing a required scope, acting on another app, or email-unverified (live keys / billing).
404Not FoundUnknown meeting, conversation, message, or external user.
402Payment RequiredMonthly request allotment exhausted on the free tier. Upgrade in the dashboard.
429Too Many RequestsExceeded your plan's per minute rate ceiling.
422 / 400ValidationMalformed 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.

Dbrij is Nigeria’s company operating system: the first Nigerian built platform to put team chat, video meetings, email, HR, tax compliant payroll, customer support, marketing, analytics and cloud hosting in one product on one subscription.

Everything on Dbrij

More from Dbrij