API reference / Get started
Authentication
Every request to https://api-v2.dropcowboy.com needs a credential. Use an
API key from your own server; you create these yourself. AI tools connect
with an OAuth token from a user's sign-in. Browser widgets use
short-lived site tokens, which your server mints with its own credential.
| Credential | Send it as | Use it for |
|---|---|---|
| API key | x-key and x-secret headers |
Server-to-server calls. Create one in the dashboard or with the API. |
| OAuth token, user sign-in | Authorization: Bearer <access_token> |
AI tools connected through our MCP server, acting as the signed-in user |
| OAuth token, client credentials | Authorization: Bearer <access_token> |
A machine client set up by support |
| Site token | Passed to the widget | Building Blocks widgets in a browser |
If a request carries both an API key and a bearer token, the API key is used.
Keep keys and secrets on your server. Never put them in browser code, a mobile app or a public repository.
API keys
An API key is a pair: the key, sent as x-key, and its secret, sent as
x-secret. Create one in the dashboard under Developers > API keys, or
with the API below. The secret is shown once, when the key is created.
Store both in environment variables and send them on every request:
export DC_KEY="7f3c9a1e-5b2d-4e8f-a6c4-9d1b3e7f5a20"
export DC_SECRET="c8e2a4f6-1d3b-4a9c-8e7f-2b5d9a1c6e34"
curl https://api-v2.dropcowboy.com/register/public/account \
-H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET"
Manage keys with the API
| Method | Route | Scope | What it does |
|---|---|---|---|
GET |
/register/public/apikeys |
balance:read |
List your keys |
POST |
/register/public/apikeys |
account:write |
Create a key |
DELETE |
/register/public/apikeys/{key_id} |
account:write |
Delete a key |
List keys
GET /register/public/apikeys returns every key on your account, without
secrets.
curl https://api-v2.dropcowboy.com/register/public/apikeys \
-H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET"
{
"data": [
{
"_id": "3b8e1f6a-9c4d-4a2e-b7f5-1d6c8a3e9b47",
"name": "Order notifications",
"key": "7f3c9a1e-5b2d-4e8f-a6c4-9d1b3e7f5a20",
"type": "standard",
"team_id": "4e9a2c7f-1b5d-4f3e-8c6a-9d2b7e4f1a53",
"scopes": ["contacts:read", "sms:send"],
"mcp_client": null,
"created_at": 1774041600000,
"expires_at": null,
"last_used_at": 1774128000000,
"request_count": 1842
}
],
"meta": { "request_id": "c2a6e8f4-3b9d-4c1e-8a7f-5d3b1e9c6a24" }
}
| Field | Type | Description |
|---|---|---|
_id |
string | The key's id. Pass it to the delete route. |
name |
string | The name you gave the key. |
key |
string | The x-key value. It isn't the id, so don't pass it to the delete route. |
type |
string | standard for keys you create. |
team_id |
string | Your account id. |
scopes |
array | The scopes the key holds. |
mcp_client |
string | The AI tool the key was created for in the dashboard's Connect AI flow, such as cursor. null otherwise. |
created_at |
integer | When the key was created, in epoch milliseconds. |
expires_at |
integer | When the key expires, in epoch milliseconds. null if it never does. |
last_used_at |
integer | The last authenticated request, in epoch milliseconds. null if never used. |
request_count |
integer | Authenticated requests made with the key. |
Create a key
POST /register/public/apikeys creates a key and returns its secret. Your
account must be verified first.
curl -X POST https://api-v2.dropcowboy.com/register/public/apikeys \
-H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET" \
-H "Content-Type: application/json" \
-d '{
"name": "Order notifications",
"scopes": ["contacts:read", "sms:send"]
}'
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | No | A name to recognise the key by. Defaults to "API Key - " plus today's date. |
scopes |
array | No | The scopes the new key holds. Defaults to the scopes of the credential making the request. |
expires_in_seconds |
integer | No | Delete the key automatically after this many seconds, from 300 to 31536000 (one year). Omit it for a key that never expires. |
The response is the key's fields from the list, plus secret. Store the
secret now. It isn't shown again.
{
"data": {
"_id": "8d2f6b9e-4a1c-4e7b-9f3d-5c8a2e6b1d74",
"name": "Order notifications",
"key": "2a6d9f3b-8e1c-4b5a-9d7f-3e6b1c8a4f92",
"secret": "5e9b2d7f-3a8c-4f1e-b6d2-8c4a1f7e3b59",
"type": "standard",
"team_id": "4e9a2c7f-1b5d-4f3e-8c6a-9d2b7e4f1a53",
"scopes": ["contacts:read", "sms:send"],
"mcp_client": null,
"created_at": 1774041600000,
"expires_at": null
},
"meta": { "request_id": "c2a6e8f4-3b9d-4c1e-8a7f-5d3b1e9c6a24" }
}
A new key can't hold a scope the credential creating it lacks. Asking for one
returns 403.
Short-lived keys for agents and scripts
Give an AI agent, a CI job or a one-off script its own key, with only the scopes it needs and an expiry:
curl -X POST https://api-v2.dropcowboy.com/register/public/apikeys \
-H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET" \
-H "Content-Type: application/json" \
-d '{
"name": "Nightly contact sync",
"scopes": ["contacts:read", "lists:read"],
"expires_in_seconds": 3600
}'
When it expires, the key stops working and requests with it get 401. It's
deleted permanently, usually within two minutes, and then drops out of the
list.
Delete a key
DELETE /register/public/apikeys/{key_id} deletes a key. It stops working
immediately. Pass the key's _id, not its key value.
curl -X DELETE https://api-v2.dropcowboy.com/register/public/apikeys/3b8e1f6a-9c4d-4a2e-b7f5-1d6c8a3e9b47 \
-H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET"
{
"data": { "deleted": true },
"meta": { "request_id": "c2a6e8f4-3b9d-4c1e-8a7f-5d3b1e9c6a24" }
}
An unknown id returns 404.
Rotate a key
- Create a new key.
- Deploy it everywhere the old key is used.
- Watch the old key's
last_used_atin the list until it stops changing. - Delete the old key.
OAuth 2.0 user sign-in
AI tools connect through the Drop Cowboy® MCP server by having a user sign in, using the OAuth authorization code flow with PKCE. The tool then holds a bearer token that acts as that user on their account. Its scopes come from the user's AI access on their role: full access gets the standard scopes, view-only access gets the read scopes, and a user with no AI access can't connect. The MCP server handles the sign-in for you; the dashboard's Connect AI page shows how to add it to each tool.
For a tool that can't sign in, create a key on the Connect AI page instead. It's an API key whose scopes are capped by the same AI access.
OAuth 2.0 client credentials
Machine clients belong to one account and are set up by Drop Cowboy support; there's no self-serve way to create one yet. For your own servers, an API key does the same job. Request a token with the audience set to the API host:
curl https://login.dropcowboy.com/oauth/token \
-d grant_type=client_credentials \
-d client_id="$DC_CLIENT_ID" \
-d client_secret="$DC_CLIENT_SECRET" \
-d audience=https://api-v2.dropcowboy.com
Send the access_token from the response as a bearer credential:
curl "https://api-v2.dropcowboy.com/contact/public/contacts?limit=10" \
-H "Authorization: Bearer $DC_TOKEN"
The token holds the scopes granted to your client. Each route needs one of the scopes listed for it. Reuse a token until it expires, then request a new one.
The send routes check credentials after the 202
POST /rvm, /sms, /voice-broadcast and /ai-broadcast answer 202
before they check your credential, so a wrong key arrives later as outcome
3007 on your callback_url. See
What a 202 means.
Scopes
A scope grants access to a group of routes. API keys and OAuth tokens both carry scopes. Each route accepts the scopes listed in its page's routes table, and a credential needs any one of them.
| Scope | Grants |
|---|---|
contacts:read |
Contacts, notes, follow-ups, custom fields, tags, timelines, call history, text history, email history, documents, Inbox Tasks, templates, users, pipelines |
contacts:write |
Create and change contacts, tags, notes, follow-ups, documents, Inbox Tasks and pipelines |
lists:read |
Contact lists and their members |
lists:write |
Create and change lists, and add or remove members |
campaigns:read |
Campaigns and their stats |
campaigns:write |
Create, update and delete campaigns |
campaigns:send |
Start and pause campaigns |
rvm:send |
Send ringless voicemail |
sms:send |
Send texts and reply to a text |
voice:send |
Send voice broadcasts and AI calls, and synthesize speech |
email:send |
Send email |
email:read |
Sending mailboxes |
media:read |
Media, voices and transcription |
media:write |
Upload media, and clone, design and delete voices |
webhooks:read |
Webhook subscriptions, signing secrets and event types |
webhooks:write |
Subscribe and unsubscribe webhooks |
balance:read |
Account details, balance, API keys, Detection keys and Building Blocks readiness |
account:write |
Create and delete API keys and Detection keys, enable Building Blocks, connect a carrier |
agents:read |
AI agents, agent templates and knowledge bases |
agents:write |
Create, change, publish and delete AI agents and knowledge bases |
consent:read |
Consent records |
consent:write |
Record and revoke consent |
dnc:read |
Check the do-not-contact list |
dnc:write |
Add to and remove from the do-not-contact list |
numbers:read |
Phone numbers, phone lines and IVRs |
numbers:write |
Rent numbers, manage phone lines, mint site tokens |
chat:read |
Web chat sites |
chat:write |
Reply to a web chat |
Give each key only the scopes it needs. The OpenAPI spec lists the exact scopes for every route.
Embed site tokens
Building Blocks widgets run in a browser, so they never see your API key.
Your server mints a short-lived site token with
POST /phone/public/embed/token (scope numbers:write) and hands it to the
page.
curl -X POST https://api-v2.dropcowboy.com/phone/public/embed/token \
-H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET" \
-H "Content-Type: application/json" \
-d '{
"site_id": "9d3f7b1e-2c8a-4f5d-b6e9-4a1c7d3f8b25",
"sub": "agent-42",
"scope": "dialer:webrtc",
"ttl_seconds": 900
}'
| Field | Type | Required | Description |
|---|---|---|---|
site_id |
string | No | The embed site from the dashboard. |
sub |
string | No | Your own id for the visitor or agent using the widget. |
scope |
string or array | No | What the widget may do: dialer:webrtc, phone:hub, contacts, campaigns, media, voice or detection. Separate several with spaces, or pass an array. Defaults to dialer:webrtc. |
ttl_seconds |
integer | No | How long the token lasts, up to 3600. Defaults to 3600. |
{
"data": {
"token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2F1dGguZXhhbXBsZS5jb20vIiwic3ViIjoiYWdlbnQtNDIifQ.c2lnbmF0dXJl",
"expires_at": 1774042500000,
"jti": "1d5b9f3e-7a2c-4e8d-b6f1-3c7a9e5d2b84",
"pool_id": "6e1f3a5c-9b2d-4c8e-a7f1-3d5b9c2e4a61"
},
"meta": { "request_id": "c2a6e8f4-3b9d-4c1e-8a7f-5d3b1e9c6a24" }
}
| Field | Type | Description |
|---|---|---|
token |
string | The site token to give the widget. |
expires_at |
integer | When it expires, in epoch milliseconds. |
jti |
string | The token's unique id. |
pool_id |
string | Your carrier connection, when you have one. |
dialer:webrtc and phone:hub tokens place calls on your own carrier, so
they need a connected carrier and funds. Account
shows how to check both. A site token can't mint another site token, and the
send routes don't accept one.
Errors
| Status | When | What to do |
|---|---|---|
401 |
No credential, a wrong key or secret, or a deleted, expired or invalid credential | Check the headers, or create a new key or token |
403 |
The credential lacks every scope the route accepts. detail names them. |
Use a credential with one of those scopes |
403 |
Creating a key on an unverified account, or asking for scopes you lack | Verify the account, or ask for fewer scopes |
403 |
A dialer site token without a connected carrier | Connect your carrier |
402 |
A dialer site token without funds | Add funds |
400 |
A bad scopes list or expires_in_seconds when creating a key |
Fix the field named in detail |
404 |
Deleting a key that doesn't exist | Check the _id |
Errors use the format in Responses, errors and limits.
API access is subject to rate limits and usage policies. API availability, endpoints, and features may change with notice. Breaking changes will be communicated via changelog with migration period when possible. API keys must be kept secure. Customers are responsible for all activity under their API credentials.