Quickstart

Getting Started

Create your VoIPBIN account and get API credentials.

Signup

To use the VoIPBIN production API, you need your own account. There are two ways to sign up: via the Admin Console (browser) or via the API (headless, for automated systems).

Sign up via Admin Console

  1. Go to the admin console.

  2. Click Sign Up.

  3. Enter your email address and submit.

  4. Check your inbox for a verification email.

  5. Click the verification link in the email to verify your address.

  6. You will receive a welcome email with instructions to set your password.

Once your password is set, you can log in to the admin console and start making API requests.

Sign up via API (Headless)

For automated systems and AI agents, use the headless signup flow. This requires two API calls: one to initiate signup and one to verify with a 6-digit OTP code sent to your email.

Note

AI Implementation Hint

The headless signup path is preferred for AI agents and automated systems. The POST /auth/complete-signup response includes an accesskey with an API token that can be used immediately — no need to call POST /auth/login separately. Note that POST /auth/signup always returns HTTP 200 regardless of success to prevent email enumeration. An empty temp_token in the response may indicate the email is already registered or invalid.

Step 1: Initiate signup

Send a POST request to /auth/signup with your email address (String, Required). All other fields are optional.

$ curl --request POST 'https://api.voipbin.net/auth/signup' \
    --header 'Content-Type: application/json' \
    --data-raw '{
        "email": "[email protected]",
        "name": "Your Company Name"
    }'

Response (always HTTP 200):

{
    "temp_token": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"
}

Save the temp_token (String) — you will need it in the next step. A 6-digit OTP verification code is sent to the email address you provided.

Step 2: Complete signup with OTP

Send a POST request to /auth/complete-signup with the temp_token (String, Required) from Step 1 and the 6-digit code (String, Required) from the verification email.

$ curl --request POST 'https://api.voipbin.net/auth/complete-signup' \
    --header 'Content-Type: application/json' \
    --data-raw '{
        "temp_token": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
        "code": "123456"
    }'

Response:

{
    "customer_id": "550e8400-e29b-41d4-a716-446655440000",
    "accesskey": {
        "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "token": "your-api-access-key-token"
    }
}

The accesskey.token (String) is your API key. Use it immediately for authentication — see Authentication.

Troubleshooting

  • 200 OK but empty ``temp_token``:
    • Cause: The email may already be registered, or the email format is invalid.

    • Fix: Try logging in via POST /auth/login with the email. If the account exists, use the existing credentials. POST /auth/signup always returns 200 to prevent email enumeration.

  • 400 Bad Request on ``/auth/complete-signup``:
    • Cause: The temp_token has expired or the code is incorrect.

    • Fix: Verify the 6-digit code from the verification email. Codes expire after a limited time — if expired, re-submit POST /auth/signup to receive a new code.

  • 429 Too Many Requests on ``/auth/complete-signup``:
    • Cause: Too many verification attempts (maximum 5 per temp_token).

    • Fix: Re-submit POST /auth/signup with the same email to receive a new temp_token and verification code, then retry.

Authentication

Every API request must be authenticated using either a Token (JWT string) or an Accesskey (API key string). Both serve the same purpose — choose whichever fits your workflow.

Note

AI Implementation Hint

Use Token (JWT) for short-lived, session-based authentication (valid for 7 days). Use Accesskey for long-lived, programmatic access with custom expiration. For automated systems and AI agents, Accesskey is recommended because it does not require periodic re-authentication.

Generate a Token

Send a POST request to /auth/login with your username (String) and password (String) to receive a JWT token. The token is valid for 7 days.

$ curl --request POST 'https://api.voipbin.net/auth/login' \
    --header 'Content-Type: application/json' \
    --data-raw '{
        "username": "your-voipbin-username",
        "password": "your-voipbin-password"
    }'

Response:

{
    "username": "your-voipbin-username",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Use the token in subsequent API requests. See Using Credentials in API Requests below for usage examples.

Generate an Accesskey

For long-lived authentication, generate an access key. You can create one from the admin console or via the API.

Via Admin Console:

_images/quickstart_authentication_accesskey.png

Via API:

$ curl --request POST 'https://api.voipbin.net/v1.0/accesskeys?token=<your-token>' \
    --header 'Content-Type: application/json' \
    --data-raw '{
        "name": "my-api-key",
        "detail": "API key for automated access"
    }'

Response:

{
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "token": "your-access-key-token",
    "name": "my-api-key",
    "detail": "API key for automated access",
    ...
}

Note

AI Implementation Hint

If you signed up via the headless flow (POST /auth/complete-signup), the response already includes an accesskey with a valid token — you do not need to create another one. Use POST /v1.0/accesskeys only if you need additional keys or want to set a custom expiration via the expire (Integer, Optional) field.

Using Credentials in API Requests

The query parameter name depends on which credential type you are using:

Credential

Query Parameter

Token (JWT)

?token=<your-token>

Accesskey

?accesskey=<your-accesskey>

Token example:

$ curl --request GET 'https://api.voipbin.net/v1.0/calls?token=<your-token>'

Accesskey example:

$ curl --request GET 'https://api.voipbin.net/v1.0/calls?accesskey=<your-accesskey>'

Tokens can also be passed via the Authorization header:

$ curl --request GET 'https://api.voipbin.net/v1.0/calls' \
    --header 'Authorization: Bearer <your-token>'

Note

AI Implementation Hint

The parameter names are not interchangeable — use token= for JWT tokens and accesskey= for access keys. Using the wrong parameter name results in 401 Unauthorized. All quickstart examples use ?token=<your-token> — if you are using an accesskey instead, replace token= with accesskey= in every request.

Troubleshooting

  • 401 Unauthorized:
    • Cause: Token has expired (older than 7 days) or is malformed.

    • Fix: Generate a new token via POST /auth/login. Ensure the Authorization header uses the format Bearer <token> (with a space after “Bearer”).

  • 401 Unauthorized (Accesskey):
    • Cause: Accesskey has expired (past tm_expire) or has been deleted.

    • Fix: Check the accesskey’s expiration via GET /accesskeys. Generate a new one from the admin console if expired.

  • 403 Forbidden:
    • Cause: The authenticated user lacks permission for the requested resource.

    • Fix: Verify the resource belongs to your customer account. Check your permission level (admin vs. manager).

For more details, see the full Accesskey tutorial.

Set Up Your Test Environment

Before running the tutorials, set up a SIP extension and softphone for voice testing, and configure event delivery to observe real-time notifications.

Extension & Softphone Setup

Create a SIP extension and register a softphone (Linphone) to receive calls from VoIPBIN. This is required for the Real-Time Voice Interaction scenario.

  • A valid authentication token (String) or accesskey (String). See Authentication.

  • Your customer ID (UUID). Obtained from GET https://api.voipbin.net/v1.0/customer or from your admin console profile.

  • Linphone softphone installed on your computer or mobile device. Download from linphone.org.

Note

AI Implementation Hint

This section requires a human with a softphone to complete the registration and answer calls. AI agents can execute the API call to create the extension and instruct the human for the Linphone registration.

Create an extension

Create a SIP extension that your softphone will register to. The name (String, Required) identifies the extension for dialing. The detail (String, Required) is a description. The extension (String, Required) and password (String, Required) are used for SIP authentication.

$ curl --request POST 'https://api.voipbin.net/v1.0/extensions?token=<your-token>' \
    --header 'Content-Type: application/json' \
    --data-raw '{
        "name": "quickstart-phone",
        "detail": "Quickstart softphone extension",
        "extension": "quickstart1",
        "password": "your-secure-password-here"
    }'

Response:

{
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "customer_id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "quickstart-phone",
    "detail": "Quickstart softphone extension",
    "extension": "quickstart1",
    "username": "quickstart1",
    "password": "your-secure-password-here",
    "domain_name": "550e8400-e29b-41d4-a716-446655440000",
    "direct_hash": "a8f3b2c1d4e5",
    "tm_create": "2026-02-21T10:00:00.000000Z",
    "tm_update": "",
    "tm_delete": ""
}

The id (UUID) is the extension’s unique identifier — use it for GET /extensions/{id}, PUT /extensions/{id}, or DELETE /extensions/{id} operations. For dialing, use the name field instead. Save the name (String) — you will use it as the call destination in the Real-Time Voice Interaction scenario.

Note

AI Implementation Hint

The extension and password are SIP credentials, not VoIPBIN login credentials. The name field is the extension identifier used when dialing (e.g., "target_name": "quickstart-phone" in the call request). The response includes both extension and username fields — they contain the same value (username is a Kamailio-internal mirror of extension). Choose a memorable extension value and a strong password.

Register Linphone

Configure your Linphone softphone to register with VoIPBIN using the extension credentials created above.

Linphone configuration:

Field

Value

Username

quickstart1 (from the extension field above)

Password

The password you set when creating the extension

Domain

<your-customer-id>.registrar.voipbin.net

Transport

UDP

Replace <your-customer-id> with your customer ID (UUID) obtained from GET https://api.voipbin.net/v1.0/customer. For example, if your customer ID is 550e8400-e29b-41d4-a716-446655440000, the domain is 550e8400-e29b-41d4-a716-446655440000.registrar.voipbin.net.

Setup steps (Linphone desktop):

  1. Open Linphone and go to Preferences > Account (or SIP Account on mobile).

  2. Select I already have a SIP account (or Use SIP account).

  3. Enter the username, password, and domain from the table above.

  4. Save. Linphone should show Registered status within a few seconds.

If registration succeeds, the status indicator turns green. If it fails, see Troubleshooting below.

Troubleshooting
  • Extension creation returns 400 Bad Request:
    • Cause: Missing required fields (name, detail, extension, password).

    • Fix: Ensure all four fields are present in the request body.

  • Linphone shows “Registration failed” or “408 Timeout”:
    • Cause: Incorrect domain, extension/username, or password. The domain must include your customer ID.

    • Fix: Verify the domain is <your-customer-id>.registrar.voipbin.net. Double-check the extension and password match exactly what was set when creating the extension. Ensure UDP port 5060 is not blocked by your firewall.

Receiving Events

VoIPBIN notifies you in real-time when things happen — calls connect, transcriptions arrive, recordings complete. There are two delivery methods:

  • WebSocket — Maintain a persistent connection and receive events instantly. No public server needed.

  • Customer Webhook — Configure a webhook URI on your customer account to receive all events via HTTP POST.

Note

AI Implementation Hint

For AI agents and automated systems, WebSocket is preferred because it requires no public server endpoint and delivers events with lower latency. Use Customer Webhook when you have a persistent server that needs to process events asynchronously.

WebSocket

Customer Webhook

Connection

Persistent bidirectional connection

Stateless HTTP pushes to your server

Setup

Connect to wss:// endpoint

PUT /customer with webhook URI

Filtering

Subscribe to specific topics

Receives all events (no filtering)

Best for

Real-time dashboards, AI agents

Server-side catch-all processing

Requires

WebSocket client library

Public HTTPS endpoint

WebSocket

Connect to VoIPBIN’s WebSocket endpoint and subscribe to topics to receive events in real-time.

Connect:

wss://api.voipbin.net/v1.0/ws?token=<your-token>

Subscribe by sending a JSON message after connecting. Replace <your-customer-id> with your customer ID (UUID) obtained from GET https://api.voipbin.net/v1.0/customer:

{
    "type": "subscribe",
    "topics": [
        "customer_id:<your-customer-id>:call:*",
        "customer_id:<your-customer-id>:transcribe:*"
    ]
}

The wildcard * subscribes to events from all resources of that type under your account.

Python example:

import websocket
import json

token = "<your-token>"
customer_id = "<your-customer-id>"

def on_message(ws, message):
    data = json.loads(message)
    event_type = data.get("event_type")
    if event_type:
        if "transcript" in event_type:
            transcript = data["data"]
            direction = transcript.get("direction", "?")
            text = transcript.get("message", "")
            print(f"[TRANSCRIBE {direction}] {text}")
        else:
            print(f"[EVENT] {event_type}")

def on_open(ws):
    subscription = {
        "type": "subscribe",
        "topics": [
            f"customer_id:{customer_id}:call:*",
            f"customer_id:{customer_id}:transcribe:*"
        ]
    }
    ws.send(json.dumps(subscription))
    print("Subscribed to call and transcribe events. Waiting...")

ws = websocket.WebSocketApp(
    f"wss://api.voipbin.net/v1.0/ws?token={token}",
    on_open=on_open,
    on_message=on_message
)
ws.run_forever()

Note

AI Implementation Hint

The topic format is <scope>:<scope_id>:<resource>:<resource_id>. Use * as the resource_id to subscribe to all resources of a type (e.g., customer_id:<id>:call:* for all call events, customer_id:<id>:transcribe:* for all transcription events). If the WebSocket connection drops, all subscriptions are lost — implement automatic reconnection and re-subscribe after reconnecting.

For the full WebSocket guide, see WebSocket documentation.

Customer Webhook

Configure a webhook URI on your customer account. VoIPBIN sends HTTP POST requests to this URI for all events associated with your account — no per-event-type filtering is needed.

Update your customer’s webhook configuration:

$ curl --request PUT 'https://api.voipbin.net/v1.0/customer?token=<your-token>' \
    --header 'Content-Type: application/json' \
    --data-raw '{
        "webhook_method": "POST",
        "webhook_uri": "https://your-server.com/voipbin/events"
    }'

Response:

{
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "webhook_method": "POST",
    "webhook_uri": "https://your-server.com/voipbin/events",
    ...
}

Once configured, VoIPBIN sends a POST request to your webhook_uri each time any event occurs for your account. Your endpoint must respond with HTTP 200 within 5 seconds.

Fields:

  • webhook_method (enum String): The HTTP method used for webhook delivery. Use "POST".

  • webhook_uri (String): The URI where event notifications are sent. Must be publicly accessible.

Note

AI Implementation Hint

The customer webhook receives all event types for your account — there is no filtering by event type. For local development, use a tunneling tool (e.g., ngrok) to expose your local server. To stop receiving events, set webhook_uri to an empty string "".

Troubleshooting
  • No events received via WebSocket:
    • Cause: Subscription topic does not match your customer ID, or subscription was sent before the connection opened.

    • Fix: Verify the customer ID in the topic matches your account (from GET https://api.voipbin.net/v1.0/customer). Send the subscribe message only after the on_open callback fires.

  • No events received via customer webhook:
    • Cause: The webhook_uri is not publicly accessible, or the endpoint does not return HTTP 200 within 5 seconds.

    • Fix: Verify the URI is reachable from the internet. For local development, use a tunneling tool (e.g., ngrok). Check your server logs for incoming requests.

  • 403 Forbidden on customer update:
    • Cause: The authenticated user lacks admin permission to update the customer.

    • Fix: Verify you are using an admin-level token. Regular agents cannot modify customer settings.

Make your first Hello World

Try VoIPBIN’s core communication APIs:

  1. Your First Call — Place an outbound voice call with text-to-speech.

  2. Your First Real-Time Voice Interaction — Make a call with live transcription and speak into the call using the TTS API.

Note

AI Implementation Hint

Tutorial 2 (Real-Time Voice) builds on the extension and event setup from the previous section. Retain your authentication token (String) or accesskey (String) from Getting Started for all tutorials. Tokens expire after 7 days — if you receive a 401 Unauthorized response, re-authenticate via POST /auth/login.

Call

Make an outbound voice call to the extension you created in Extension & Softphone Setup.

  • A valid authentication token (String) or accesskey (String). See Authentication.

  • A source phone number in E.164 format (e.g., +15551234567). Must be a number owned by your VoIPBIN account. Obtain available numbers via GET /numbers.

  • A registered SIP extension and softphone. See Extension & Softphone Setup.

Note

AI Implementation Hint

Phone numbers must be in E.164 format: + followed by country code and number, no dashes or spaces (e.g., +15551234567, +821012345678). The source number must be a VoIPBIN-owned number — using an unowned number will result in a 400 Bad Request. The destination type is extension (not tel), and target_name (String) is the extension’s name field from the Extension & Softphone Setup.

Make your first call

This example calls your registered extension and plays a text-to-speech greeting. Make sure Linphone is registered and ready to receive the call.

$ curl --request POST 'https://api.voipbin.net/v1.0/calls?token=<your-token>' \
    --header 'Content-Type: application/json' \
    --data-raw '{
        "source": {
            "type": "tel",
            "target": "<your-source-number>"
        },
        "destinations": [
            {
                "type": "extension",
                "target_name": "quickstart-phone"
            }
        ],
        "actions": [
            {
                "type": "talk",
                "option": {
                    "text": "Hello. This is a VoIPBIN test call. Thank you, bye.",
                    "language": "en-US"
                }
            }
        ]
    }'

When calling an extension, the response contains a groupcalls entry (the system creates a group call to ring the extension):

{
    "calls": [],
    "groupcalls": [
        {
            "id": "8dfb3692-9403-49d5-b2b2-031ef7f52d9d",
            "customer_id": "550e8400-e29b-41d4-a716-446655440000",
            "status": "progressing",
            "source": {
                "type": "tel",
                "target": "<your-source-number>"
            },
            "destinations": [
                {
                    "type": "extension",
                    "target_name": "quickstart-phone"
                }
            ],
            "ring_method": "ring_all",
            "answer_method": "hangup_others",
            "call_ids": [
                "98146643-1b81-498c-a3e2-9dd480899945"
            ],
            "call_count": 1,
            ...
        }
    ]
}

Linphone rings — answer the call to hear the TTS greeting.

Note

AI Implementation Hint

When calling an extension, the response contains a groupcalls array (not calls). The groupcalls[0].id (UUID) is the group call ID. The individual call IDs are in groupcalls[0].call_ids — use these with GET /calls/{id} to check call status or DELETE /calls/{id} to hang up. The ring_method (String) controls how multiple devices are rung (ring_all rings all registered devices simultaneously). For the full call lifecycle and advanced call scenarios, see Call overview and Call tutorial.

Troubleshooting
  • 400 Bad Request:
    • Cause: The source number is not owned by your VoIPBIN account, or the phone number is not in E.164 format.

    • Fix: Verify your numbers via GET /numbers. Ensure all phone numbers start with + followed by digits only (e.g., +15551234567).

  • Call created but Linphone does not ring:
    • Cause: Linphone is not registered, or the target_name does not match the extension name.

    • Fix: Verify Linphone shows “Registered” status. Verify the target_name in the call request matches the extension name from Extension & Softphone Setup exactly (case-sensitive).

  • Call status immediately shows “hangup”:
    • Cause: The destination extension has no registered devices, or the source number has no telephony provider attached.

    • Fix: Verify Linphone is registered. Check extension status via GET /extensions.

Real-Time Voice Interaction

This scenario walks through making a call with live transcription and speaking into the call using the real-time TTS API.

By the end, you will have:

  • A live call with real-time speech-to-text transcription

  • Real-time text-to-speech injected into the call via the Speaking API

  • A valid authentication token (String) or accesskey (String). See Authentication.

  • A source phone number in E.164 format (e.g., +15551234567). Must be a number owned by your VoIPBIN account. Obtain available numbers via GET /numbers.

  • Your customer ID (UUID). Obtained from GET https://api.voipbin.net/v1.0/customer or from your admin console profile.

  • A registered SIP extension and softphone. See Extension & Softphone Setup.

  • Event subscription set up (WebSocket or customer webhook). See Receiving Events.

Note

AI Implementation Hint

This scenario requires a registered softphone (Linphone) to answer the call and speak. AI agents can execute all API calls and instruct the human for answering the call on Linphone. Set up event delivery before starting — you will need it to observe transcription events during the call.

Make a call to the extension

With event subscription configured and Linphone registered, make an outbound call to the extension. This call starts real-time transcription, plays a TTS greeting, and then sleeps to keep the call alive while you interact.

$ curl --request POST 'https://api.voipbin.net/v1.0/calls?token=<your-token>' \
    --header 'Content-Type: application/json' \
    --data-raw '{
        "source": {
            "type": "tel",
            "target": "<your-source-number>"
        },
        "destinations": [
            {
                "type": "extension",
                "target_name": "quickstart-phone"
            }
        ],
        "actions": [
            {
                "type": "transcribe_start",
                "option": {
                    "language": "en-US"
                }
            },
            {
                "type": "talk",
                "option": {
                    "text": "Hello. This is the VoIPBIN real-time voice interaction test. You can speak now and your speech will be transcribed. The call will stay open for you to test the Speaking API.",
                    "gender": "female",
                    "language": "en-US"
                }
            },
            {
                "type": "sleep",
                "option": {
                    "duration": 600000
                }
            }
        ]
    }'

Response:

{
    "calls": [
        {
            "id": "e2a65df2-4e50-4e37-8628-df07b3cec579",
            "source": {
                "type": "tel",
                "target": "<your-source-number>",
                "target_name": ""
            },
            "destination": {
                "type": "extension",
                "target_name": "quickstart-phone"
            },
            "status": "dialing",
            "direction": "outgoing",
            ...
        }
    ],
    "groupcalls": []
}

Save the call id (UUID) from calls[0].id in the response — you will need it when creating a speaking stream.

Call status lifecycle (enum string):

  • dialing: The system is currently dialing the destination extension.

  • ringing: The destination device (Linphone) is ringing, awaiting answer.

  • progressing: The call is answered. Audio is flowing between parties.

  • terminating: The system is ending the call.

  • canceling: The originator canceled the call before it was answered (outgoing calls only).

  • hangup: The call has ended. Final state — no further changes possible.

What happens next:

  1. Linphone rings. Answer the call.

  2. The TTS greeting plays (you hear it through Linphone).

  3. The call enters the sleep action (600 seconds = 10 minutes), keeping the call alive.

  4. Transcription is active — anything you say into Linphone is transcribed.

Note

AI Implementation Hint

The source number must be a VoIPBIN-owned number (from GET /numbers). The destination type is extension (not tel), and target_name (String) is the extension’s name field from the Extension & Softphone Setup. The sleep duration (Integer, milliseconds) keeps the call alive — 600000 = 10 minutes. The transcribe_start action uses BCP47 language codes (e.g., en-US, ko-KR, ja-JP).

Observe real-time transcription

After answering the call on Linphone, you receive transcription events via your configured event subscription (WebSocket or customer webhook). The examples below show WebSocket event payloads.

The TTS greeting appears first (direction: "out" — VoIPBIN to caller):

{
    "event_type": "transcript_created",
    "timestamp": "2026-02-21T10:05:02.000000Z",
    "topic": "customer_id:<your-customer-id>:transcribe:<transcribe-id>",
    "data": {
        "id": "9d59e7f0-7bdc-4c52-bb8c-bab718952050",
        "transcribe_id": "8c5a9e2a-2a7f-4a6f-9f1d-debd72c279ce",
        "direction": "out",
        "message": "Hello. This is the VoIPBIN real-time voice interaction test. You can speak now and your speech will be transcribed.",
        "tm_create": "2026-02-21T10:05:02.233415Z"
    }
}

Transcript event fields:

  • data.transcribe_id (UUID): The transcription session ID, generated internally when the transcribe_start action executes. Query all transcripts for this session via GET /transcripts?transcribe_id=<transcribe_id>.

  • data.direction (enum String): "in" — speech from the caller to VoIPBIN. "out" — speech from VoIPBIN to the caller (TTS output).

  • data.message (String): The transcribed text.

When you speak into Linphone, your speech appears as direction: "in" (caller to VoIPBIN):

{
    "event_type": "transcript_created",
    "data": {
        "transcribe_id": "8c5a9e2a-2a7f-4a6f-9f1d-debd72c279ce",
        "direction": "in",
        "message": "Hi, this is a test of the transcription feature.",
        "tm_create": "2026-02-21T10:05:15.100000Z"
    }
}

If you run the Python WebSocket example from Receiving Events, you will see output like:

Subscribed to call and transcribe events. Waiting...
[EVENT] call_progressing
[TRANSCRIBE out] Hello. This is the VoIPBIN real-time voice interaction test...
[TRANSCRIBE in] Hi, this is a test of the transcription feature.

Create a speaking stream

While the call is active, you can inject real-time text-to-speech audio using the Speaking API. Create a speaking stream attached to the call.

POST /speakings with:

  • reference_type (String, Required): "call"

  • reference_id (UUID, Required): The call id from the call response above

  • language (String, Optional): BCP47 language code (e.g., "en-US")

  • provider (String, Optional): TTS provider. Use "elevenlabs" for high-quality streaming TTS

  • direction (enum String, Optional): Controls which side of the call hears the TTS audio. One of: "out" (the call’s destination hears the TTS), "in" (the call’s source hears the TTS), "both" (both sides hear). In this scenario, use "out" so the Linphone user (destination) hears the TTS.

$ curl --request POST 'https://api.voipbin.net/v1.0/speakings?token=<your-token>' \
    --header 'Content-Type: application/json' \
    --data-raw '{
        "reference_type": "call",
        "reference_id": "<call-id>",
        "language": "en-US",
        "provider": "elevenlabs",
        "direction": "out"
    }'

Response (HTTP 201 Created):

{
    "id": "f1e103d2-0429-4170-83b3-e95e29bb0ca8",
    "customer_id": "550e8400-e29b-41d4-a716-446655440000",
    "reference_type": "call",
    "reference_id": "<call-id>",
    "language": "en-US",
    "provider": "elevenlabs",
    "voice_id": "",
    "direction": "out",
    "status": "initiating",
    "pod_id": "",
    "tm_create": "2026-02-21T10:06:00.000000Z",
    "tm_update": "",
    "tm_delete": ""
}

Save the speaking id (UUID) — you will use it to send text-to-speech.

Speaking status lifecycle (enum string):

  • initiating: The speaking stream is being set up and connecting to the TTS provider.

  • active: The TTS provider is connected. You can now call POST /speakings/{id}/say to speak.

  • stopped: The speaking stream has been stopped or the call has ended.

Note

AI Implementation Hint

The speaking stream must be created while the call is in progressing status (answered and audio flowing). If the call has already hung up, the API returns 400 Bad Request. The direction field controls which side of the call hears the TTS: "out" means the called party (Linphone) hears it. The status transitions from initiating to active once the TTS provider connects.

Speak via TTS API

Send text to the speaking stream to have it spoken into the call in real time.

POST /speakings/{id}/say with:

  • text (String, Required): The text to speak. Maximum 5000 characters.

$ curl --request POST 'https://api.voipbin.net/v1.0/speakings/<speaking-id>/say?token=<your-token>' \
    --header 'Content-Type: application/json' \
    --data-raw '{
        "text": "Hello, how are you today? This is VoIPBIN speaking to you in real time using the ElevenLabs text-to-speech engine."
    }'

Response:

{
    "id": "f1e103d2-0429-4170-83b3-e95e29bb0ca8",
    "reference_type": "call",
    "reference_id": "<call-id>",
    "language": "en-US",
    "provider": "elevenlabs",
    "direction": "out",
    "status": "active",
    ...
}

You should hear the text spoken through Linphone within a second or two. You can call /say multiple times to queue additional speech.

Additional speaking operations:

  • POST /speakings/{id}/flush — Clear the speech queue (stop any pending text from being spoken).

  • POST /speakings/{id}/stop — Stop the current speech immediately.

  • DELETE /speakings/{id} — Delete the speaking stream entirely.

Note

AI Implementation Hint

You can call POST /speakings/{id}/say multiple times. Each call queues text for sequential playback. If you need to interrupt, call POST /speakings/{id}/flush first, then POST /speakings/{id}/say with new text. The text field has a 5000-character limit per request. Since transcription is still active, the TTS output will also appear in transcription events as direction: "out".

Troubleshooting
  • Call created but Linphone does not ring:
    • Cause: Linphone is not registered, or the target_name does not match the extension name.

    • Fix: Verify Linphone shows “Registered” status. Verify the target_name in the call request matches the extension name from the Extension & Softphone Setup exactly (case-sensitive). See Extension troubleshooting for registration issues.

  • No transcription events received:
  • Speaking creation returns 400 Bad Request:
    • Cause: The call is not in progressing status (not yet answered or already hung up), or the reference_id is invalid.

    • Fix: Verify the call status via GET /calls/<call-id>. The call must be answered (status: "progressing") before creating a speaking stream.

  • Speaking say returns 400 Bad Request:
    • Cause: The text field is empty or exceeds 5000 characters, or the speaking stream is no longer active.

    • Fix: Verify the text is non-empty and under 5000 characters. Check the speaking status via GET /speakings/<speaking-id>.

  • 401 Unauthorized on any API call:
    • Cause: Token has expired (older than 7 days) or is malformed.

    • Fix: Generate a new token via POST /auth/login. Ensure the Authorization header uses the format Bearer <token> (with a space after “Bearer”), or pass the token as a query parameter ?token=<token>.

  • 404 Not Found on speaking or call operations:
    • Cause: The call ID or speaking ID is incorrect, or the resource has been deleted.

    • Fix: Verify the ID by listing resources via GET /calls or GET /speakings.

  • One-way audio (can hear TTS but Linphone speech is not transcribed):
    • Cause: NAT or firewall blocking RTP traffic.

    • Fix: Enable STUN in Linphone settings (use stun.linphone.org). Ensure your network allows UDP traffic on ports 5060 and 10000-20000.

Sandbox

The VoIPBIN Sandbox lets you run the entire VoIPBIN platform on your local machine with a single command. No sign-up required — it comes with a pre-configured admin account and is ready to use immediately.

Note

AI Implementation Hint

The Sandbox is a fully functional local VoIPBIN instance. When working with the Sandbox, replace all API URLs from https://api.voipbin.net to https://api.voipbin.test:8443. The Sandbox uses a self-signed SSL certificate, so add -k (insecure) flag to curl commands. The default credentials are admin@localhost / admin@localhost.

Prerequisites

  • Docker and Docker Compose v2

Install and Run

Clone the sandbox repository and launch the interactive CLI:

$ git clone https://github.com/voipbin/sandbox.git
$ cd sandbox
$ sudo ./voipbin

Initialize and start the services:

voipbin> init
voipbin> start

Once started, you can log in with the default admin account:

For full documentation, troubleshooting, and advanced usage, see the Sandbox GitHub repository.

Using Tutorials with the Sandbox

All tutorials in this documentation use https://api.voipbin.net as the API endpoint. If you are using the sandbox, substitute it with your local sandbox endpoint:

  • Production: https://api.voipbin.net

  • Sandbox: https://api.voipbin.test:8443

For example, to generate a token using the sandbox:

$ curl -k --request POST 'https://api.voipbin.test:8443/auth/login' \
    --header 'Content-Type: application/json' \
    --data-raw '{
        "username": "admin@localhost",
        "password": "admin@localhost"
    }'

Troubleshooting

  • “init” or “start” command fails:
    • Cause: Docker daemon is not running or Docker Compose v2 is not installed.

    • Fix: Verify Docker is running with docker compose version. Must show v2.x or later.

  • Port 8443 already in use:
    • Cause: Another service is occupying port 8443.

    • Fix: Stop the conflicting service, or check with ss -tlnp | grep 8443 (Linux) / lsof -i :8443 (macOS).

  • SSL certificate error (without ``-k`` flag):
    • Cause: The Sandbox uses a self-signed SSL certificate that is not trusted by default.

    • Fix: Add the -k flag to all curl commands when using the Sandbox.

  • 401 Unauthorized with default credentials:
    • Cause: The Sandbox has not fully initialized.

    • Fix: Run voipbin> init again and wait for it to complete before running voipbin> start.

What’s Next

Now that you have completed the quickstart, explore the full capabilities of VoIPBIN:

  • Flow — Build programmable voice workflows with the visual flow builder.

  • AI — Integrate AI-powered voice agents with real-time speech processing.

  • Conference — Set up multi-party conferencing.

  • Conversation — Manage messaging conversations.

  • Queue — Route incoming calls to available agents with queue management.

For the complete API reference, visit the API documentation.

Note

AI Implementation Hint

The full OpenAPI 3.0 specification is available as machine-readable JSON at https://api.voipbin.net/openapi.json. Use this for automated client generation, API discovery, and building integrations programmatically.