REST API v1

Programmatic access to conversations, messages, contacts, ratings, and API key management.

Base URL: https://api.berlay.app/api/v1/{orgSlug}

Authentication

Include your API key as a Bearer token on every request:

http
Authorization: Bearer brly_sk_your_key_here

Keys are prefixed with brly_sk_ and are 48 characters total. The full key is shown only once at creation time — only a SHA-256 hash is stored.

503 when API disabled: If API access is not enabled for your organization, all v1 endpoints return 503 API_DISABLED.

Scopes

Each API key carries a set of scopes. Calling an endpoint without the required scope returns 403 INSUFFICIENT_SCOPE.

ScopeDescription
conversations:readRead conversations and their metadata
conversations:writeCreate and update conversations
messages:readRead messages within conversations
messages:writeSend messages to conversations
contacts:readRead contact records
contacts:writeCreate and update contacts
ratings:writeSubmit conversation ratings
api_keys:readList API keys (session auth only)
api_keys:writeCreate and revoke API keys (session auth only)

Response format

Single resource

json
{
  "data": { ... }
}

List

json
{
  "data": [ ... ],
  "total": 142,
  "limit": 50,
  "offset": 0
}

Errors

All errors follow a consistent envelope:

json
{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable description."
  }
}
HTTP StatusCodeWhen
401UNAUTHORIZEDMissing or invalid Bearer token
403INSUFFICIENT_SCOPEToken lacks the required scope
404NOT_FOUNDResource does not exist or belongs to a different org
422VALIDATIONRequest body failed validation
500INTERNALUnexpected server error
503API_DISABLEDAPI feature is not enabled for this organization

Conversations

List conversations

GET/api/v1/{orgSlug}/conversationsconversations:read
ParameterTypeDescription
statusstringFilter by status: open, resolved, closed. Omit for all.
assignedTostringFilter by agent ID.
limitintegerMax results. Default: 50, max: 200.
offsetintegerPagination offset. Default: 0.
http
GET /api/v1/acme/conversations?status=open&limit=20
Authorization: Bearer brly_sk_...
json
{
  "data": [
    {
      "id": "conv_01j9abc",
      "subject": "Help with my order",
      "status": "open",
      "assignedTo": null,
      "contactId": "cont_01j9xyz",
      "channelType": "custom",
      "createdAt": "2024-01-15T10:30:00.000Z",
      "updatedAt": "2024-01-15T10:35:00.000Z"
    }
  ],
  "total": 84,
  "limit": 20,
  "offset": 0
}

Get conversation

GET/api/v1/{orgSlug}/conversations/{conversationId}conversations:read
json
{
  "data": {
    "id": "conv_01j9abc",
    "subject": "Help with my order",
    "status": "open",
    "assignedTo": null,
    "contactId": "cont_01j9xyz",
    "channelType": "custom",
    "externalConversationId": "session_abc123",
    "createdAt": "2024-01-15T10:30:00.000Z",
    "updatedAt": "2024-01-15T10:35:00.000Z"
  }
}

Create conversation

POST/api/v1/{orgSlug}/conversationsconversations:write
FieldTypeRequiredDescription
subjectstringYesConversation subject line.
contactIdstringNoID of an existing contact to associate.
assignedTostringNoAgent ID to assign immediately.
channelTypestringNoDefaults to api.
http
POST /api/v1/acme/conversations
Authorization: Bearer brly_sk_...
Content-Type: application/json

{
  "subject": "Billing inquiry",
  "contactId": "cont_01j9xyz"
}
json
{
  "data": {
    "id": "conv_01j9def",
    "subject": "Billing inquiry",
    "status": "open",
    "assignedTo": null,
    "contactId": "cont_01j9xyz",
    "channelType": "api",
    "createdAt": "2024-01-15T11:00:00.000Z",
    "updatedAt": "2024-01-15T11:00:00.000Z"
  }
}

Update conversation

PATCH/api/v1/{orgSlug}/conversations/{conversationId}conversations:write
FieldTypeRequiredDescription
subjectstringNoNew subject.
assignedTostring | nullNoAgent ID to assign, or null to unassign.
http
PATCH /api/v1/acme/conversations/conv_01j9abc
Authorization: Bearer brly_sk_...
Content-Type: application/json

{
  "assignedTo": "agent_01j9ghi"
}
json
{
  "data": {
    "id": "conv_01j9abc",
    "subject": "Help with my order",
    "status": "open",
    "assignedTo": "agent_01j9ghi",
    "updatedAt": "2024-01-15T11:05:00.000Z"
  }
}

Resolve conversation

POST/api/v1/{orgSlug}/conversations/{conversationId}/resolveconversations:write

Marks the conversation as resolved. A system event is recorded.

json
{
  "data": {
    "id": "conv_01j9abc",
    "status": "resolved",
    "updatedAt": "2024-01-15T12:00:00.000Z"
  }
}

Close conversation

POST/api/v1/{orgSlug}/conversations/{conversationId}/closeconversations:write

Marks the conversation as closed. Closed conversations are archived.

json
{
  "data": {
    "id": "conv_01j9abc",
    "status": "closed",
    "updatedAt": "2024-01-15T12:01:00.000Z"
  }
}

Reopen conversation

POST/api/v1/{orgSlug}/conversations/{conversationId}/reopenconversations:write

Moves a resolved or closed conversation back to open.

json
{
  "data": {
    "id": "conv_01j9abc",
    "status": "open",
    "updatedAt": "2024-01-15T12:05:00.000Z"
  }
}

Messages

List messages

GET/api/v1/{orgSlug}/conversations/{conversationId}/messagesmessages:read
ParameterTypeDescription
limitintegerMax results. Default: 50, max: 200.
offsetintegerPagination offset. Default: 0.
json
{
  "data": [
    {
      "id": "msg_01j9aaa",
      "conversationId": "conv_01j9abc",
      "role": "customer",
      "content": "Hi, I placed an order but haven't received a confirmation.",
      "contentType": "text",
      "sentAt": "2024-01-15T10:30:00.000Z",
      "attachment": null
    },
    {
      "id": "msg_01j9bbb",
      "conversationId": "conv_01j9abc",
      "role": "agent",
      "content": "Hello! Let me look into that for you.",
      "contentType": "text",
      "sentAt": "2024-01-15T10:32:00.000Z",
      "attachment": null
    }
  ],
  "total": 2,
  "limit": 50,
  "offset": 0
}

Send message

POST/api/v1/{orgSlug}/conversations/{conversationId}/messagesmessages:write
FieldTypeRequiredDescription
contentstringYesMessage body.
contentTypestringNotext (default) or html.
attachment.urlstringNoPublicly accessible URL to the attached file.
attachment.filenamestringNoOriginal filename shown to the recipient.
attachment.mimeTypestringNoMIME type, e.g. application/pdf, image/png.
http
POST /api/v1/acme/conversations/conv_01j9abc/messages
Authorization: Bearer brly_sk_...
Content-Type: application/json

{
  "content": "Please find your invoice attached.",
  "attachment": {
    "url": "https://files.example.com/invoice-1234.pdf",
    "filename": "invoice-1234.pdf",
    "mimeType": "application/pdf"
  }
}
json
{
  "data": {
    "id": "msg_01j9ccc",
    "conversationId": "conv_01j9abc",
    "role": "agent",
    "content": "Please find your invoice attached.",
    "contentType": "text",
    "sentAt": "2024-01-15T11:10:00.000Z",
    "attachment": {
      "url": "https://files.example.com/invoice-1234.pdf",
      "filename": "invoice-1234.pdf",
      "mimeType": "application/pdf"
    }
  }
}

Contacts

List contacts

GET/api/v1/{orgSlug}/contactscontacts:read
ParameterTypeDescription
searchstringFull-text search across name, email, and externalId.
limitintegerMax results. Default: 50, max: 200.
offsetintegerPagination offset. Default: 0.
http
GET /api/v1/acme/contacts?search=jane
Authorization: Bearer brly_sk_...
json
{
  "data": [
    {
      "id": "cont_01j9xyz",
      "externalId": "user_42",
      "name": "Jane Smith",
      "email": "jane@example.com",
      "createdAt": "2024-01-10T09:00:00.000Z",
      "updatedAt": "2024-01-15T10:30:00.000Z"
    }
  ],
  "total": 1,
  "limit": 50,
  "offset": 0
}

Create contact

POST/api/v1/{orgSlug}/contactscontacts:write
FieldTypeRequiredDescription
externalIdstringNoStable ID from your system. Must be unique per org.
namestringNoDisplay name.
emailstringNoEmail address. Must be unique per org if provided.
http
POST /api/v1/acme/contacts
Authorization: Bearer brly_sk_...
Content-Type: application/json

{
  "externalId": "user_99",
  "name": "Bob Jones",
  "email": "bob@example.com"
}
json
{
  "data": {
    "id": "cont_01j9qrs",
    "externalId": "user_99",
    "name": "Bob Jones",
    "email": "bob@example.com",
    "createdAt": "2024-01-15T11:20:00.000Z",
    "updatedAt": "2024-01-15T11:20:00.000Z"
  }
}

Get contact

GET/api/v1/{orgSlug}/contacts/{contactId}contacts:read
json
{
  "data": {
    "id": "cont_01j9xyz",
    "externalId": "user_42",
    "name": "Jane Smith",
    "email": "jane@example.com",
    "createdAt": "2024-01-10T09:00:00.000Z",
    "updatedAt": "2024-01-15T10:30:00.000Z"
  }
}

Update contact

PATCH/api/v1/{orgSlug}/contacts/{contactId}contacts:write
FieldTypeRequiredDescription
namestringNoNew display name.
emailstringNoNew email. Must be unique per org if provided.
http
PATCH /api/v1/acme/contacts/cont_01j9xyz
Authorization: Bearer brly_sk_...
Content-Type: application/json

{
  "name": "Jane Smith-Johnson"
}
json
{
  "data": {
    "id": "cont_01j9xyz",
    "externalId": "user_42",
    "name": "Jane Smith-Johnson",
    "email": "jane@example.com",
    "updatedAt": "2024-01-15T13:00:00.000Z"
  }
}

Ratings

Submit rating

POST/api/v1/{orgSlug}/conversations/{conversationId}/rateratings:write
FieldTypeRequiredDescription
ratingintegerYesScore from 1 (worst) to 5 (best).
commentstringNoOptional free-text feedback.
ratedBystringNoIdentifier of the person submitting the rating.
http
POST /api/v1/acme/conversations/conv_01j9abc/rate
Authorization: Bearer brly_sk_...
Content-Type: application/json

{
  "rating": 5,
  "comment": "Very helpful, resolved quickly!",
  "ratedBy": "jane@example.com"
}
json
{
  "data": {
    "id": "rate_01j9uvw",
    "conversationId": "conv_01j9abc",
    "rating": 5,
    "comment": "Very helpful, resolved quickly!",
    "ratedBy": "jane@example.com",
    "createdAt": "2024-01-15T14:00:00.000Z"
  }
}

API Keys

Session auth only. The api_keys:read and api_keys:write scopes are reserved for Admin UI sessions. API keys cannot manage other API keys.

List API keys

GET/api/v1/{orgSlug}/api-keysapi_keys:read
json
{
  "data": [
    {
      "id": "key_01j9lmn",
      "name": "Production integration",
      "prefix": "brly_sk_a1b2",
      "scopes": ["conversations:read", "messages:read"],
      "createdAt": "2024-01-01T00:00:00.000Z",
      "lastUsedAt": "2024-01-15T10:30:00.000Z"
    }
  ],
  "total": 1,
  "limit": 50,
  "offset": 0
}

Create API key

POST/api/v1/{orgSlug}/api-keysapi_keys:write
FieldTypeRequiredDescription
namestringYesHuman-readable label for this key.
scopesstring[]YesArray of scope strings. Must be a valid subset of available scopes.
http
POST /api/v1/acme/api-keys
Authorization: Bearer brly_sk_...
Content-Type: application/json

{
  "name": "CRM sync",
  "scopes": ["conversations:read", "contacts:read", "contacts:write"]
}
json
{
  "data": {
    "id": "key_01j9opq",
    "name": "CRM sync",
    "key": "brly_sk_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2",
    "prefix": "brly_sk_a1b2",
    "scopes": ["conversations:read", "contacts:read", "contacts:write"],
    "createdAt": "2024-01-15T15:00:00.000Z"
  }
}
The key field contains the full plaintext key. It is only present in this response — store it immediately in a secrets manager. Subsequent calls return only the prefix.

Revoke API key

DELETE/api/v1/{orgSlug}/api-keys/{keyId}api_keys:write

Immediately invalidates the key. Any subsequent requests using the revoked key receive 401 UNAUTHORIZED.

json
{
  "data": { "revoked": true }
}