Daily Bots allows purchasing numbers directly via the dashboard and using them to call your bot via phone.

To get started, buy a number via the dashboard or using the curl commands.

Creating a dial-in route

Dial-in requires additional dialin_settings properties that are passed to the Daily Bots /start endpoint.

To accommodate this, we’ll create a new Next.js route in the api directory:

app/api/dialin/route.ts
// [POST] /api
export async function POST(request: Request) {
  const { test, callId, callDomain } = await request.json();

  if (test) {
    // Webhook creation test response
    return new Response(JSON.stringify({ test: true }), { status: 200 });
  }

  if (!callId || !callDomain || !process.env.DAILY_BOTS_KEY) {
    return Response.json(`callId and/or callDomain not found on request body`, {
      status: 400,
    });
  }

  const payload = {
    bot_profile: "voice_2024_10",
    max_duration: 600,
    dialin_settings: {
      callId,
      callDomain,
    },
    services: {
      llm: "together",
      tts: "cartesia",
    },
    config: [
      {
        service: "tts",
        options: [
          { name: "voice", value: "79a125e8-cd45-4c13-8a67-188112f4dd22" },
        ],
      },
      {
        service: "llm",
        options: [
          {
            name: "model",
            value: "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo",
          },
          {
            name: "initial_messages",
            value: [
              {
                role: "system",
                content:
                  "You are a assistant called ExampleBot. You're talking on the phone. You can ask me anything. Keep responses brief and legible.",
              },
            ],
          },
          { name: "run_on_config", value: true },
        ],
      },
    ],
  };

  const req = await fetch("https://api.daily.co/v1/bots/start", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.DAILY_BOTS_KEY}`,
    },
    body: JSON.stringify(payload),
  });

  const res = await req.json();

  if (req.status !== 200) {
    return Response.json(res, { status: req.status });
  }

  return Response.json(res);
}

We’re defining our config in the route directly, as dial-in bypasses the web client. You can abstract this into a separate file (e.g. rtvi.config.ts) if you prefer, allowing you to use the same config for both dial-in and web.

Externally exposing your new route in local development

Since we’re working locally, we’ll need create a publically accessible URL for Daily to connect when a person dials the number.

We’ll use ngrok to create a tunnel to our local server by running the following in our terminal:

ngrok http localhost:3000

Your Next.js server may be running on a different port than 3000. Update the command accordingly.

This will give us a publically accessible https URL that points to our local server.

Remember to update the URL your number points outside of local development.

Configure via the Daily Bots dashboard

Navigate to the dashboard and click on the plus icon next to the number you purchased. A modal will open, select Self-hosted: provide a webhook URL. You’ll then see a field to enter a webhook URL:

Enter your ngrok URL (e.g. https://123.ngrok.app/api/dialin) and click Save.

Configuring via REST

If you’d prefer to do this programmatically, Daily provides a REST API, pinless_dialin for configuring your purchased number.

Let’s point it at our newly created ngrok tunnel URL:

curl --location --request POST 'https://api.daily.co/v1/' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer $YOUR_DAILY_BOTS_API_KEY' \
--data '{
    "properties": {
        "pinless_dialin": [
            {
                "phone_number": "$YOUR_PURCHASED_NUMBER (e.g.+12345)",
                "room_creation_api": "$YOUR_NGROK_TUNNEL_URL/api/dialin",
                "name_prefix": "DailyBot"
            }
        ]
    }
}'

pinless_dialin is an array that can contain forwarding rules for several phone_numbers. These phone numbers can map to the same room_creation_api or different URLs.

If you have configured multiple phone numbers, make note that the all phone_numbers should be present when updating a single phone_number. The API does not do partial array updates, i.e., a POST request will override the existing pinless_dialin array with the new value. Hence, to keep existing routes, you should them all.

Assuming all is well, you should receive a JSON response (and see a test webhook creation request arrive at the route.) In addition, when a call is received, the webhook $YOUR_NGROK_TUNNEL_URL/api/dialin contains the dialin_settings, which are passed to the /bot/start. The dialin_settingsare:

// data received on the webhook, map it to `dialin_settings`
{
  "From": "+CALLERS_PHONE",
  "To": "$YOUR_PURCHASED_NUMBER",
  "callId": "callid-read-only-string",
  "callDomain": "callDomain-read-only-string"
}

The To and From denote your phone number and the caller’s number, respectively. The callId and callDomain are Daily internal identifiers that are used to connect the incoming call to the bot (which in the interim period is on hold and listening to hold_music.)

Dialing in

Time to give our bot a call!

When you’re dialing from outside America, to avoid international carrier rates by your carrier, you can use Skype (or similar such service) to connect to the number you purchased.

Assuming all is well, you should hear the bot’s initial message and be able to interact with it.