Skip to content

Quickstart

Everything below uses a vcp_test_ key. When you have published a version, its test calls route to a mock provider: no PSTN dial ever originates, outcomes are simulated deterministically, and nothing is billed. Your sandbox tenant’s first key is granted the full scope set at onboarding; use it as the bearer token here.

You get that first key from the browser, once, at the end of signup - the onboarding routes are session-authenticated and not key-reachable (Auth & scopes). Everything after it is the API.

The base URL is https://api.vocapable.com. Every request authenticates with Authorization: Bearer vcp_test_….

Keys are shown exactly once at creation, because only a SHA-256 hash and the first 8 characters are retained. scopes[] is required, non-empty, and validated against the catalog - a value that is not a scope is refused with 422 unknown_scope rather than stored, so a typo fails here instead of surfacing later as a baffling 403. Minting requires keys:manage, and a key may only grant scopes it holds itself (see Auth & scopes).

POST /v1/api-keys
curl -X POST https://api.vocapable.com/v1/api-keys \
-H "Authorization: Bearer vcp_test_YOUR_ONBOARDING_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "test",
"name": "quickstart",
"scopes": [
"agents:read", "agents:write",
"calls:read", "calls:write",
"telephony:manage",
"keys:manage"
]
}'

keys:manage is in the list on purpose: without it, the key you just minted cannot rotate itself, and rotation is the only way to replace its secret without a gap.

name and the optional expires_at are worth setting on anything you hand to another system - the name is what you will use to decide which credential to retire, and expiry is enforced at authentication.

GET /v1/me returns the authenticated principal: tenant, key id, mode, and scopes. It declares no scope of its own: authentication alone is its check.

GET /v1/me
curl https://api.vocapable.com/v1/me \
-H "Authorization: Bearer vcp_test_..."

Create an ordinary draft agent directly. name, use_case, and disclosure_config are required; the AI-disclosure opener is mandatory and non-removable. Keep tools empty for this browser/simulated-test-first workflow: those transports have no contact and never invoke contact-bound actions. On a configured live phone deployment, the supported subset is check_calendar, book_meeting, schedule_callback, record_optout, and end_call; calendar actions additionally require the tenant’s active direct Google Calendar connection.

Do not use POST /v1/templates/{template_id}/instantiate unless every tool_grants[] entry is in that supported live-phone subset. Instantiation copies grants into the new agent unchanged; browser and simulated test calls still do not exercise them.

POST /v1/agents
curl -X POST https://api.vocapable.com/v1/agents \
-H "Authorization: Bearer vcp_test_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Qualification draft, pilot",
"use_case": "sdr",
"tools": [],
"disclosure_config": {
"en-US": "Hi, this is Ava, an automated assistant calling for Acme..."
}
}'

Publishing uses a queued scripted release check

Section titled “Publishing uses a queued scripted release check”

Drafts are never dialed. POST /v1/agents/{agent_id}/publish snapshots a definition into an immutable AgentVersion, but it is blocked until the golden-transcript replay suite has produced a passing EvalRun for this exact draft revision. Request that check with POST /v1/agents/{agent_id}/release-gate-evaluations, wait for the deployment’s release worker to complete it, then call publish. A changed draft needs a new check; an old pass cannot authorize it.

The current worker runs built-in golden conversations with sandboxed tools in scripted_stand_in mode. It is not a live model/provider/carrier evaluation. If the release worker is not deployed, the request truthfully remains queued and publish continues to refuse with 409 eval_gate_failed.

POST /v1/agents/{agent_id}/release-gate-evaluations
curl -X POST https://api.vocapable.com/v1/agents/agnt_01.../release-gate-evaluations \
-H "Authorization: Bearer vcp_test_..."

A test call may only dial a number your tenant has proven it controls. Register it, receive a one-time code (SMS to mobiles, a short voice readout to landlines), and confirm it.

That message verifies control of the destination; it is not campaign SMS and does not enable an agent’s send_sms tool.

POST /v1/verified-numbers → check
curl -X POST https://api.vocapable.com/v1/verified-numbers \
-H "Authorization: Bearer vcp_test_..." \
-H "Content-Type: application/json" \
-d '{"phone_e164": "+14155552671", "label": "Founder cell"}'
curl -X POST https://api.vocapable.com/v1/verified-numbers/vn_01.../check \
-H "Authorization: Bearer vcp_test_..." \
-H "Content-Type: application/json" \
-d '{"code": "123456"}'

Only a currently verified number counts, because pending is a registration, not proof. An unverified destination refuses with test_call_destination_not_verified.

POST /v1/agents/{agent_id}/test-call
curl -X POST https://api.vocapable.com/v1/agents/agnt_01.../test-call \
-H "Authorization: Bearer vcp_test_..." \
-H "Content-Type: application/json" \
-d '{
"to": "+14155552671",
"timezone": "America/Los_Angeles",
"jurisdictions": ["US"]
}'

The response is 202 with a campaign-less contact attempt (att_…). Under a vcp_test_ key the dial runs synchronously against mock telephony through the full dial pipeline of gate, materialization, seize, originate, AMD, and status outcome, and the response’s state already carries the outcome the destination’s last digit deterministically selects:

Last digit Simulated outcome
05 A human answers
67 An answering machine (AMD machine_start)
8 Nobody answers
9 The carrier rejects the call

It exercises the dial pipeline, not the audio leg, and mock test calls never draw down the daily live test-call cap.

Not a ceremony - the point of doing it now is that you find out your integration survives a secret change before it is carrying traffic.

POST /v1/api-keys/{key_id}/rotate
curl -X POST https://api.vocapable.com/v1/api-keys/key_01J9.../rotate \
-H "Authorization: Bearer vcp_test_..." \
-H "Content-Type: application/json" \
-d '{"name": "quickstart (rotated)"}'

The response carries the new secret and the replaced_api_key_id of the one it retired, in a single transaction: there is no moment where both work and no moment where neither does. The replacement keeps the retired key’s mode and scopes, so a narrow key can rotate itself.

From here: read Test mode for the full mock semantics, then Campaigns & scrub for the production loop, or Headless mode if your own stack is the system of record.