Bring your own carrier

Bring-your-own-carrier (BYOC) accounts send over a carrier account or SIP trunk they already have. The caller ID is your number at that carrier, and the audio can be a file you host yourself. Neither needs to exist in Drop Cowboy®.

Setup is two steps: connect the carrier once, then send as usual with caller_id and audio_url. To take inbound calls on those numbers, you also load them onto a phone line and point your trunk at Drop Cowboy.

For every way to use your numbers, with code in curl, Node.js and Python, see Use your own numbers on BYOC.

How routing works

A send has no SIP route, trunk or host field. The route is part of the carrier connection. When you connect a carrier, Drop Cowboy builds the voice and ringless voicemail routes that point at its trunk, and every send goes out through them. To change where calls go, connect a different carrier or update the one you have. See Connect a carrier.

1. Connect your carrier

POST /integration/public/byoc/connect with the account:write scope. The credentials keys depend on the carrier:

provider credentials keys
twilio account_sid, auth_token
thinq account_id, account_token, username, password
telnyx api_key
signalwire space_url, project_id, api_token
flowroute access_key, secret_key
plivo auth_id, auth_token
bandwidth account_id, username, password, site_id, application_id
vonage api_key, api_secret
sinch project_id, key_id, key_secret
custom carrier_name, sip_host, sip_port, username, password

Use custom for a SIP trunk that isn't listed. sip_host is the trunk's hostname or IP address and sip_port is its port, usually 5060. Send username and password when the trunk uses credential authentication, and leave them out when it trusts your source IP.

curl -X POST https://api-v2.dropcowboy.com/integration/public/byoc/connect \
  -H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "custom",
    "credentials": {
      "carrier_name": "Example Carrier",
      "sip_host": "sip.carrier.example",
      "sip_port": "5060",
      "username": "example-trunk-user",
      "password": "example-trunk-password"
    },
    "channels": 60,
    "cps": 2,
    "enabled": true,
    "default": true,
    "confirm_leave_retail": true
  }'
Field Type Required Description
provider string Yes One of the carriers in the table.
credentials object Yes The keys for that carrier.
channels integer No The most calls your carrier lets you run at once. Defaults to 20000, so send your carrier's real limit.
cps integer No Calls per second your carrier allows. Defaults to 1.
enabled boolean No Send true to send on this carrier. Defaults to false.
default boolean No Send true to make it your default carrier. Defaults to false.
confirm_leave_retail boolean Unless already on BYOC Send true to allow the plan change described below.

The response carries connected: true, the pool_id, and plan_switched.

The plan change

Connecting a carrier moves an account that isn't on a BYOC plan to one. That releases any phone numbers you rent from Drop Cowboy and changes your billing. Because an API key could do this by accident, the call fails with 400 confirm-leave-retail-required until you send confirm_leave_retail: true. Nothing changes on that failure. Accounts already on BYOC can leave the field out, and plan_switched is false for them.

Check the connection

GET /integration/public/byoc lists the carriers on your account. The credentials are never returned.

2. Send a ringless voicemail

POST /rvm as described in Ringless voicemail, with the BYOC fields:

Field Type Required Description
caller_id string Yes Your number at the carrier, in E.164. It's shown exactly as given, and your carrier has to accept it as a caller ID.
audio_url string One audio option A public http(s) URL of an mp3 or wav file, up to 50 MB. We fetch it when the voicemail is sent. You can send media_id or tts_body with voice_id instead.
byoc.sti_orig_id string No The origination ID your carrier assigned for STIR/SHAKEN signing, as a UUID.
byoc.sti_attestation string No The attestation level for the call: A, B or C.

Don't send phone_line_id. When both are present the phone line wins, and numbers that aren't in Drop Cowboy aren't on a line.

curl -X POST https://api-v2.dropcowboy.com/rvm \
  -H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET" \
  -H "Idempotency-Key: 3b7e9d1a-6c4f-4a82-b5d0-8f1e2c7a9b46" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+13125550142",
    "caller_id": "+17735550188",
    "audio_url": "https://cdn.example.com/voicemails/appointment-reminder.mp3",
    "byoc": {
      "sti_orig_id": "c4a7e1d2-9b3f-4a68-8d05-2e7f6b1a9c34",
      "sti_attestation": "A"
    },
    "foreign_id": "order-1042",
    "callback_url": "https://hooks.example.com/dropcowboy/outcome"
  }'

The route answers 202 and the result arrives on callback_url and the contact.rvm.status webhook. See Where results arrive. Try it on your own phone first, as in the Quickstart.

Receive calls on your own numbers

Sending needs no number inventory. A caller_id that isn't in Drop Cowboy goes out as given. Receiving is different. When a contact calls back the number on their voicemail, the call reaches your carrier first, and Drop Cowboy only sees it if you set up both of these:

  1. Point the trunk at us. In your carrier's portal, send inbound calls for the number to sip.dropcowboy.com on port 5060. If your carrier supports SRV records, use _sip._udp.sip.dropcowboy.com instead, which gives you failover. For Twilio, Telnyx, SignalWire, Plivo, Bandwidth, Vonage, Sinch, Flowroute and Commio (ThinQ), connecting the carrier already sets this up and attaches the numbers you import. A custom trunk is set up by hand.
  2. Put the number on a phone line. The phone line decides what happens to the call: forward, queue, voicemail, an AI agent or whatever you set up. A number that isn't on a phone line has nowhere to go. See Phone numbers.

Load numbers you already own

POST /phone/public/numbers/import loads numbers you already hold at your carrier onto a phone line. It needs the numbers:write scope and a BYOC account. Any other account gets 403 with code byoc_required, because a number loaded here becomes a caller ID your account can send from. A BYOC account with no connected carrier gets 400 with code pool_required.

Field Type Required Description
phone_numbers array of strings Yes The numbers, in E.164, up to 5,000 per request. A value that isn't E.164 fails the whole request with code invalid_phone_numbers and names the values.
phone_line_id string No The phone line the numbers go on. Default: your default voice line. With neither, the request fails with code phone_line_required.
overwrite_routing boolean No A number already on your account keeps its routing unless this is true. Default: false.
iso_country string No Two-letter country code, when it can't be read from the numbers.
curl -X POST https://api-v2.dropcowboy.com/phone/public/numbers/import \
  -H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET" \
  -H "Content-Type: application/json" \
  -d '{ "phone_numbers": ["+13125550142", "+13125550143"] }'

The route answers 202 with a long_job_id. Poll GET /phone/public/numbers/import/{job_id} until status is completed or failed:

{
  "data": {
    "long_job_id": "1d5b9f3e-7a2c-4e8d-b6f1-3c7a9e5d2b84",
    "status": "completed",
    "result": { "total_rows": 2, "added": 2, "updated": 0, "invalid": 0, "conflicts": 0 },
    "error": null
  }
}

Drop Cowboy doesn't check with your carrier that you own the numbers, so load only numbers you hold. A number that another account already holds isn't taken. It's counted in conflicts, and the rest still load. Loading doesn't change anything at your carrier, so point the trunk at us as described above.

If you'd rather work in the dashboard, use Number Pools, from the actions menu of your carrier's pool: Import from Carrier (BYOC) lists the numbers on your carrier account and mirrors the ones you pick, and Import CSV to Phone Line reads them from a CSV.

Texting from a loaded number also needs a registered texting campaign. See Texts.

Why POST /phone/public/numbers/rent doesn't load them

On a BYOC account, that route buys the numbers from your connected carrier and puts them on a phone line. It doesn't load numbers you already own. Use POST /phone/public/numbers/import. To find numbers to buy, search first with POST /phone/public/numbers/available. See Search for numbers to rent.

Who can choose a caller ID

Only a BYOC account can send from a caller_id it names. Every other account sends from its own phone lines, and a caller_id in the request is ignored. Replying to a text with POST /phone/public/sms/reply needs a caller_id that's a number on your account, and a campaign's caller_id must be a number on your account too, unless the account is BYOC.

Disconnect a carrier

POST /integration/public/byoc/disconnect with the provider. See Disconnect a carrier.

Common problems

  • Connect failed with confirm-leave-retail-required. The account isn't on a BYOC plan. Read the plan change, then send confirm_leave_retail: true.
  • Connect failed with invalid-credentials. The carrier rejected them. Check the keys against the table and try again.
  • Connect failed with plan-switch-failed. The carrier was saved but the plan change didn't finish. Send the same request again.
  • People call back and nothing answers. The trunk isn't pointed at us, or the number isn't on a phone line. Do both under Receive calls on your own numbers.
  • Loading numbers failed with byoc_required. The account isn't on a BYOC plan. Connect a carrier first.
  • Loading numbers failed with pool_required. No carrier is connected yet. Connect one, then load the numbers.
  • Some numbers show in conflicts. Another account holds them. They weren't loaded.
  • The send failed with 3014. The account isn't on a BYOC plan or approved to send unreviewed audio, so audio_url isn't allowed. Connect a carrier first, or send a media_id.
  • The send failed with 3017 or 3018. sti_orig_id isn't a UUID, or sti_attestation isn't A, B or C.

For more symptoms and fixes, see Troubleshooting. Every code is in Outcomes.

Code samples

The requests from this page, ready to copy. Set DC_KEY and DC_SECRET to your API key pair first.

1. Connect your carrier
curl -X POST https://api-v2.dropcowboy.com/integration/public/byoc/connect \
  -H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "custom",
    "credentials": {
      "carrier_name": "Example Carrier",
      "sip_host": "sip.carrier.example",
      "sip_port": "5060",
      "username": "example-trunk-user",
      "password": "example-trunk-password"
    },
    "channels": 60,
    "cps": 2,
    "enabled": true,
    "default": true,
    "confirm_leave_retail": true
  }'
2. Send a ringless voicemail
curl -X POST https://api-v2.dropcowboy.com/rvm \
  -H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET" \
  -H "Idempotency-Key: 3b7e9d1a-6c4f-4a82-b5d0-8f1e2c7a9b46" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+13125550142",
    "caller_id": "+17735550188",
    "audio_url": "https://cdn.example.com/voicemails/appointment-reminder.mp3",
    "byoc": {
      "sti_orig_id": "c4a7e1d2-9b3f-4a68-8d05-2e7f6b1a9c34",
      "sti_attestation": "A"
    },
    "foreign_id": "order-1042",
    "callback_url": "https://hooks.example.com/dropcowboy/outcome"
  }'
Load numbers you already own
curl -X POST https://api-v2.dropcowboy.com/phone/public/numbers/import \
  -H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET" \
  -H "Content-Type: application/json" \
  -d '{ "phone_numbers": ["+13125550142", "+13125550143"] }'