API reference · v1 ·
VICIfast API reference
The VICIfast API is a REST API for phone numbers: search the catalogue, buy and route numbers to your VICIdial servers, place back-orders, read calls with their recordings and transcripts, and follow your wallet, from your own code. JSON over HTTPS, one error shape, signed webhooks, and a sandbox that never charges.
Guides: Quickstart · Receiving webhooks · Testing in the sandbox · SDKs for TypeScript and Python
Authentication
Create a key in API → Keys in the dashboard and send it on every request. A key carries scopes — reading numbers, buying them, releasing them, transcribing calls, seeing billing and managing webhooks are separate grants — and can be limited to the IP addresses you use. The number, order, call and billing endpoints need an approved termination account.
curl https://vicifast.com/api/v1/numbers \
-H "Authorization: Bearer vf_live_…"Test mode
Keys that start vf_test_ work against a sandbox: a pretend $100.00 wallet, a made-up catalogue (555-0100 to 555-0199 in every area code; numbers ending 00 or 11 are premium at $25.00) and one server, srv_sandbox. Prices are your real ones. Nothing real is bought, charged or pointed, and every response carries X-VICIfast-Mode: test. A sandbox back-order delivers half of each line at once and leaves the rest open, so you can try tracking and cancelling. With the sandbox:simulate scope, a test key can also make things happen: an inbound call, a month passing, a back-order filling, a low wallet — each sending its webhook to your test endpoints (see Sandbox). Start over with POST /api/v1/sandbox/reset.
Errors
Every error has the same shape. Branch on code; the message is for people and may change. Every response carries an X-Request-Id to quote to support.
{ "error": { "code": "insufficient_funds",
"message": "That comes to $5.00 and the wallet holds $2.10. …",
"details": { "needed_cents": 500, "balance_cents": 210 } } }| Code | Status | Meaning |
|---|---|---|
invalid_request | 400 | Malformed input; `param` names the field. |
unauthorized | 401 | No API key, or not a valid one. |
insufficient_funds | 402 | The wallet cannot cover it. Nothing was charged; top up and retry with the same Idempotency-Key. |
forbidden | 403 | The key lacks the scope this endpoint needs. |
ip_not_allowed | 403 | The key is restricted to other addresses. |
account_not_approved | 403 | The account has no approved termination account yet. Test keys still work. |
purchasing_disabled | 403 | Number purchases are turned off for this account. Numbers it owns keep working; test keys still work. |
test_mode_unsupported | 403 | This endpoint has no sandbox. |
sandbox_only | 403 | A sandbox simulation: use a test key (vf_test_…). Nothing real can be simulated. |
not_found | 404 | No such thing on this account. |
conflict | 409 | A request with this Idempotency-Key is still running, or the thing changed state. |
numbers_unavailable | 409 | Those numbers are no longer for sale, or there are not enough. `details` says which. |
unprocessable | 422 | Understood but refused (an Idempotency-Key reused for a different request, a server that cannot take numbers). |
premium_price_not_accepted | 422 | A premium number named without accept_premium_price_cents equal to its price. |
limit_exceeded | 422 | It would charge more than your max_total_cents or max_charge_cents. Nothing happened. |
rate_limited | 429 | Over 600 requests a minute on this key. Wait Retry-After seconds. |
internal_error | 500 | Our fault. Quote X-Request-Id to support. |
service_unavailable | 503 | Something we depend on did not answer. Retry shortly. |
Retries and Idempotency-Key
Requests that spend or refund money — buying, releasing, placing and cancelling back-orders, transcribing a call — need an Idempotency-Key header: any unique string, sent again unchanged when you retry. A retry of a request that succeeded gets the original answer back (with Idempotent-Replayed: true) and nothing happens twice. A request refused with a 4xx changed nothing, so the same key may be retried once the cause is fixed. Keys are remembered for 24 hours.
Pagination
Lists answer { "data": […], "has_more": true, "next_cursor": "…" }. Pass cursor=next_cursor for the next page, and limit (up to 500) for its size.
Rate limits
600 requests a minute per key, on every endpoint. Over it, the answer is 429 with a Retry-After header.
Numbers
What you hold, the catalogue, buying and releasing.
/api/v1/numbersList your numbers
The numbers this account holds (or held), newest first.
Needs the numbers:read scope.
Parameters
status | "active" | "released" | "all" · query | Default active. |
type | "local" | "toll_free" · query | |
routed | "true" | "false" · query | Pointed somewhere, or not. |
area_code | string · query | Only numbers in this area code (or toll-free prefix). |
order_id | string · query | Only numbers from this order. |
limit | integer · query | Page size, up to 500. |
cursor | string · query | next_cursor from the previous page. |
200 · A page of numbers.
datarequired | PhoneNumber[] | |
has_morerequired | boolean | |
next_cursorrequired | string | null |
curl 'https://vicifast.com/api/v1/numbers?status=active&limit=50' \
-H "Authorization: Bearer $VICIFAST_KEY"/api/v1/numbersChange many numbers at once
One change — label, notes, auto_renew, routing (with CID groups) — to up to 500 of your numbers. Every one must be a number you hold now, or nothing changes and the answer lists the ones that are not.
Needs the numbers:write scope.
Request body
label | string | null | |
notes | string | null | |
auto_renew | boolean | |
routing | object | null | Where it rings: a server and in-group, or {"sip_endpoints": true}. null unpoints it; it stays yours and keeps billing. |
numbersrequired | string[] | Up to 500 of your numbers. Every one must be a number you hold now, or nothing changes. |
200 · Changed. warnings says what did not fully land (a carrier update, a CID group).
datarequired | BulkUpdateResult | |
warnings | string[] |
curl -X PATCH 'https://vicifast.com/api/v1/numbers' \
-H "Authorization: Bearer $VICIFAST_KEY" \
-H "Content-Type: application/json" \
-d '{"numbers":["+13055550101","+13055550102"],"routing":{"server_id":"srv_…","in_group":"SALES","cid_groups":["FL_LOCAL"]},"auto_renew":true}'/api/v1/numbers/availableSearch numbers for sale
Numbers in stock, at your own price, in the order a quantity purchase takes them: never-owned first. Premium numbers only with include_premium=true.
Needs the numbers:read scope.
Parameters
type | "local" | "toll_free" · query | |
area_code | string · query | For toll-free, the prefix: 800, 833, 844… |
state | string · query | Two-letter state. |
contains | string · query | Digits, or letters read off a keypad, anywhere in the number: 7777, or FLOWERS. |
ends_with | string · query | Digits or keypad letters the number must END with: 0000, or SALES. |
npa_nxx | string · query | Area code and exchange together: 305555. |
rate_center | string · query | The rate center (in practice, the town) the number belongs to, as the numbering plan names it: MIAMI, NEW YORK CITY. Case does not matter. |
include_premium | "true" | "false" · query | Default false. |
sort | "recommended" | "number" | "number_desc" · query | recommended (the default): never-owned numbers first, as a quantity purchase takes them. number / number_desc: by the digits. |
limit | integer · query | Page size, up to 500. |
cursor | string · query | next_cursor from the previous page. |
200 · A page of numbers for sale.
datarequired | AvailableNumber[] | |
has_morerequired | boolean | |
next_cursorrequired | string | null |
curl 'https://vicifast.com/api/v1/numbers/available?area_code=305&ends_with=SALES&sort=number' \
-H "Authorization: Bearer $VICIFAST_KEY"/api/v1/numbers/purchaseBuy numbers
Pays from the wallet: the first month of each number (plus any setup fee) now, then monthly. All of them or none. Name the numbers, or ask for a quantity matching criteria. A premium number is bought only when named with accept_premium_price_cents equal to its monthly price. Each number has a three-month minimum term. dry_run returns the exact charge and buys nothing; without it an Idempotency-Key is required.
Needs the numbers:buy scope.
Parameters
Idempotency-Key | string · header |
Request body
numbers | (string | object)[] | Buy these exact numbers. All of them or none. Premium numbers need accept_premium_price_cents. |
numberrequired | string | Any common North American form. |
accept_premium_price_cents | integer | Required for a premium number: its monthly price, as you were shown it. |
quantity | integer | Or buy this many matching the criteria, chosen for you (never premium). All of them or none. |
type | "local" | "toll_free" | With quantity: local or toll-free. |
area_code | string | With quantity: the area code (toll-free: the prefix). |
state | string | With quantity: two-letter state. |
contains | string | With quantity: digits or keypad letters the numbers must contain. |
ends_with | string | With quantity: digits or keypad letters the numbers must end with. |
npa_nxx | string | With quantity: area code and exchange. |
rate_center | string | With quantity: the rate center. |
max_total_cents | integer | Refuse the purchase if it would charge more than this now. |
ring_on | object | Point the new numbers here straight away. |
server_idrequired | string | |
in_grouprequired | string | |
dry_run | boolean | Return exactly what the purchase would charge and buy nothing. Needs no Idempotency-Key. Not a reservation. |
200 · With dry_run: what it would charge. Nothing bought.
datarequired | PurchaseQuote |
201 · Bought. The numbers are yours.
datarequired | PurchaseResult |
curl -X POST 'https://vicifast.com/api/v1/numbers/purchase' \
-H "Authorization: Bearer $VICIFAST_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"quantity":5,"area_code":"305","max_total_cents":1000,"ring_on":{"server_id":"srv_…","in_group":"SALES"}}'/api/v1/numbers/releaseGive numbers back
Inside the three-month minimum term, the rest of the term is charged per number. dry_run returns that charge and changes nothing; the same call without it releases. All of them or none. Needs an Idempotency-Key unless dry_run.
Needs the numbers:release scope.
Parameters
Idempotency-Key | string · header |
Request body
numbersrequired | string[] | |
dry_run | boolean | Return exactly what releasing would charge, and release nothing. |
max_charge_cents | integer | Refuse if the minimum-term charge would be more than this. |
200 · Released (or, with dry_run, what releasing would cost).
datarequired | ReleaseResult |
curl -X POST 'https://vicifast.com/api/v1/numbers/release' \
-H "Authorization: Bearer $VICIFAST_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"numbers":["+13055550101"],"dry_run":true}'/api/v1/numbers/{number}Get a number
Needs the numbers:read scope.
Parameters
numberrequired | string · path | The number, in any common form (+13055551234 URL-encoded as %2B13055551234). |
200 · The number.
datarequired | PhoneNumber |
curl 'https://vicifast.com/api/v1/numbers/%2B13055550101' \
-H "Authorization: Bearer $VICIFAST_KEY"/api/v1/numbers/{number}Update a number
Label it, write notes, turn auto-renew on or off, or change where it rings: routing points it at one of your servers and an in-group, and null unpoints it (it stays yours and keeps billing). With auto-renew off a number is released at its first renewal after the minimum term.
Needs the numbers:write scope.
Parameters
numberrequired | string · path | The number, in any common form (+13055551234 URL-encoded as %2B13055551234). |
Request body
label | string | null | |
notes | string | null | |
auto_renew | boolean | |
routing | object | null | Where it rings: a server and in-group, or {"sip_endpoints": true}. null unpoints it; it stays yours and keeps billing. |
200 · The number, as it is now.
datarequired | PhoneNumber |
curl -X PATCH 'https://vicifast.com/api/v1/numbers/%2B13055550101' \
-H "Authorization: Bearer $VICIFAST_KEY" \
-H "Content-Type: application/json" \
-d '{"label":"Miami sales","auto_renew":false,"routing":{"server_id":"srv_…","in_group":"SALES"}}'/api/v1/serversList your servers
Where numbers can be pointed: use a server id in routing or ring_on.
Needs the numbers:read scope.
200 · Your servers.
datarequired | Server[] | |
has_morerequired | boolean | |
next_cursorrequired | string | null |
curl 'https://vicifast.com/api/v1/servers' \
-H "Authorization: Bearer $VICIFAST_KEY"/api/v1/servers/{id}/cid-groupsList a server's CID groups
The caller-ID pools on one of your servers that its campaigns dial out from, read from a snapshot of the server taken every minute.
Needs the numbers:read scope.
Parameters
idrequired | string · path |
200 · Every CID group on the server.
datarequired | CidGroup[] | |
has_morerequired | boolean | |
next_cursorrequired | string | null |
curl 'https://vicifast.com/api/v1/servers/:id/cid-groups' \
-H "Authorization: Bearer $VICIFAST_KEY"/api/v1/servers/{id}/cid-groups/{group}Get a CID group, with its numbers
Needs the numbers:read scope.
Parameters
idrequired | string · path | |
grouprequired | string · path |
200 · The group.
datarequired | CidGroupWithNumbers |
curl 'https://vicifast.com/api/v1/servers/:id/cid-groups/:group' \
-H "Authorization: Bearer $VICIFAST_KEY"/api/v1/servers/{id}/cid-groups/{group}/numbersPut numbers into a CID group
Written to the server straight away. The area code column is filled the way the group's type needs: the number's area code, its state, or NONE in a rotation. Numbers the server will not take come back in skipped.
Needs the numbers:write scope.
Parameters
idrequired | string · path | |
grouprequired | string · path |
Request body
numbersrequired | string[] | |
description | string | Default "VICIfast DID". |
200 · Added.
datarequired | CidGroupAdded |
curl -X POST 'https://vicifast.com/api/v1/servers/:id/cid-groups/:group/numbers' \
-H "Authorization: Bearer $VICIFAST_KEY" \
-H "Content-Type: application/json" \
-d '{"numbers":["+13055550101","+13055550102"]}'/api/v1/servers/{id}/cid-groups/{group}/numbers/removeTake numbers out of a CID group
The numbers stay yours and keep ringing; campaigns using the group stop calling out from them.
Needs the numbers:write scope.
Parameters
idrequired | string · path | |
grouprequired | string · path |
Request body
numbersrequired | string[] |
200 · Removed; not_in_group lists any that were not in it.
datarequired | CidGroupRemoved |
curl -X POST 'https://vicifast.com/api/v1/servers/:id/cid-groups/:group/numbers/remove' \
-H "Authorization: Bearer $VICIFAST_KEY" \
-H "Content-Type: application/json" \
-d '{"numbers":["+13055550102"]}'Orders
Purchases and back-orders.
/api/v1/ordersList orders
Batches of numbers you received: a purchase, or a back-order as it fills.
Needs the orders:read scope.
Parameters
status | "open" | "filled" | "routed" | "closed" · query | |
limit | integer · query | Page size, up to 500. |
cursor | string · query | next_cursor from the previous page. |
200 · A page of orders.
datarequired | Order[] | |
has_morerequired | boolean | |
next_cursorrequired | string | null |
curl 'https://vicifast.com/api/v1/orders' \
-H "Authorization: Bearer $VICIFAST_KEY"/api/v1/orders/{id}Get an order, with its numbers
Needs the orders:read scope.
Parameters
idrequired | string · path |
200 · The order.
datarequired | Order |
curl 'https://vicifast.com/api/v1/orders/:id' \
-H "Authorization: Bearer $VICIFAST_KEY"/api/v1/backordersList back-orders
Needs the orders:read scope.
Parameters
status | "open" | "filled" | "cancelled" | "expired" · query | |
limit | integer · query | Page size, up to 500. |
cursor | string · query | next_cursor from the previous page. |
200 · A page of back-orders.
datarequired | Backorder[] | |
has_morerequired | boolean | |
next_cursorrequired | string | null |
curl 'https://vicifast.com/api/v1/backorders' \
-H "Authorization: Bearer $VICIFAST_KEY"/api/v1/backordersPlace a back-order
Ask for numbers by area code (or toll-free prefix). Paid up front at your price; numbers are delivered as stock arrives, each line counted separately. Cancel any time to get back what has not arrived.
Needs the orders:write scope.
Parameters
Idempotency-Key | string · header |
Request body
type | "local" | "toll_free" | Default local. |
lines | object[] | Required for local. A toll-free order may give quantity instead. |
area_coderequired | string | Area code; for toll-free, the prefix (800, 833…). |
quantityrequired | integer | |
quantity | integer | Toll-free only, in place of lines: this many numbers from whichever toll-free prefix is in stock. |
notes | string | Free text sent with the order, for the people filling it. |
allow_nearby | boolean | When an area code runs short, a nearby one in the same state will do (any toll-free prefix, on a toll-free order). |
ring_on | object | Point the numbers here as they arrive. |
server_idrequired | string | |
in_grouprequired | string | |
max_total_cents | integer | Refuse the order if it would charge more than this now. |
dry_run | boolean | Return exactly what the order would charge and place nothing. Needs no Idempotency-Key. |
200 · With dry_run: what it would charge. Nothing placed.
datarequired | BackorderQuote |
201 · Placed and paid.
datarequired | Backorder |
curl -X POST 'https://vicifast.com/api/v1/backorders' \
-H "Authorization: Bearer $VICIFAST_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"lines":[{"area_code":"305","quantity":20},{"area_code":"786","quantity":10}],"allow_nearby":true}'/api/v1/backorders/{id}Get a back-order
Needs the orders:read scope.
Parameters
idrequired | string · path |
200 · The back-order.
datarequired | Backorder |
curl 'https://vicifast.com/api/v1/backorders/:id' \
-H "Authorization: Bearer $VICIFAST_KEY"/api/v1/backorders/{id}/cancelCancel a back-order
Stops waiting and refunds the numbers that have not arrived. Delivered numbers stay yours.
Needs the orders:write scope.
Parameters
idrequired | string · path | |
Idempotency-Keyrequired | string · header | Required. Any unique string (a UUID is ideal). Retry with the same key and the original result is returned instead of the work being done twice. |
200 · Cancelled and refunded.
datarequired | object | |
objectrequired | "backorder" | |
idrequired | string | |
statusrequired | "open" | "filled" | "cancelled" | "expired" | |
typerequired | "local" | "toll_free" | |
quantityrequired | integer | |
filledrequired | integer | |
allow_nearbyrequired | boolean | |
price_each_centsrequired | integer | Prepaid per number; the most any will cost. |
paid_centsrequired | integer | US cents. |
refunded_centsrequired | integer | US cents. |
linesrequired | object[] | |
area_coderequired | string | Area code, or toll-free prefix. |
staterequired | string | null | |
quantityrequired | integer | |
filledrequired | integer | |
ring_onrequired | object | null | |
server_idrequired | string | |
in_grouprequired | string | |
order_idrequired | string | null | The order the delivered numbers are in, once the first arrives. |
notesrequired | string | null | What you sent with the order. |
created_atrequired | string | |
cancelled_atrequired | string | null | |
refunded_now_centsrequired | integer |
curl -X POST 'https://vicifast.com/api/v1/backorders/:id/cancel' \
-H "Authorization: Bearer $VICIFAST_KEY" \
-H "Idempotency-Key: $(uuidgen)"Calls
Call records and recordings.
/api/v1/callsList calls
Outbound calls you made and inbound calls to your numbers, newest first, from the last 90 days at most.
Needs the calls:read scope.
Parameters
direction | "inbound" | "outbound" · query | |
number | string · query | Inbound calls to this one of your numbers. |
answered | "true" | "false" · query | |
from | string · query | Only calls from this number. |
to | string · query | Only calls to this number. |
window | "1h" | "24h" | "7d" | "30d" | "90d" · query | Default 7d. Calls are kept for at least 90 days. |
limit | integer · query | Page size, up to 500. |
cursor | string · query | next_cursor from the previous page. |
200 · A page of calls.
datarequired | Call[] | |
has_morerequired | boolean | |
next_cursorrequired | string | null |
curl 'https://vicifast.com/api/v1/calls?direction=inbound&window=7d' \
-H "Authorization: Bearer $VICIFAST_KEY"/api/v1/calls/{id}Get a call
Needs the calls:read scope.
Parameters
idrequired | string · path |
200 · The call.
datarequired | Call |
curl 'https://vicifast.com/api/v1/calls/:id' \
-H "Authorization: Bearer $VICIFAST_KEY"/api/v1/calls/exportExport calls as CSV
The same calls and the same fields as List calls, as a CSV file of up to 50,000 rows. When there are more, X-Next-Cursor names where the next file starts (pass it as cursor).
Needs the calls:read scope.
Parameters
direction | "inbound" | "outbound" · query | |
number | string · query | Inbound calls to this one of your numbers. |
answered | "true" | "false" · query | |
from | string · query | Only calls from this number. |
to | string · query | Only calls to this number. |
window | "1h" | "24h" | "7d" | "30d" | "90d" · query | Default 7d. Calls are kept for at least 90 days. |
limit | integer · query | Rows per file, up to 50,000 (default 10,000). |
cursor | string · query | next_cursor from the previous page. |
curl 'https://vicifast.com/api/v1/calls/export?window=90d&direction=outbound&from=%2B13055550101' \
-H "Authorization: Bearer $VICIFAST_KEY"/api/v1/calls/{id}/transcriptGet a call's transcript
A transcript made before, free to read as often as you like.
Needs the calls:read scope.
Parameters
idrequired | string · path |
200 · The transcript.
datarequired | Transcript |
curl 'https://vicifast.com/api/v1/calls/:id/transcript' \
-H "Authorization: Bearer $VICIFAST_KEY"/api/v1/calls/{id}/transcriptTranscribe a call
Transcribes the call's recording, with the speakers told apart. 5¢ a minute of call (rounded up), charged to the wallet once: a call already transcribed returns its transcript free. dry_run says what it would cost. If the transcription fails, nothing is charged.
Needs the calls:transcribe scope.
Parameters
idrequired | string · path | |
Idempotency-Key | string · header |
Request body
dry_run | boolean | Say what it would cost, and change nothing. |
max_charge_cents | integer | Refuse if it would cost more than this. |
200 · With dry_run: what it would cost. Or the transcript made before, free.
datarequired | TranscriptQuote | Transcript |
201 · Made and charged.
datarequired | Transcript |
curl -X POST 'https://vicifast.com/api/v1/calls/:id/transcript' \
-H "Authorization: Bearer $VICIFAST_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"max_charge_cents":50}'/api/v1/calls/{id}/recordingGet a recording link
A link to the call's recording, good for fifteen minutes and usable without an API key (hand it to a browser or a player). Only for calls that have one.
Needs the calls:read scope.
Parameters
idrequired | string · path |
200 · The link.
datarequired | RecordingLink |
curl 'https://vicifast.com/api/v1/calls/:id/recording' \
-H "Authorization: Bearer $VICIFAST_KEY"Billing
The wallet.
/api/v1/billing/balanceGet the wallet balance
Needs the billing:read scope.
200 · The wallet.
datarequired | Balance |
curl 'https://vicifast.com/api/v1/billing/balance' \
-H "Authorization: Bearer $VICIFAST_KEY"/api/v1/billing/transactionsList wallet transactions
Needs the billing:read scope.
Parameters
type | string · query | One type or several, comma-separated: charge_did_purchase,topup_stripe |
direction | "credit" | "debit" · query | |
from | string · query | |
to | string · query | |
limit | integer · query | Page size, up to 500. |
cursor | string · query | next_cursor from the previous page. |
200 · A page of transactions, newest first.
datarequired | Transaction[] | |
has_morerequired | boolean | |
next_cursorrequired | string | null |
curl 'https://vicifast.com/api/v1/billing/transactions' \
-H "Authorization: Bearer $VICIFAST_KEY"/api/v1/billing/renewalsList upcoming renewals
What the wallet will be charged, and when: your numbers and wallet-billed servers.
Needs the billing:read scope.
Parameters
days | integer · query | Default 30. |
200 · Upcoming renewals, soonest first.
datarequired | Renewal[] | |
has_morerequired | boolean | |
next_cursorrequired | string | null |
curl 'https://vicifast.com/api/v1/billing/renewals' \
-H "Authorization: Bearer $VICIFAST_KEY"Messaging
SMS and MMS from your numbers.
/api/v1/messagesSend a message
SMS (text) or MMS (media_urls) from one of your numbers. Accepted with 202: queued, not yet at the carrier; watch status for sent and delivered. An Idempotency-Key makes a retry return the same message (200) instead of sending twice. Not in the sandbox.
Needs the messages:send scope.
Parameters
Idempotency-Key | string · header |
Request body
fromrequired | string | One of your numbers that can send messages. |
torequired | string | |
text | string | |
media_urls | string[] | For MMS. |
send_at | string | Schedule it instead of sending now. |
200 · The message an Idempotency-Key already sent.
datarequired | Message |
202 · Accepted and queued.
datarequired | Message |
curl -X POST 'https://vicifast.com/api/v1/messages' \
-H "Authorization: Bearer $VICIFAST_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"from":"+13055550101","to":"+12125559999","text":"Your appointment is at 3pm."}'/api/v1/messagesList messages
Sent and received, newest first. Pages also carry X-Next-Cursor, as they always have.
Needs the messages:read scope.
Parameters
direction | "inbound" | "outbound" · query | |
status | "queued" | "scheduled" | "sending" | "sent" | "delivered" | "undelivered" | "failed" | "suppressed" | "received" · query | |
conversation_id | string · query | |
limit | integer · query | Page size, up to 100. Default 25. |
cursor | string · query | next_cursor from the previous page. |
200 · A page of messages.
datarequired | Message[] | |
has_morerequired | boolean | |
next_cursorrequired | string | null |
curl 'https://vicifast.com/api/v1/messages' \
-H "Authorization: Bearer $VICIFAST_KEY"Journeys
Starting a journey for a lead from outside the dialer.
/api/v1/journeys/{id}/enrollEnrol a lead in a journey
Starts a journey's calls and texts for a phone number, from a web form or a CRM. A number the dialer does not know yet becomes a lead first, in the journey's list (or list_id), after the dialer's own DNC checks. The journey must allow API enrolment in its settings. Already enrolled is not an error: enrolled comes back false with the reason. Not in the sandbox.
Needs the journeys:enroll scope.
Parameters
idrequired | string · path | |
Idempotency-Key | string · header |
Request body
phone_numberrequired | string | A North American number: the dialer stores ten digits. |
list_id | integer | The dialer list a new lead is added to. Default: the journey's own list. Not used when the number is already a lead. |
first_name | string | |
last_name | string | |
address1 | string | |
address2 | string | |
city | string | |
state | string | |
postal_code | string | |
country_code | string | |
email | string | |
comments | string | |
vendor_lead_code | string | |
source_id | string | |
title | string | |
fields | object | Any other lead fields, by their VICIdial names. |
idempotency_key | string | Deprecated: send the Idempotency-Key header instead. Still honoured. |
200 · Enrolled, or why not.
datarequired | JourneyEnrollment |
curl -X POST 'https://vicifast.com/api/v1/journeys/:id/enroll' \
-H "Authorization: Bearer $VICIFAST_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"phone_number":"+13055550123","first_name":"Ana","email":"ana@example.com","fields":{"lead_source":"web form"}}'SIP endpoints
Your own PBX or SBC: the addresses your numbers ring when they are not on a server we host.
/api/v1/sip-endpointsList your SIP endpoints
Every endpoint with the addresses it resolves to and their health, how calls are shared, and whether new numbers ring them.
Needs the numbers:read scope.
200 · Your endpoints and settings.
datarequired | SipEndpointList |
curl 'https://vicifast.com/api/v1/sip-endpoints' \
-H "Authorization: Bearer $VICIFAST_KEY"/api/v1/sip-endpointsAdd a SIP endpoint
A public IP address or a DNS name. Refused: private and reserved addresses, ours and our carriers’, and an address another account uses.
Needs the numbers:write scope.
Request body
hostrequired | string | |
port | integer | Default 5060, or 5061 for tls. |
transport | "udp" | "tcp" | "tls" | Default udp. |
weight | integer | Default 1. |
enabled | boolean | Default true. |
note | string |
201 · Added.
datarequired | SipEndpoint |
curl -X POST 'https://vicifast.com/api/v1/sip-endpoints' \
-H "Authorization: Bearer $VICIFAST_KEY" \
-H "Content-Type: application/json" \
-d '{"host":"pbx.example.com","port":5060,"transport":"udp","weight":70}'/api/v1/sip-endpoints/{id}Change a SIP endpoint
Its host, port, transport, weight or note, or switch it off and on.
Needs the numbers:write scope.
Parameters
idrequired | string · path |
Request body
host | string | |
port | integer | Default 5060, or 5061 for tls. |
transport | "udp" | "tcp" | "tls" | Default udp. |
weight | integer | Default 1. |
enabled | boolean | Default true. |
note | string |
200 · Changed.
datarequired | SipEndpoint |
curl -X PATCH 'https://vicifast.com/api/v1/sip-endpoints/:id' \
-H "Authorization: Bearer $VICIFAST_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled":false}'/api/v1/sip-endpoints/{id}Remove a SIP endpoint
Numbers keep ringing the endpoints that are left.
Needs the numbers:write scope.
Parameters
idrequired | string · path |
200 · Removed.
datarequired | DeletedSipEndpoint |
curl -X DELETE 'https://vicifast.com/api/v1/sip-endpoints/:id' \
-H "Authorization: Bearer $VICIFAST_KEY"/api/v1/sip-endpoint-settingsChange how SIP endpoints are used
How calls are shared, whether new numbers ring the endpoints, and whether existing numbers move there now.
Needs the numbers:write scope.
Request body
share | "roundrobin" | "weighted" | roundrobin: every address takes calls equally. weighted: in proportion to weight. |
new_numbers_ring_endpoints | boolean | |
move_unpointed | boolean | With new_numbers_ring_endpoints true: also point every number that rings nowhere at the endpoints. |
move_server_pointed | boolean | With new_numbers_ring_endpoints true: also move every number that rings a server to the endpoints. |
200 · The endpoints and settings, as they now are.
datarequired | SipEndpointList |
curl -X PUT 'https://vicifast.com/api/v1/sip-endpoint-settings' \
-H "Authorization: Bearer $VICIFAST_KEY" \
-H "Content-Type: application/json" \
-d '{"share":"weighted","new_numbers_ring_endpoints":true,"move_unpointed":true}'Webhook endpoints
Where events are sent, their signing secrets, and what was delivered.
/api/v1/webhook-endpointsList webhook endpoints
This key's mode only: a live key sees live endpoints, a test key test ones.
Needs the webhooks:read scope.
200 · Every endpoint, oldest first.
datarequired | WebhookEndpoint[] | |
has_morerequired | boolean | |
next_cursorrequired | string | null |
curl 'https://vicifast.com/api/v1/webhook-endpoints' \
-H "Authorization: Bearer $VICIFAST_KEY"/api/v1/webhook-endpointsAdd a webhook endpoint
Up to 10 per account. The endpoint gets this key's mode: one made with a test key receives the sandbox's events. The secret is in the answer; keep it.
Needs the webhooks:write scope.
Request body
urlrequired | string | https only, and on the public internet: private and loopback addresses are refused. |
description | string | |
eventsrequired | ("numbers.arrived" | "number.released" | "number.renewal_failed" | "call.inbound.completed" | "call.outbound.completed" | "wallet.low" | "backorder.filled" | "number.routing_failed" | "wallet.topped_up" | "ai_call.completed")[] | |
wallet_low_threshold_cents | integer | Required with wallet.low. |
201 · Added.
datarequired | WebhookEndpointWithSecret |
curl -X POST 'https://vicifast.com/api/v1/webhook-endpoints' \
-H "Authorization: Bearer $VICIFAST_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/hooks/vicifast","events":["numbers.arrived","number.routing_failed","wallet.low"],"wallet_low_threshold_cents":5000}'/api/v1/webhook-endpoints/{id}Get a webhook endpoint
Needs the webhooks:read scope.
Parameters
idrequired | string · path |
200 · The endpoint.
datarequired | WebhookEndpoint |
curl 'https://vicifast.com/api/v1/webhook-endpoints/:id' \
-H "Authorization: Bearer $VICIFAST_KEY"/api/v1/webhook-endpoints/{id}Change a webhook endpoint
Its address, events, threshold, description, or switch it off and on.
Needs the webhooks:write scope.
Parameters
idrequired | string · path |
Request body
url | string | https only, and on the public internet: private and loopback addresses are refused. |
description | string | null | |
events | ("numbers.arrived" | "number.released" | "number.renewal_failed" | "call.inbound.completed" | "call.outbound.completed" | "wallet.low" | "backorder.filled" | "number.routing_failed" | "wallet.topped_up" | "ai_call.completed")[] | Replaces the list. |
enabled | boolean | |
wallet_low_threshold_cents | integer | null |
200 · Changed.
datarequired | WebhookEndpoint |
curl -X PATCH 'https://vicifast.com/api/v1/webhook-endpoints/:id' \
-H "Authorization: Bearer $VICIFAST_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled":false}'/api/v1/webhook-endpoints/{id}Remove a webhook endpoint
Its undelivered events are dropped with it.
Needs the webhooks:write scope.
Parameters
idrequired | string · path |
200 · Removed.
datarequired | object | |
objectrequired | "webhook_endpoint" | |
idrequired | string | |
deletedrequired | true |
curl -X DELETE 'https://vicifast.com/api/v1/webhook-endpoints/:id' \
-H "Authorization: Bearer $VICIFAST_KEY"/api/v1/webhook-endpoints/{id}/rotate-secretRotate the signing secret
A new secret for this endpoint, used from the next delivery on. The old one stops working at once, so update your receiver straight after.
Needs the webhooks:write scope.
Parameters
idrequired | string · path |
200 · The new secret.
datarequired | WebhookSecret |
curl -X POST 'https://vicifast.com/api/v1/webhook-endpoints/:id/rotate-secret' \
-H "Authorization: Bearer $VICIFAST_KEY"/api/v1/webhook-endpoints/{id}/testSend a test event
Sends a sample of one event type to this endpoint now, signed like any other, and says what came back. The sample has test: true in its data and is tried once.
Needs the webhooks:write scope.
Parameters
idrequired | string · path |
Request body
typerequired | "numbers.arrived" | "number.released" | "number.renewal_failed" | "call.inbound.completed" | "call.outbound.completed" | "wallet.low" | "backorder.filled" | "number.routing_failed" | "wallet.topped_up" | "ai_call.completed" | Which event to send a sample of. |
200 · Sent; delivered says whether it arrived.
datarequired | WebhookTestResult |
curl -X POST 'https://vicifast.com/api/v1/webhook-endpoints/:id/test' \
-H "Authorization: Bearer $VICIFAST_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"numbers.arrived"}'/api/v1/webhook-endpoints/{id}/deliveriesList deliveries to an endpoint
Newest first: what was sent, what came back, and what is still being retried.
Needs the webhooks:read scope.
Parameters
idrequired | string · path | |
state | "pending" | "retrying" | "delivered" | "gave_up" · query | |
limit | integer · query | Page size, up to 500. |
cursor | string · query | next_cursor from the previous page. |
200 · A page of deliveries.
datarequired | WebhookDelivery[] | |
has_morerequired | boolean | |
next_cursorrequired | string | null |
curl 'https://vicifast.com/api/v1/webhook-endpoints/:id/deliveries' \
-H "Authorization: Bearer $VICIFAST_KEY"Events
The events of the last 30 days, and sending one again.
/api/v1/eventsList events
The events of the last 30 days, newest first, in this key's mode — each one exactly as its webhook body. An event is recorded when at least one endpoint was subscribed to it at the time; catch up on what your receiver missed from here.
Needs the webhooks:read scope.
Parameters
type | string · query | One type or several, comma-separated: numbers.arrived,number.released |
from | string · query | |
to | string · query | |
limit | integer · query | Page size, up to 500. |
cursor | string · query | next_cursor from the previous page. |
200 · A page of events.
datarequired | Event[] | |
has_morerequired | boolean | |
next_cursorrequired | string | null |
curl 'https://vicifast.com/api/v1/events?type=numbers.arrived,number.released&limit=50' \
-H "Authorization: Bearer $VICIFAST_KEY"/api/v1/events/{id}Get an event, with its deliveries
Needs the webhooks:read scope.
Parameters
idrequired | string · path |
200 · The event.
datarequired | EventWithDeliveries |
curl 'https://vicifast.com/api/v1/events/:id' \
-H "Authorization: Bearer $VICIFAST_KEY"/api/v1/events/{id}/resendSend an event again
Queues the event again, to one endpoint or to every endpoint subscribed to it now, and starts sending at once. Each is a new delivery with a new X-VICIfast-Delivery id; the event id is the same, so a receiver that keeps the event ids it has handled can tell it is a repeat.
Needs the webhooks:write scope.
Parameters
idrequired | string · path |
Request body
endpoint_id | string | Only to this endpoint. Left out: to every endpoint that is on and subscribed to the type now. |
202 · Queued; the deliveries are on their way.
datarequired | ResendResult |
curl -X POST 'https://vicifast.com/api/v1/events/:id/resend' \
-H "Authorization: Bearer $VICIFAST_KEY" \
-H "Content-Type: application/json" \
-d '{"endpoint_id":"we_…"}'Sandbox
Test keys only: make a call ring, time pass, a back-order fill or the wallet run low, and see what your code does.
/api/v1/sandbox/inbound-callsSimulate an inbound call
A caller rings one of your sandbox numbers. The call appears in List calls (with a recording you can transcribe, when answered) and call.inbound.completed goes to your test endpoints.
Needs the sandbox:simulate scope.
Request body
numberrequired | string | One of the sandbox numbers you hold. |
from | string | The caller. Default +12025550123. |
answered | boolean | Default true. |
duration_s | integer | How long it lasted, if answered. Default 60. |
201 · The call.
datarequired | Call |
curl -X POST 'https://vicifast.com/api/v1/sandbox/inbound-calls' \
-H "Authorization: Bearer $VICIFAST_KEY" \
-H "Content-Type: application/json" \
-d '{"number":"+13055550100","from":"+12125559999","duration_s":95}'/api/v1/sandbox/time-travelMove the sandbox on in time
Up to 400 days. Renewals that fall due are charged to the pretend wallet, oldest first; a number with auto-renew off past its minimum term is released; a renewal the wallet cannot pay fails. Each sends its webhook to your test endpoints.
Needs the sandbox:simulate scope.
Request body
daysrequired | integer | How far to move the sandbox on. Renewals that fall due are charged to the pretend wallet, numbers with auto-renew off past their minimum term are released, and a renewal the wallet cannot pay fails — each with its webhook. |
200 · What happened.
datarequired | TimeTravelResult |
curl -X POST 'https://vicifast.com/api/v1/sandbox/time-travel' \
-H "Authorization: Bearer $VICIFAST_KEY" \
-H "Content-Type: application/json" \
-d '{"days":31}'/api/v1/sandbox/backorders/{id}/deliverSimulate a back-order delivery
Numbers arrive against an open sandbox back-order: numbers.arrived, and backorder.filled once everything asked for is in.
Needs the sandbox:simulate scope.
Parameters
idrequired | string · path |
Request body
quantity | integer | How many to deliver now. Default: everything still to come. |
200 · The back-order, after the delivery.
datarequired | Backorder |
curl -X POST 'https://vicifast.com/api/v1/sandbox/backorders/:id/deliver' \
-H "Authorization: Bearer $VICIFAST_KEY" \
-H "Content-Type: application/json" \
-d '{"quantity":5}'/api/v1/sandbox/walletSet the sandbox wallet
Sets the pretend balance. Dropping below a test endpoint’s wallet.low threshold sends wallet.low; raising it sends wallet.topped_up. Set it low, then time-travel, to see a renewal fail.
Needs the sandbox:simulate scope.
Request body
balance_centsrequired | integer | The pretend balance to set. Below a test endpoint’s wallet.low threshold sends wallet.low; raising it sends wallet.topped_up. |
200 · The pretend wallet.
datarequired | Balance |
curl -X POST 'https://vicifast.com/api/v1/sandbox/wallet' \
-H "Authorization: Bearer $VICIFAST_KEY" \
-H "Content-Type: application/json" \
-d '{"balance_cents":150}'/api/v1/sandbox/resetStart the sandbox over
No numbers, no orders, and a fresh $100 pretend wallet.
Needs the sandbox:simulate scope.
200 · Reset.
datarequired | SandboxReset |
curl -X POST 'https://vicifast.com/api/v1/sandbox/reset' \
-H "Authorization: Bearer $VICIFAST_KEY"Webhooks
Add endpoints in API → Webhooks in the dashboard or with the endpoints API — several if you like, each with its own events and its own signing secret. We POST each event as JSON and retry anything but a 2xx after 1, 5, 15, 60 and 240 minutes. Skip an event you have already handled by its id, which is the same on every retry and on a resend; X-VICIfast-Delivery names one delivery, and a resend is a new one. Missed some? The last 30 days are in List events. No receiver yet? Point an endpoint at the hosted test inbox in API → Webhooks and see each request, its headers and whether its signature checks out.
{
"id": "cmg…",
"object": "event",
"type": "numbers.arrived",
"livemode": true,
"created_at": "2026-10-03T20:00:00.000Z",
"data": { … }
}Checking a request is from us
X-VICIfast-Signature: t=…,v1=… carries an HMAC-SHA256 of `${t}.${body}` with the endpoint’s secret. Compare it in constant time and refuse a t more than five minutes old.
import crypto from 'node:crypto';
function fromVicifast(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const want = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return v1?.length === want.length &&
crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(want));
}numbers.arrivedNumbers are yours: bought, delivered against a back-order, or assigned.
data
sourcerequired | "purchase" | "backorder" | "assigned" | |
order_idrequired | string | null | |
backorder_idrequired | string | null | |
numbersrequired | string[] | |
test | true | Only on a sample sent with "send test": the values are made up. Real events never carry it. |
number.releasedA number left your account.
data
numberrequired | string | E.164, with the plus. |
reasonrequired | "customer" | "auto_renew_off" | "account" | |
charge_centsrequired | integer | |
released_atrequired | string | |
test | true | Only on a sample sent with "send test": the values are made up. Real events never carry it. |
number.renewal_failedA renewal could not be paid (sent once per number per billing month; the charge is retried every hour).
data
numberrequired | string | E.164, with the plus. |
amount_centsrequired | integer | |
due_atrequired | string | |
reasonrequired | "insufficient_funds" | "error" | |
test | true | Only on a sample sent with "send test": the values are made up. Real events never carry it. |
call.inbound.completedAn inbound call to one of your numbers finished.
data
objectrequired | "call" | |
idrequired | string | The SIP call id. |
directionrequired | "inbound" | "outbound" | |
fromrequired | string | The caller, E.164 when it is a North American number. |
torequired | string | |
numberrequired | string | null | Inbound: which of your numbers it arrived on. |
started_atrequired | string | |
answered_atrequired | string | null | |
ended_atrequired | string | null | |
answeredrequired | boolean | |
duration_srequired | integer | |
billable_srequired | integer | |
sip_coderequired | integer | null | |
hangup_causerequired | string | |
charge_nanosrequired | integer | What the call cost, in billionths of a dollar (2000000 = $0.002). |
recordingrequired | boolean | A recording can be fetched with GET /v1/calls/{id}/recording. |
test | true | Only on a sample sent with "send test": the values are made up. Real events never carry it. |
call.outbound.completedAn outbound call you made finished, answered or not. A dialer makes many; each is one delivery.
data
objectrequired | "call" | |
idrequired | string | The SIP call id. |
directionrequired | "inbound" | "outbound" | |
fromrequired | string | The caller, E.164 when it is a North American number. |
torequired | string | |
numberrequired | string | null | Inbound: which of your numbers it arrived on. |
started_atrequired | string | |
answered_atrequired | string | null | |
ended_atrequired | string | null | |
answeredrequired | boolean | |
duration_srequired | integer | |
billable_srequired | integer | |
sip_coderequired | integer | null | |
hangup_causerequired | string | |
charge_nanosrequired | integer | What the call cost, in billionths of a dollar (2000000 = $0.002). |
recordingrequired | boolean | A recording can be fetched with GET /v1/calls/{id}/recording. |
test | true | Only on a sample sent with "send test": the values are made up. Real events never carry it. |
wallet.lowThe wallet dropped below the endpoint's threshold.
data
objectrequired | "balance" | |
balance_centsrequired | integer | |
threshold_centsrequired | integer | |
currencyrequired | "usd" | |
test | true | Only on a sample sent with "send test": the values are made up. Real events never carry it. |
backorder.filledEvery number a back-order asked for has arrived (sent once, after the numbers.arrived for the last delivery).
data
backorder_idrequired | string | |
order_idrequired | string | The order the numbers were delivered into. |
quantityrequired | integer | |
refunded_centsrequired | integer | Refunded in all because numbers cost less than the price paid up front. |
filled_atrequired | string | |
test | true | Only on a sample sent with "send test": the values are made up. Real events never carry it. |
number.routing_failedA number could not be pointed where you asked (a server, or your SIP endpoints), so it will not ring there yet. Point it again (PATCH the number) once the cause is fixed.
data
numberrequired | string | E.164, with the plus. |
server_idrequired | string | Empty when it was being pointed at your SIP endpoints. |
route_kindrequired | string | ingroup, or endpoints for your SIP endpoints. |
targetrequired | string | The in-group (or other target) it was to ring. |
stagerequired | "platform" | "switch" | "server" | platform: the server had no address or VICIfast trunk, or pointing failed before anything was sent. switch: our switch did not take the change. server: your server did not take it after three tries (is it running?). |
errorrequired | string | null | |
test | true | Only on a sample sent with "send test": the values are made up. Real events never carry it. |
wallet.topped_upMoney was added to the wallet: by card, PayPal, crypto, or by us.
data
objectrequired | "transaction" | |
idrequired | string | |
typerequired | string | |
amount_centsrequired | integer | Signed: positive credits the wallet. |
balance_after_centsrequired | integer | US cents. |
descriptionrequired | string | null | |
reference_idrequired | string | null | |
created_atrequired | string | |
test | true | Only on a sample sent with "send test": the values are made up. Real events never carry it. |
ai_call.completedOne of your AI agents finished a call: its outcome, the answers it collected, and a summary. Sent a minute or so after the call, once the summary is written. Only for agents with "Notify my CRM" on; never for test calls.
data
objectrequired | "ai_call" | |
idrequired | string | |
agentrequired | object | |
idrequired | string | |
namerequired | string | |
directionrequired | "inbound" | "outbound" | |
moderequired | "live" | "voicemail" | voicemail: the agent left a message on an answering machine. |
caller_numberrequired | string | null | The other party: who called in, or the lead that was dialled. |
lead_idrequired | string | null | The VICIdial lead id, when there is one. |
started_atrequired | string | null | |
ended_atrequired | string | null | |
duration_secondsrequired | integer | null | |
outcomerequired | string | null | What the agent decided the call was: qualified, appointment, not_interested, callback, do_not_call, wrong_number, answered, message, and so on. |
vicidial_statusrequired | string | null | The status written on the lead in VICIdial. |
transferred_torequired | string | null | The in-group the caller was handed to, when the agent transferred them. |
answersrequired | object | What the agent collected, keyed by the question you set up. |
callback_atrequired | string | null | |
appointment_atrequired | string | null | |
do_not_callrequired | boolean | The person asked not to be called again. |
summaryrequired | string | null | |
sentimentrequired | "positive" | "neutral" | "negative" | null | |
flagsrequired | string[] | Problems found when the call was reviewed: ai_not_disclosed, dnc_ignored, promise_made, talked_over, rude, wrong_info. |
recordingrequired | boolean | A recording is kept for this call (90 days). |
test | true | Only on a sample sent with "send test": the values are made up. Real events never carry it. |
Objects
What the endpoints and events above return, described once. Every object carries an object field naming its kind.
AvailableNumber
Fields
objectrequired | "available_number" | |
numberrequired | string | E.164, with the plus. |
typerequired | "local" | "toll_free" | |
staterequired | string | null | |
area_coderequired | string | null | |
rate_centerrequired | string | null | |
freshrequired | boolean | Never owned by anybody before. |
premiumrequired | boolean | |
monthly_price_centsrequired | integer | Your price, from your own terms. |
setup_price_centsrequired | integer | US cents. |
Backorder
Fields
objectrequired | "backorder" | |
idrequired | string | |
statusrequired | "open" | "filled" | "cancelled" | "expired" | |
typerequired | "local" | "toll_free" | |
quantityrequired | integer | |
filledrequired | integer | |
allow_nearbyrequired | boolean | |
price_each_centsrequired | integer | Prepaid per number; the most any will cost. |
paid_centsrequired | integer | US cents. |
refunded_centsrequired | integer | US cents. |
linesrequired | object[] | |
area_coderequired | string | Area code, or toll-free prefix. |
staterequired | string | null | |
quantityrequired | integer | |
filledrequired | integer | |
ring_onrequired | object | null | |
server_idrequired | string | |
in_grouprequired | string | |
order_idrequired | string | null | The order the delivered numbers are in, once the first arrives. |
notesrequired | string | null | What you sent with the order. |
created_atrequired | string | |
cancelled_atrequired | string | null |
BackorderQuote
Fields
objectrequired | "backorder_quote" | |
dry_runrequired | true | |
charge_centsrequired | integer | US cents. |
quantityrequired | integer | |
price_each_centsrequired | integer | US cents. |
balance_centsrequired | integer | US cents. |
Balance
Fields
objectrequired | "balance" | |
balance_centsrequired | integer | US cents. |
currencyrequired | "usd" | |
credit_limit_centsrequired | integer | How far below zero renewals may take the wallet so numbers keep ringing. Purchases need the funds. |
low_balance_alertrequired | object | |
enabledrequired | boolean | |
threshold_centsrequired | integer | null | |
auto_top_uprequired | object | |
enabledrequired | boolean |
BulkUpdateResult
Fields
objectrequired | "bulk_update" | |
updatedrequired | integer | |
numbersrequired | PhoneNumber[] |
Call
Fields
objectrequired | "call" | |
idrequired | string | The SIP call id. |
directionrequired | "inbound" | "outbound" | |
fromrequired | string | The caller, E.164 when it is a North American number. |
torequired | string | |
numberrequired | string | null | Inbound: which of your numbers it arrived on. |
started_atrequired | string | |
answered_atrequired | string | null | |
ended_atrequired | string | null | |
answeredrequired | boolean | |
duration_srequired | integer | |
billable_srequired | integer | |
sip_coderequired | integer | null | |
hangup_causerequired | string | |
charge_nanosrequired | integer | What the call cost, in billionths of a dollar (2000000 = $0.002). |
recordingrequired | boolean | A recording can be fetched with GET /v1/calls/{id}/recording. |
CidGroup
Fields
objectrequired | "cid_group" | |
idrequired | string | |
typerequired | "area_code" | "state" | "rotation" | area_code: a caller ID is picked to match the area code dialled. state: to match the state. rotation: every caller ID in turn. |
notesrequired | string | null | |
numbers_countrequired | integer | |
synced_atrequired | string | When the server was last read. |
CidGroupAdded
Fields
objectrequired | "cid_group_change" | |
group_idrequired | string | |
addedrequired | integer | |
skippedrequired | object[] | |
numberrequired | string | E.164, with the plus. |
errorrequired | string |
CidGroupRemoved
Fields
objectrequired | "cid_group_change" | |
group_idrequired | string | |
removedrequired | integer | |
not_in_grouprequired | string[] |
CidGroupWithNumbers
Fields
objectrequired | "cid_group" | |
idrequired | string | |
typerequired | "area_code" | "state" | "rotation" | area_code: a caller ID is picked to match the area code dialled. state: to match the state. rotation: every caller ID in turn. |
notesrequired | string | null | |
numbers_countrequired | integer | |
synced_atrequired | string | When the server was last read. |
numbersrequired | object[] | |
numberrequired | string | |
area_coderequired | string | What VICIdial matches on: an area code, a state, or NONE in a rotation. |
activerequired | boolean | |
descriptionrequired | string | null |
DeletedSipEndpoint
Fields
objectrequired | "sip_endpoint" | |
idrequired | string | |
deletedrequired | true |
Event
Fields
idrequired | string | |
objectrequired | "event" | |
typerequired | string | |
livemoderequired | boolean | |
created_atrequired | string | |
datarequired | object | Shaped by the type; see Webhooks below. Samples sent with "send test" have test: true. |
EventWithDeliveries
Fields
idrequired | string | |
objectrequired | "event" | |
typerequired | string | |
livemoderequired | boolean | |
created_atrequired | string | |
datarequired | object | Shaped by the type; see Webhooks below. Samples sent with "send test" have test: true. |
deliveriesrequired | WebhookDelivery[] |
JourneyEnrollment
Fields
objectrequired | "journey_enrollment" | |
enrolledrequired | boolean | |
lead_idrequired | integer | |
created_leadrequired | boolean | true when the number was not a lead yet. |
reason | string | Why not, when enrolled is false (already in it, re-entry rules). |
Message
Fields
idrequired | string | |
objectrequired | "message" | |
directionrequired | "inbound" | "outbound" | |
statusrequired | "queued" | "scheduled" | "sending" | "sent" | "delivered" | "undelivered" | "failed" | "suppressed" | "received" | sent means the carrier took it; only delivered means it landed. undelivered is the common outcome of carrier filtering. |
fromrequired | string | |
torequired | string | |
textrequired | string | null | |
media_urlsrequired | string[] | |
segmentsrequired | integer | null | |
cost_centsrequired | integer | null | |
currencyrequired | string | null | |
error_coderequired | string | null | |
error_detailrequired | string | null | |
conversation_idrequired | string | null | |
send_atrequired | string | null | When a scheduled message goes. |
sent_atrequired | string | null | |
delivered_atrequired | string | null | |
failed_atrequired | string | null | |
created_atrequired | string | null |
Order
Fields
objectrequired | "order" | |
idrequired | string | |
statusrequired | "open" | "filled" | "routed" | "closed" | open: still arriving (from a back-order). filled: all delivered. routed: all pointed at one place. closed: every number released. |
typerequired | "local" | "toll_free" | null | |
quantityrequired | integer | |
active_numbersrequired | integer | |
criteriarequired | object | |
staterequired | string | null | |
area_coderequired | string | null | |
containsrequired | string | null | |
backorder_idrequired | string | null | |
routingrequired | object | null | Where the whole order was pointed, when it was pointed as one. |
server_idrequired | string | |
in_grouprequired | string | null | |
created_atrequired | string | |
numbers | PhoneNumber[] | On GET /v1/orders/{id} only. |
PhoneNumber
Fields
objectrequired | "phone_number" | |
idrequired | string | |
numberrequired | string | E.164, with the plus. |
typerequired | "local" | "toll_free" | |
premiumrequired | boolean | A memorable number with its own price. |
statusrequired | "active" | "released" | |
labelrequired | string | null | |
notesrequired | string | null | |
staterequired | string | null | |
area_coderequired | string | null | |
rate_centerrequired | string | null | |
monthly_price_centsrequired | integer | US cents. |
currencyrequired | "usd" | |
auto_renewrequired | boolean | |
at_next_renewalrequired | "charge" | "release" | null | What happens on next_renewal_at. With auto_renew off the number is released at the first renewal after its three-month minimum term; before that, months are still charged. |
purchased_atrequired | string | |
min_term_ends_atrequired | string | |
next_renewal_atrequired | string | null | |
released_atrequired | string | null | |
release_charge_centsrequired | integer | null | |
order_idrequired | string | null | |
routingrequired | object | null | Where it rings on a server. Null when it rings nowhere or rings your SIP endpoints. |
server_idrequired | string | |
server_hostnamerequired | string | |
route_kindrequired | "ingroup" | "campaign" | "extension" | "ivr" | |
targetrequired | string | The in-group (or other target) calls land on. |
staterequired | "pending" | "applied" | "failed" | Whether the server has taken the change yet. |
errorrequired | string | null | |
sip_endpointsrequired | boolean | It rings your own SIP endpoints (see /api/v1/sip-endpoints); routing is then null. |
PurchaseQuote
Fields
objectrequired | "purchase_quote" | |
dry_runrequired | true | |
charge_centsrequired | integer | What the purchase would charge now. |
balance_centsrequired | integer | US cents. |
numbersrequired | object[] | |
numberrequired | string | E.164, with the plus. |
typerequired | "local" | "toll_free" | |
premiumrequired | boolean | |
monthly_price_centsrequired | integer | US cents. |
setup_price_centsrequired | integer | US cents. |
PurchaseResult
Fields
objectrequired | "purchase" | |
order_idrequired | string | |
charged_centsrequired | integer | US cents. |
balance_centsrequired | integer | Your wallet after the charge. |
numbersrequired | PhoneNumber[] | |
ringingrequired | object | null | How pointing went, when ring_on was given. |
okrequired | boolean | |
messagerequired | string |
RecordingLink
Fields
objectrequired | "recording_link" | |
call_idrequired | string | |
urlrequired | string | Fetch with no API key: the URL is the credential. WAV audio. Works for 15 minutes. |
expires_atrequired | string |
ReleaseResult
Fields
objectrequired | "release" | |
dry_runrequired | boolean | |
charge_centsrequired | integer | The balance of each number's three-month minimum term. Zero once the term is served. |
numbersrequired | object[] | |
numberrequired | string | E.164, with the plus. |
releasedrequired | boolean | |
charge_centsrequired | integer | US cents. |
errorrequired | string | null |
Renewal
Fields
objectrequired | "renewal" | |
kindrequired | "number" | "server" | |
descriptionrequired | string | The number, or the server hostname. |
due_atrequired | string | |
actionrequired | "charge" | "release" | release: a number with auto-renew off, past its minimum term, ends that day. |
amount_centsrequired | integer | US cents. |
ResendResult
Fields
objectrequired | "event_resend" | |
event_idrequired | string | |
deliveriesrequired | WebhookDelivery[] |
SandboxReset
Fields
objectrequired | "sandbox" | |
resetrequired | true |
Server
Fields
objectrequired | "server" | |
idrequired | string | |
hostnamerequired | string | |
statusrequired | string | |
accepts_numbersrequired | boolean | Has an address, so numbers can be pointed at it. They arrive over the VICIfast trunk, installed on the server when needed. |
SipEndpoint
Fields
objectrequired | "sip_endpoint" | |
idrequired | string | |
hostrequired | string | A public IP address or a DNS name. |
portrequired | integer | |
transportrequired | "udp" | "tcp" | "tls" | |
weightrequired | integer | Its share under weighted sharing, 1 to 1000. |
enabledrequired | boolean | Off takes it out of the rotation without forgetting it. Its addresses can still send your outbound calls. |
noterequired | string | null | |
addressesrequired | object[] | What the host resolves to, checked every five minutes. Every address takes part in the rotation. Empty: the name does not resolve yet. |
iprequired | string | |
healthrequired | "up" | "down" | "unknown" | "refused" | up / down: answering our health checks, or stopped. unknown: has never answered one — common for a PBX that ignores OPTIONS, and it still gets calls. refused: never used, see 'refused'. |
refusedrequired | "" | "private" | "ours" | "other_account" | Why a refused address is never used: private (not reachable from the internet), ours (one of our addresses or our carriers), other_account (in use on another account). |
SipEndpointList
Fields
objectrequired | "sip_endpoint_list" | |
sharerequired | "roundrobin" | "weighted" | roundrobin: every address takes calls equally. weighted: in proportion to weight. |
new_numbers_ring_endpointsrequired | boolean | Numbers bought from now on ring these endpoints unless the purchase names a server. |
datarequired | SipEndpoint[] |
TimeTravelResult
Fields
objectrequired | "time_travel" | |
daysrequired | integer | |
renewals_chargedrequired | integer | |
charged_centsrequired | integer | US cents. |
releasedrequired | string[] | |
renewal_failedrequired | string[] |
Transaction
Fields
objectrequired | "transaction" | |
idrequired | string | |
typerequired | string | |
amount_centsrequired | integer | Signed: positive credits the wallet. |
balance_after_centsrequired | integer | US cents. |
descriptionrequired | string | null | |
reference_idrequired | string | null | |
created_atrequired | string |
Transcript
Fields
objectrequired | "transcript" | |
call_idrequired | string | |
languagerequired | string | null | |
textrequired | string | |
utterancesrequired | object[] | |
speakerrequired | string | A, B, … as told apart in the audio; ? when unsure. |
start_srequired | number | |
end_srequired | number | |
textrequired | string | |
duration_srequired | integer | |
charged_centsrequired | integer | What making it cost, once. |
created_atrequired | string |
TranscriptQuote
Fields
objectrequired | "transcript_quote" | |
dry_runrequired | true | |
call_idrequired | string | |
minutesrequired | integer | |
price_per_minute_centsrequired | integer | US cents. |
charge_centsrequired | integer | US cents. |
balance_centsrequired | integer | US cents. |
WebhookDelivery
Fields
objectrequired | "webhook_delivery" | |
idrequired | string | Sent as X-VICIfast-Delivery. |
endpoint_idrequired | string | |
event_idrequired | string | |
event_typerequired | string | |
testrequired | boolean | A sample sent with "send test". Tried once. |
staterequired | "pending" | "retrying" | "delivered" | "gave_up" | retrying: failed, and tried again after 1, 5, 15, 60 and 240 minutes. gave_up: every attempt failed, or the receiver refused it outright (a 4xx other than 408 or 429). |
attemptsrequired | integer | |
last_statusrequired | integer | null | |
last_errorrequired | string | null | |
created_atrequired | string | |
delivered_atrequired | string | null | |
next_attempt_atrequired | string | null |
WebhookEndpoint
Fields
objectrequired | "webhook_endpoint" | |
idrequired | string | |
urlrequired | string | |
descriptionrequired | string | null | |
eventsrequired | ("numbers.arrived" | "number.released" | "number.renewal_failed" | "call.inbound.completed" | "call.outbound.completed" | "wallet.low" | "backorder.filled" | "number.routing_failed" | "wallet.topped_up" | "ai_call.completed")[] | |
enabledrequired | boolean | false: nothing is sent to it. |
livemoderequired | boolean | true: your account's real events. false: the sandbox's, from test keys. Follows the key that created it. |
wallet_low_threshold_centsrequired | integer | null | wallet.low fires once when the balance drops below this, and again only after it has been back above. |
last_statusrequired | integer | null | HTTP status of the last attempt. |
last_errorrequired | string | null | |
last_sent_atrequired | string | null | |
failing_sincerequired | string | null | Set while deliveries are failing; cleared by the next one that succeeds. |
created_atrequired | string |
WebhookEndpointWithSecret
Fields
objectrequired | "webhook_endpoint" | |
idrequired | string | |
urlrequired | string | |
descriptionrequired | string | null | |
eventsrequired | ("numbers.arrived" | "number.released" | "number.renewal_failed" | "call.inbound.completed" | "call.outbound.completed" | "wallet.low" | "backorder.filled" | "number.routing_failed" | "wallet.topped_up" | "ai_call.completed")[] | |
enabledrequired | boolean | false: nothing is sent to it. |
livemoderequired | boolean | true: your account's real events. false: the sandbox's, from test keys. Follows the key that created it. |
wallet_low_threshold_centsrequired | integer | null | wallet.low fires once when the balance drops below this, and again only after it has been back above. |
last_statusrequired | integer | null | HTTP status of the last attempt. |
last_errorrequired | string | null | |
last_sent_atrequired | string | null | |
failing_sincerequired | string | null | Set while deliveries are failing; cleared by the next one that succeeds. |
created_atrequired | string | |
secretrequired | string | Signs every delivery (X-VICIfast-Signature). Shown when the endpoint is made and when the secret is rotated; keep it. |
WebhookSecret
Fields
objectrequired | "webhook_secret" | |
endpoint_idrequired | string | |
secretrequired | string | The new secret. The old one stops working from the next delivery. |
WebhookTestResult
Fields
objectrequired | "webhook_test" | |
event_idrequired | string | |
delivery_idrequired | string | |
deliveredrequired | boolean | Whether your endpoint answered 2xx. |
statusrequired | integer | null | |
errorrequired | string | null |
Common questions
How do I authenticate with the VICIfast API?
Send an API key from API → Keys in the dashboard as Authorization: Bearer vf_live_…. Each key has scopes (reading numbers, buying them, releasing them, billing and webhooks are separate grants) and can be limited to the IP addresses you use.
Is there a sandbox for testing?
Yes. Keys that start vf_test_ work against a sandbox with a pretend $100 wallet, a made-up catalogue and one server. Nothing real is bought or charged, and the sandbox can simulate an inbound call, time passing, a back-order delivery or a low wallet.
What are the rate limits?
600 requests a minute per key, on every endpoint. A request over the limit is answered 429 with a Retry-After header saying when to try again.
How are webhooks signed and retried?
Every delivery carries X-VICIfast-Signature: an HMAC-SHA256 of the timestamp and the body with the endpoint’s secret. Refuse one more than five minutes old. Anything but a 2xx is retried after 1, 5, 15, 60 and 240 minutes.
How do I retry a purchase safely?
Send an Idempotency-Key header on every request that spends or refunds money. Retrying with the same key returns the original answer instead of buying twice; keys are remembered for 24 hours.
Are there SDKs for the VICIfast API?
Yes: @vicifast/api for TypeScript and Node.js (npm install @vicifast/api) and vicifast for Python (pip install vicifast). Both are generated from the OpenAPI document, and both send Idempotency-Keys, retry safely, walk pages and check webhook signatures for you.
Is there an OpenAPI specification?
Yes: an OpenAPI 3.1 document at /api-docs/openapi.json, generated from the same schemas the API validates requests with, for code generators and API clients.