A. Connect Claude Code
Claude Code speaks MCP over HTTP and handles OAuth itself, including registration by its own client metadata document.
claude mcp add --transport http voixcall https://api.voixcall.com/mcp
claude mcp login voixcall - The second command opens
app.voixcall.comin your browser. Sign in if you need to. - The consent page names the client (Claude Code), the permissions it asked for, and the host it will redirect to. Choose Allow.
- Back in Claude Code, ask: which VoixCall account am I connected to? It calls
voixcall_whoamiand answers with the account's name and email.
To disconnect later, either run claude mcp remove voixcall or open Settings โ Connected apps in the
web app and choose Disconnect; the second one revokes the tokens on our side as well.
B. Connect claude.ai or ChatGPT
In claude.ai, add a custom connector with the URL https://api.voixcall.com/mcp. Leave the
client id and secret fields empty: the connector discovers the authorization server from the URL, registers itself and
runs the OAuth flow. You approve once on app.voixcall.com, then the VoixCall tools appear in the chat.
Hosted connectors use the same standard MCP discovery and OAuth 2.1 flow. Unlike Claude Code (which is verified against
production), they connect from the vendor's servers with an https callback, which we have not yet exercised end to end.
ChatGPT's connector settings take the same server URL and use the same standard MCP discovery and OAuth 2.1 flow; like claude.ai, it connects from the vendor's servers with an https callback, which we have not yet exercised end to end. If you try it, tell us at support@voixcall.com.
Any other MCP client that supports Streamable HTTP and OAuth works the same way. Details and the tool list are on the MCP page.
C. Call the REST API with curl
The API accepts OAuth 2.1 access tokens (and, once the Settings screen for them ships, personal API keys). Getting a token by hand is the standard authorization-code flow with PKCE; the steps below do it with curl and a browser and take about two minutes. Nothing here needs a client secret: every client is a public client.
1. Read the server metadata
All endpoints, supported scopes and PKCE methods come from the authorization-server metadata document (RFC 8414). Nothing in this guide is hard-coded that the document does not also say.
curl -s https://api.voixcall.com/.well-known/oauth-authorization-server 2. Register a client
If you host a public client metadata document (an https URL that returns your client's metadata, with
client_id equal to that URL), skip this step and use the URL as your client_id. Otherwise register once
with Dynamic Client Registration. Loopback redirect URIs (http://127.0.0.1, http://localhost,
http://[::1], any port) are allowed for native and script clients.
curl -s https://api.voixcall.com/oauth/register \
-H "Content-Type: application/json" \
-d '{
"client_name": "My VoixCall script",
"redirect_uris": ["http://127.0.0.1:8765/callback"],
"application_type": "native",
"token_endpoint_auth_method": "none",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"]
}' {
"client_id": "dcr_โฆ",
"client_id_issued_at": 1790612000,
"redirect_uris": ["http://127.0.0.1:8765/callback"],
"token_endpoint_auth_method": "none",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"client_name": "My VoixCall script",
"application_type": "native"
} Keep the client_id. Registrations that are never used are purged after 30 days.
3. Send the user to the authorization page
Build a PKCE verifier and challenge, then open the authorization URL. Ask only for the scopes you need: the example
requests rates:read alone, which is enough for /v1/me. Assistants are asked to start with the
read-only set rates:read credits:read calls:read contacts:read and request more later. resource is required and must be
exactly https://api.voixcall.com/v1 (or โฆ/mcp for an MCP client); the token is bound to it.
CLIENT_ID="dcr_โฆ" # from the registration response
CODE_VERIFIER=$(openssl rand -base64 48 | tr -d '=+/')
CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')
STATE=$(openssl rand -hex 16)
open "https://api.voixcall.com/oauth/authorize?response_type=code\
&client_id=$CLIENT_ID\
&redirect_uri=http%3A%2F%2F127.0.0.1%3A8765%2Fcallback\
&scope=rates%3Aread\
&state=$STATE\
&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256\
&resource=https%3A%2F%2Fapi.voixcall.com%2Fv1"
Sign in and choose Allow. The browser is redirected to http://127.0.0.1:8765/callback?code=โฆ&state=โฆ&iss=https://api.voixcall.com.
Nothing is listening there, so the page shows a connection error: that is expected. Copy the code value from the
address bar. It is single-use and expires 60 seconds after it was issued, so do step 4 right away.
4. Exchange the code for tokens
CODE="โฆ" # the code= value from the redirect URL
curl -s https://api.voixcall.com/oauth/token \
-d grant_type=authorization_code \
-d code="$CODE" \
-d code_verifier="$CODE_VERIFIER" \
-d client_id="$CLIENT_ID" \
-d redirect_uri=http://127.0.0.1:8765/callback \
-d resource=https://api.voixcall.com/v1 {
"access_token": "vcat_โฆ",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "vcrt_โฆ",
"scope": "rates:read"
} Access tokens (vcat_) last one hour. Refresh tokens (vcrt_) last 30 days, or 14 days without use, and are rotated on every refresh: the old one stops working and presenting it again revokes the whole grant.
5. Call /v1/me
TOKEN="vcat_โฆ"
curl -s https://api.voixcall.com/v1/me \
-H "Authorization: Bearer $TOKEN" {
"object": "user",
"id": "usr_0f8c2b6a4d1e4c7b9a3e5d2f1c0b9a87",
"email": "you@example.com",
"display_name": "Your Name",
"country_code": null,
"time_zone": null,
"livemode": true
} id is the account id with the usr_ prefix. country_code and time_zone are always
null today (not stored yet). livemode is true for OAuth tokens and live API keys.
/v1/me works with any scope. This is the only /v1 operation live today; the
reference grows as endpoints ship.
6. Refresh
client_id is required on refresh (public client). A scope parameter may narrow the token's scopes, never widen them.
curl -s https://api.voixcall.com/oauth/token \
-d grant_type=refresh_token \
-d refresh_token="vcrt_โฆ" \
-d client_id="$CLIENT_ID" API keys
Personal API keys are sent the same way, as a bearer token: Authorization: Bearer vc_live_โฆ. Keys work on
/v1 only and are rejected at /mcp; test keys (vc_test_) additionally see only test-mode
data. Keys carry a name, a subset of scopes, an expiry (90 days by default, one year at most) and an optional IP allowlist.
coming The management endpoints exist, but the screen to create a key in Settings has not shipped yet, so there is no self-serve way to obtain one today. Use OAuth until it does; the changelog will say when.
curl -s https://api.voixcall.com/v1/me \
-H "Authorization: Bearer vc_live_โฆ" What a failure looks like
Every error is the same JSON envelope. Every response carries Request-Id; /v1 responses carry VoixCall-Version; rate-limited responses carry the RateLimit headers.
curl -s https://api.voixcall.com/v1/me
# HTTP 401, WWW-Authenticate: Bearer error="invalid_token", resource_metadata="https://api.voixcall.com/.well-known/oauth-protected-resource/v1"
{
"error": {
"type": "authentication_error",
"code": "invalid_token",
"message": "Provide a bearer access token.",
"param": null,
"doc_url": "https://voixcall.com/developers/errors#invalid_token",
"request_id": "req_Fa3rR6ZW8v3AM31xlv9CYQ"
}
}
The error catalogue lists every code with its status and what to do;
rate limits explains the headers. Quote the request_id when you write to
support@voixcall.com.