API reference / Outreach
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:
- Point the trunk at us. In your carrier's portal, send inbound calls for
the number to
sip.dropcowboy.comon port 5060. If your carrier supports SRV records, use_sip._udp.sip.dropcowboy.cominstead, 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. Acustomtrunk is set up by hand. - 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 sendconfirm_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, soaudio_urlisn't allowed. Connect a carrier first, or send amedia_id. - The send failed with
3017or3018.sti_orig_idisn't a UUID, orsti_attestationisn'tA,BorC.
For more symptoms and fixes, see Troubleshooting. Every code is in Outcomes.