Syncli Developer Integration Guide

Overview

The Syncli Voice Agent is now available as an external API, similar to ChatGPT or Grok. You can interact with it via standard HTTP endpoints for text chat completion, voice WebRTC sessions, and custom tool integrations via the Model Context Protocol (MCP).

Base URL

The API runs on the same host as the voice gateway. Use the base URL of the gateway you're connected to:

https://apivoice.syncli.cloud          # Production (Docker)
https://apidev.syncli.cloud            # Development (host :7445)
https://voice.syncli.cloud             # Production alias
device.local:7445                      # Local development

All endpoints documented below are relative to this base URL. The frontend auto-detects the base URL from window.location.origin.

Authentication

All API requests require a Bearer token in the Authorization header.

Authorization: Bearer <your-token>

Token Types

Type Format Description
Session JWT eyJhbG... Standard OpenWebUI session token (from browser login)
API Key syncli_... Persistent developer key generated via the Developer Portal
Legacy Key sk-... Legacy API keys (deprecated, still supported)

Replace <your-token> with either your session JWT or a syncli_... API key. All three token types are accepted on every endpoint.

Generating an API Key

  1. Log in to the Syncli voice web app.
  2. Click the Developer Portal button (code icon) in the top-right header.
  3. Click Generate API Key.
  4. Name your key and select scopes (chat, voice, mcp).
  5. Copy the key immediately — it is only shown once.

API Key Endpoints

Method Endpoint Description
GET /api/v1/api-keys List your active keys
POST /api/v1/api-keys Generate a new key
DELETE /api/v1/api-keys/{id} Revoke a key
POST /api/v1/api-keys/{id}/rotate Rotate (revoke + regenerate)

OpenAI-Compatible Chat Completions

POST /v1/chat/completions

Standard OpenAI-compatible chat completion endpoint.

Headers:

Authorization: Bearer <token>
Content-Type: application/json

Request Body:

{
  "model": "syncli-voice",
  "messages": [
    { "role": "system", "content": "You are a helpful assistant." },
    { "role": "user", "content": "What is on my calendar today?" }
  ],
  "stream": false,
  "temperature": 0.1,
  "max_tokens": 300,
  "conversation_id": "my-session-123"
}

Response (non-streaming):

{
  "id": "chatcmpl-syncli",
  "object": "chat.completion",
  "created": 1779271807,
  "model": "syncli-voice",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "You have a meeting with Willy at 2 PM."
      },
      "finish_reason": "stop"
    }
  ]
}

Streaming: Set "stream": true. The server returns text/event-stream chunks:

data: {"choices":[{"delta":{"content":"Hello"}}]}
data: {"choices":[{"delta":{"content":" world"}}]}
data: [DONE]

GET /v1/models

List available models.

{
  "object": "list",
  "data": [
    { "id": "qwen2.5:14b-instruct-q4_K_M", "object": "model", "owned_by": "syncli" }
  ]
}

Native Tool Calling

The agent supports the same tools as the voice bot:

Tool Description
get_calendar_summary List upcoming Google Calendar events
create_google_meet Schedule a Google Meet with auto-invite
add_reminder_task Add a task to Google Tasks
send_email Send email via Gmail
list_contacts List Google contacts
search_contacts Search contacts by name/email
update_user_profile Update user learning profile

Tools are automatically available when you call /v1/chat/completions. The LLM decides which tool to call based on the conversation context.


MCP (Model Context Protocol) Integration

MCP allows you to connect external tool servers to extend the agent's capabilities.

Register an MCP Server

curl -X POST https://api.syncli.cloud/v1/mcp/servers \
  -H "Authorization: Bearer syncli_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "server_id": "github-tools",
    "url": "https://your-mcp-server.com/sse",
    "headers": { "Authorization": "Bearer xxxxx" }
  }'

Response:

{
  "status": "connected",
  "server_id": "github-tools",
  "tools": ["github_search_repo", "github_create_issue"]
}

MCP Management Endpoints

Method Endpoint Description
GET /v1/mcp/servers List registered servers
POST /v1/mcp/servers Connect a new server
DELETE /v1/mcp/servers/{id} Disconnect a server
GET /v1/mcp/tools List all available MCP tools

Once connected, MCP tools are automatically included in every /v1/chat/completions call.


Voice Endpoint (WebRTC)

Overview

The voice endpoint uses WebRTC for real-time audio streaming. The connection flow is:

  1. POST /start — Create a session and get ICE configuration
  2. Create a WebRTC RTCPeerConnection with the ICE config
  3. POST /sessions/{sessionId}/api/offer — Send your SDP offer
  4. Apply the SDP answer from the server
  5. Exchange ICE candidates via PATCH /sessions/{sessionId}/api/offer
  6. Stream audio over the data channel

End-user session identity (required for API embeds)

When calling voice endpoints with a syncli_... API key, your frontend must send a stable end_user_session_id on POST /start (and again in the WebRTC offer request_data). Without it, the gateway identifies callers by IP only — visitors on the same network share history, rate limits, and webhook identity.

function getBrowserSessionId() {
  const key = 'syncli_browser_session_id';
  let id = localStorage.getItem(key);
  if (!id) {
    id = (crypto.randomUUID && crypto.randomUUID())
      || ('bs_' + Date.now() + '_' + Math.random().toString(36).slice(2));
    localStorage.setItem(key, id);
  }
  return id;
}
FieldRequiredDescription
end_user_session_idYes (API embeds)Stable per-browser or per-user ID from your frontend
chat_idNoReuse from prior /start response to resume conversation history
end_user_emailNoWhen your rate-capping tracking mode includes email

Step-by-Step JavaScript Example

const token = 'syncli_your_api_key';

function getBrowserSessionId() {
  const key = 'syncli_browser_session_id';
  let id = localStorage.getItem(key);
  if (!id) {
    id = (crypto.randomUUID && crypto.randomUUID())
      || ('bs_' + Date.now() + '_' + Math.random().toString(36).slice(2));
    localStorage.setItem(key, id);
  }
  return id;
}

// 1. Start session
const startRes = await fetch('https://apivoice.syncli.cloud/start', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${token}`
  },
  body: JSON.stringify({
    end_user_session_id: getBrowserSessionId(),
    chat_id: localStorage.getItem('syncli_voice_chat_id') || null,
    end_user_email: null,
    client_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
    client_timestamp: new Date().toISOString()
  })
});
const { sessionId, iceServers, chat_id } = await startRes.json();
if (chat_id) localStorage.setItem('syncli_voice_chat_id', chat_id);

// 2. Create peer connection
const pc = new RTCPeerConnection({
  iceServers: iceServers,
  iceTransportPolicy: 'relay'
});

// 3. Add audio track
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
stream.getAudioTracks().forEach(track => pc.addTrack(track, stream));

// 4. Create data channel
dc = pc.createDataChannel('chat');
dc.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  console.log('Bot says:', msg.data?.text);
};

// 5. Create and send offer
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);

const offerRes = await fetch(`https://apivoice.syncli.cloud/sessions/${sessionId}/api/offer`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${token}`
  },
  body: JSON.stringify({
    sdp: pc.localDescription.sdp,
    type: pc.localDescription.type,
    request_data: {
      end_user_session_id: getBrowserSessionId(),
      chat_id: chat_id,
      client_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
      client_timestamp: new Date().toISOString()
    }
  })
});

const answer = await offerRes.json();
await pc.setRemoteDescription(new RTCSessionDescription(answer));

// 6. Handle ICE candidates
pc.onicecandidate = async (event) => {
  if (!event.candidate) return;
  await fetch(`https://apivoice.syncli.cloud/sessions/${sessionId}/api/offer`, {
    method: 'PATCH',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`
    },
    body: JSON.stringify({
      pc_id: answer.pc_id,
      candidates: [{
        candidate: event.candidate.candidate,
        sdp_mid: event.candidate.sdpMid,
        sdp_mline_index: event.candidate.sdpMLineIndex
      }]
    })
  });
};

Data Channel Messages

Messages received on the chat data channel:

Type Description
bot-output Live assistant text (TTS output)
bot-output-raw Final condensed assistant text
user-transcript What the STT heard
bot-started-speaking Turn start marker
bot-stopped-speaking Turn end marker

Error Codes

Code Meaning
400 Bad request (e.g., malformed JSON)
401 Unauthorized (invalid or missing token)
429 Rate limit exceeded
500 Internal server error
502 MCP server unreachable

Rate Limits


Security Notes