Write Preview

Agent SDK

Agent SDK is for CRM and internal support workspaces that need to show Ringnity agent data without opening the full dashboard. It uses a short-lived agent token, not a public website key.

1. Create an agent token

Your backend calls Server API with a private Server API key. The response token is safe to pass to your internal CRM page for a short time.

curl -X POST "https://api.ringnity.com/api/server/sdk-token/agent" \
  -H "Authorization: Bearer SERVER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "agent.one",
    "scopes": [
      "agent:profile:read",
      "agent:presence:read",
      "agent:presence:write",
      "agent:conversations:read",
      "agent:conversations:write",
      "agent:messages:read",
      "agent:messages:write",
      "agent:media:delete",
      "agent:calls:read",
      "agent:calls:write"
    ],
    "expiresIn": 900
  }'

Token rules

  • Keep the Server API key on your backend only.
  • Agent token includes tenant, agent, role, department, and scopes.
  • Default expiry is 900 seconds. Maximum expiry is 3600 seconds.
  • Use scope server:agent-token:write on the Server API key.

2. Use the token in your CRM page

Minimal JavaScript flow

Your CRM page asks your own backend for an Agent SDK token, then uses the Agent SDK object model to read profile, set presence, open inbox, send replies, and mark messages as read.

const tokenResponse = await fetch('/api/ringnity/agent-token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ userId: currentUser.id })
});

const { token } = await tokenResponse.json();

const agent = await RingnityAgent.create({ token });

await agent.presence.set('online');

const inbox = await agent.conversations.list({
  status: 'open',
  limit: 10
});

Read a conversation thread

const conversationId = inbox.conversations[0].conversationId;

const thread = await agent.conversations.messages(conversationId, {
  limit: 50
});

await agent.conversations.reply(conversationId, {
  body: 'Hi, how can I help?'
});

await agent.conversations.assign(conversationId, {
  assignedAgentId: currentAgent.id,
  reason: 'Accepted from CRM inbox'
});

await agent.media.deleteAttachment(conversationId, attachmentMessageId, {
  reason: 'Removed by agent request'
});

Typed client example

import { RingnityAgent } from '@ringnity/agent-sdk';

const agent = await RingnityAgent.create({ token });

await agent.presence.set('online');

const inbox = await agent.conversations.list({
  status: 'open',
  limit: 10
});

if (inbox.conversations[0]) {
  const conversationId = inbox.conversations[0].conversationId;
  const thread = await agent.conversations.messages(conversationId);

  await agent.conversations.reply(conversationId, {
    body: 'Hi, how can I help?'
  });

  const lastMessage = thread.messages.at(-1);
  if (lastMessage) {
    await agent.conversations.markRead(conversationId, lastMessage.messageId);
  }
}

3. Add call controls when your workspace handles calls

Incoming call popup

Your CRM owns the popup and buttons. The SDK handles the live connection, call setup, media permission, and cleanup.

import { RingnityAgent } from '@ringnity/agent-sdk';

const agent = await RingnityAgent.create({ token });

await agent.calls.goOnline();

agent.calls.onIncomingCall((call) => {
  showIncomingCallPopup({
    customerName: call.customer.name ?? 'Customer',
    onAccept: async () => {
      await call.accept();
      showActiveCallPanel(call);
    },
    onReject: () => call.reject('Agent declined')
  });
});

Active call buttons

agent.calls.onIncomingCall((call) => {
  call.attachView({
    local: document.querySelector('#agent-video'),
    remote: document.querySelector('#customer-video')
  });

  document.querySelector('#accept').onclick = () =>
    call.accept({ video: call.type === 'video' });

  document.querySelector('#mute').onclick = () => call.mute();
  document.querySelector('#unmute').onclick = () => call.unmute();
  document.querySelector('#hold').onclick = () => call.hold();
  document.querySelector('#resume').onclick = () => call.resume();
  document.querySelector('#transfer').onclick = () =>
    call.transfer({ departmentId: selectedDepartmentId });
  document.querySelector('#start-video').onclick = () => call.startVideo();
  document.querySelector('#stop-video').onclick = () => call.stopVideo();
  document.querySelector('#end').onclick = () => call.end();
});

Full call playground

The repository includes a plain HTML CRM call page at sun_webrtc_sdk/examples/agent-call-playground. Use it to see the token endpoint, agent online button, incoming popup, active call panel, transfer input, and optional video containers in one place.

cd sun_webrtc_sdk/examples/agent-call-playground
npm install --package-lock=false
npm run dev

# Then update TOKEN_ENDPOINT in index.html to your backend route:
# /ringnity/agent-token
# or, for the current demo customer backend:
# https://sadavir.id/ringnity/agent-token

Agent SDK object model

agent.profile.get()

Read the current agent, tenant, role, department, and scopes.

agent.presence.get()

Read whether the agent is online, busy, offline, or in an active call.

agent.presence.set(status)

Set the agent status from your CRM workspace.

agent.conversations.list(options)

Show open, closed, or department-filtered conversations.

agent.conversations.messages(id)

Read the message thread for one conversation.

agent.conversations.reply(id, input)

Send an agent reply or internal note.

agent.conversations.markRead(id, messageId)

Mark a message as read by this agent.

agent.conversations.assign(id, input)

Accept, assign, or transfer a conversation inside tenant visibility rules.

agent.conversations.close(id, input)

Close a conversation and record the reason.

agent.media.deleteAttachment(id, messageId, input)

Soft-delete an attachment-bearing message so media removal stays auditable.

agent.calls.goOnline(options)

Make this agent available for Ringnity calls.

agent.calls.goOffline()

Mark the agent unavailable and close the live workspace connection.

agent.calls.onIncomingCall(handler)

Run your own UI when a customer call arrives.

call.accept(options)

Accept a customer call and let the SDK handle call setup.

call.reject(reason)

Decline an incoming call.

call.end()

End the active call and clean up local media.

call.hold()

Put an active call on hold.

call.resume()

Resume a held call.

call.transfer({ departmentId })

Transfer the call to another department.

call.mute() / call.unmute()

Control the local microphone.

call.startVideo() / call.stopVideo()

Turn video on or off from your call UI.

call.attachView({ local, remote })

Choose where optional video appears in your page.

Exact API reference

Agent SDK methods above are the recommended public surface. When you need exact request fields, response shapes, status codes, or auth schemes, use the Scalar API reference generated from OpenAPI.

Open Scalar API reference

Who can see a conversation?

  • The conversation is assigned to the agent.
  • The agent is an active participant in the conversation.
  • The conversation is unassigned and belongs to the agent department.
  • The conversation is unassigned and has no department.
  • Supervisor or admin roles can read tenant conversations without the regular agent filter.

Next milestone

  • Add typed return models for every stable backend response.
  • Add custom CRM voice/video example with browser permission guidance.
  • Add live call smoke checklist after the first custom workspace test.

Common errors

401 AGENT_SDK_TOKEN_MISSING
401 AGENT_SDK_TOKEN_INVALID
403 AGENT_SDK_SCOPE_DENIED
404 AGENT_SDK_CONVERSATION_NOT_FOUND
403 AGENT_SDK_CONVERSATION_WRITE_FORBIDDEN