VICIfast
All guides

VICIfast API SDKs for TypeScript and Python

Install @vicifast/api or vicifast, make your first calls, page through lists, handle errors and retries safely, verify webhooks and test in the sandbox - with every SDK method listed.

VICIfastLast updated

The VICIfast API has two official SDKs: @vicifast/api for TypeScript and Node.js, and vicifast for Python. They call the same REST API documented at /api-docs, and they are generated from the same OpenAPI document, so every endpoint is a method and every method takes exactly what the endpoint takes. Neither package has dependencies.

You do not need an SDK to use the API - every request is plain HTTPS and JSON, and the reference shows each one in curl, Node.js, Python and PHP. What the SDKs add is the part that is easy to get subtly wrong: a safe Idempotency-Key on every call that spends money, retries only where retrying is safe, pages, typed errors and webhook signature checks.

How do I install an SDK?

npm install @vicifast/api   # Node.js 18.17 or later
pip install vicifast        # Python 3.8 or later

Then create an API key in the dashboard under API → Keys. A key that starts vf_test_ works against the sandbox: the same calls and the same answers, with a pretend $100 wallet and nothing real bought or charged. Build with a test key, then swap in a live one (vf_live_…) - nothing else changes.

Your first calls

The client takes the key; everything else has a sensible default.

import { Vicifast, VicifastError, parseEvent } from '@vicifast/api';

const vf = new Vicifast({ apiKey: process.env.VICIFAST_KEY! });

const { data: balance } = await vf.getBalance();
console.log(`wallet: ${balance.balance_cents} cents, live: ${vf.livemode}`);

const found = await vf.searchNumbers({ query: { area_code: '305', limit: 5 } });
const first = found.data[0]!.number;
const order = await vf.purchaseNumbers({ body: { numbers: [first] } });
// The answer is a quote when you send dry_run: true, a purchase otherwise.
if (order.data.object === 'purchase') console.log(order.data.order_id);
import os
from vicifast import Vicifast, VicifastError, parse_event

vf = Vicifast(api_key=os.environ["VICIFAST_KEY"])

balance = vf.get_balance()["data"]
print(f"wallet: {balance['balance_cents']} cents, live: {vf.livemode}")

found = vf.search_numbers(area_code="305", limit=5)
first = found["data"][0]["number"]
order = vf.purchase_numbers({"numbers": [first]})
# The answer is a quote when you send "dry_run": True, a purchase otherwise.
if order["data"]["object"] == "purchase":
    print(order["data"]["order_id"])

How are the methods named?

Each endpoint's method is its operationId from the reference: getBalance, purchaseNumbers, listCalls. Python uses the same name in snake_case: get_balance, purchase_numbers, list_calls.

  • TypeScript takes one object: path for the parts of the URL, query for the query string, body for the JSON body. The types say which of the three each method needs, and every response is typed.
  • Python takes the path parameters first, in order, then the body, then query parameters as keyword arguments: vf.update_number("+13055550101", {"auto_renew": False}). A query parameter whose name is a Python keyword takes a trailing underscore: vf.list_calls(from_="+13055550101").
await vf.updateNumber({ path: { number: '+13055550101' }, body: { auto_renew: false } });
const today = await vf.listCalls({ query: { from: '+13055550101', window: '24h' } });
vf.update_number("+13055550101", {"auto_renew": False})
today = vf.list_calls(from_="+13055550101", window="24h")

The full list is at the end of this guide.

How do I get every page of a list?

A list answers one page at a time with has_more and next_cursor. paginate follows the cursor for you and hands back one item at a time, fetching the next page only when you reach it.

for await (const number of vf.paginate('listNumbers', { query: { status: 'active' } })) {
  console.log(number.number, number.routing);
}
// The calls export is CSV, one file per page of up to 10,000 calls.
for await (const csv of vf.exportCallFiles({ query: { window: '30d' } })) {
  process.stdout.write(csv);
}
for number in vf.paginate("listNumbers", status="active"):
    print(number["number"], number["routing"])
# The calls export is CSV, one file per page of up to 10,000 calls.
for csv in vf.export_call_files(window="30d"):
    print(csv, end="")

What happens when a call fails?

Anything other than a success raises a VicifastError. Branch on its code, never on the message: the codes are listed in the reference. It also carries the HTTP status, the offending param where there is one, any details, and a request id to quote to support.

try {
  await vf.purchaseNumbers({ body: { numbers: ['+13055550199'] } });
} catch (e) {
  if (e instanceof VicifastError && e.code === 'insufficient_funds') {
    console.log(`top up first (request ${e.requestId})`);
  } else {
    throw e;
  }
}
try:
    vf.purchase_numbers({"numbers": ["+13055550199"]})
except VicifastError as e:
    if e.code == "insufficient_funds":
        print(f"top up first (request {e.request_id})")
    else:
        raise

Are retries safe?

Yes, and that is most of the reason to use an SDK. A request is retried - twice by default, on a timeout, a dropped connection, a 429 or a 5xx - only when repeating it cannot do anything twice:

  • Reads are always safe to repeat.
  • Calls that spend or move money - buying, releasing, back-ordering, transcribing - are sent with an Idempotency-Key the SDK makes for you, and the same key goes with every retry. The API answers a repeated key with the original result, so a retried purchase still buys once.
  • Everything else is sent once, and a failure is yours to decide about.

A 429 waits as long as its Retry-After header asks. To make a purchase exactly-once across a restart of your own program, choose the key yourself (idempotencyKey in TypeScript, idempotency_key= in Python) and store it with the order before you send it.

How do I check a webhook is from VICIfast?

Every webhook carries an X-VICIfast-Signature header signed with your endpoint's secret. Check it against the body exactly as it arrived - before any JSON parsing - and refuse anything that fails. parseEvent checks the signature and the timestamp (a request signed more than five minutes ago is refused, so a captured one cannot be replayed) and returns the event; in TypeScript the event is typed by its type.

export function onWebhook(rawBody: Buffer, signature: string | undefined): void {
  const event = parseEvent(rawBody, signature, process.env.VICIFAST_WEBHOOK_SECRET!);
  if (event.type === 'call.inbound.completed') {
    console.log('a call ended', event.data);
  }
}
def on_webhook(raw_body: bytes, signature: str) -> None:
    event = parse_event(raw_body, signature, os.environ["VICIFAST_WEBHOOK_SECRET"])
    if event["type"] == "call.inbound.completed":
        print("a call ended", event["data"])

Wire it to your framework's raw request body - express.raw() in Express, request.get_data() in Flask. Receiving VICIfast webhooks covers endpoints, events and retries.

Testing in the sandbox

With a test key, the sandbox can make things happen on demand, so you can watch your code react without waiting for a real call or a real month. Each simulation sends the same webhooks a real one would.

await vf.simulateInboundCall({ body: { number: first, duration_s: 42 } });
await vf.timeTravel({ body: { days: 31 } }); // renewals fall due
await vf.setSandboxWallet({ body: { balance_cents: 150 } }); // a low wallet
await vf.resetSandbox();
vf.simulate_inbound_call({"number": first, "duration_s": 42})
vf.time_travel({"days": 31})  # renewals fall due
vf.set_sandbox_wallet({"balance_cents": 150})  # a low wallet
vf.reset_sandbox()

A live key refuses these with sandbox_only. Testing in the sandbox has the rest.

Every method

Every endpoint, with its method in each SDK. Optional parts are shown as the API takes them; the reference has every field.

Numbers

What it doesMethod
List your numbers
GET /api/v1/numbers
TSvf.listNumbers({ query })
Pyvf.list_numbers(**query)
Change many numbers at once
PATCH /api/v1/numbers
TSvf.bulkUpdateNumbers({ body })
Pyvf.bulk_update_numbers(body)
Search numbers for sale
GET /api/v1/numbers/available
TSvf.searchNumbers({ query })
Pyvf.search_numbers(**query)
Buy numbers
POST /api/v1/numbers/purchase
TSvf.purchaseNumbers({ body })
Pyvf.purchase_numbers(body)
Give numbers back
POST /api/v1/numbers/release
TSvf.releaseNumbers({ body })
Pyvf.release_numbers(body)
Get a number
GET /api/v1/numbers/{number}
TSvf.getNumber({ path: { number } })
Pyvf.get_number(number)
Update a number
PATCH /api/v1/numbers/{number}
TSvf.updateNumber({ path: { number }, body })
Pyvf.update_number(number, body)
List your servers
GET /api/v1/servers
TSvf.listServers()
Pyvf.list_servers()
List a server's CID groups
GET /api/v1/servers/{id}/cid-groups
TSvf.listCidGroups({ path: { id } })
Pyvf.list_cid_groups(id)
Get a CID group, with its numbers
GET /api/v1/servers/{id}/cid-groups/{group}
TSvf.getCidGroup({ path: { id, group } })
Pyvf.get_cid_group(id, group)
Put numbers into a CID group
POST /api/v1/servers/{id}/cid-groups/{group}/numbers
TSvf.addCidGroupNumbers({ path: { id, group }, body })
Pyvf.add_cid_group_numbers(id, group, body)
Take numbers out of a CID group
POST /api/v1/servers/{id}/cid-groups/{group}/numbers/remove
TSvf.removeCidGroupNumbers({ path: { id, group }, body })
Pyvf.remove_cid_group_numbers(id, group, body)

Orders

What it doesMethod
List orders
GET /api/v1/orders
TSvf.listOrders({ query })
Pyvf.list_orders(**query)
Get an order, with its numbers
GET /api/v1/orders/{id}
TSvf.getOrder({ path: { id } })
Pyvf.get_order(id)
List back-orders
GET /api/v1/backorders
TSvf.listBackorders({ query })
Pyvf.list_backorders(**query)
Place a back-order
POST /api/v1/backorders
TSvf.placeBackorder({ body })
Pyvf.place_backorder(body)
Get a back-order
GET /api/v1/backorders/{id}
TSvf.getBackorder({ path: { id } })
Pyvf.get_backorder(id)
Cancel a back-order
POST /api/v1/backorders/{id}/cancel
TSvf.cancelBackorder({ path: { id } })
Pyvf.cancel_backorder(id)

Calls

What it doesMethod
List calls
GET /api/v1/calls
TSvf.listCalls({ query })
Pyvf.list_calls(**query)
Get a call
GET /api/v1/calls/{id}
TSvf.getCall({ path: { id } })
Pyvf.get_call(id)
Export calls as CSV
GET /api/v1/calls/export
TSvf.exportCalls({ query })
Pyvf.export_calls(**query)
Get a call's transcript
GET /api/v1/calls/{id}/transcript
TSvf.getTranscript({ path: { id } })
Pyvf.get_transcript(id)
Transcribe a call
POST /api/v1/calls/{id}/transcript
TSvf.createTranscript({ path: { id }, body })
Pyvf.create_transcript(id, body=None)
Get a recording link
GET /api/v1/calls/{id}/recording
TSvf.getRecordingLink({ path: { id } })
Pyvf.get_recording_link(id)

Billing

What it doesMethod
Get the wallet balance
GET /api/v1/billing/balance
TSvf.getBalance()
Pyvf.get_balance()
List wallet transactions
GET /api/v1/billing/transactions
TSvf.listTransactions({ query })
Pyvf.list_transactions(**query)
List upcoming renewals
GET /api/v1/billing/renewals
TSvf.listRenewals({ query })
Pyvf.list_renewals(**query)

Messaging

What it doesMethod
Send a message
POST /api/v1/messages
TSvf.sendMessage({ body })
Pyvf.send_message(body)
List messages
GET /api/v1/messages
TSvf.listMessages({ query })
Pyvf.list_messages(**query)

Journeys

What it doesMethod
Enrol a lead in a journey
POST /api/v1/journeys/{id}/enroll
TSvf.enrollInJourney({ path: { id }, body })
Pyvf.enroll_in_journey(id, body)

Webhook endpoints

What it doesMethod
List webhook endpoints
GET /api/v1/webhook-endpoints
TSvf.listWebhookEndpoints()
Pyvf.list_webhook_endpoints()
Add a webhook endpoint
POST /api/v1/webhook-endpoints
TSvf.createWebhookEndpoint({ body })
Pyvf.create_webhook_endpoint(body)
Get a webhook endpoint
GET /api/v1/webhook-endpoints/{id}
TSvf.getWebhookEndpoint({ path: { id } })
Pyvf.get_webhook_endpoint(id)
Change a webhook endpoint
PATCH /api/v1/webhook-endpoints/{id}
TSvf.updateWebhookEndpoint({ path: { id }, body })
Pyvf.update_webhook_endpoint(id, body)
Remove a webhook endpoint
DELETE /api/v1/webhook-endpoints/{id}
TSvf.deleteWebhookEndpoint({ path: { id } })
Pyvf.delete_webhook_endpoint(id)
Rotate the signing secret
POST /api/v1/webhook-endpoints/{id}/rotate-secret
TSvf.rotateWebhookSecret({ path: { id } })
Pyvf.rotate_webhook_secret(id)
Send a test event
POST /api/v1/webhook-endpoints/{id}/test
TSvf.sendTestWebhook({ path: { id }, body })
Pyvf.send_test_webhook(id, body)
List deliveries to an endpoint
GET /api/v1/webhook-endpoints/{id}/deliveries
TSvf.listWebhookDeliveries({ path: { id }, query })
Pyvf.list_webhook_deliveries(id, **query)

Events

What it doesMethod
List events
GET /api/v1/events
TSvf.listEvents({ query })
Pyvf.list_events(**query)
Get an event, with its deliveries
GET /api/v1/events/{id}
TSvf.getEvent({ path: { id } })
Pyvf.get_event(id)
Send an event again
POST /api/v1/events/{id}/resend
TSvf.resendEvent({ path: { id }, body })
Pyvf.resend_event(id, body=None)

Sandbox

What it doesMethod
Simulate an inbound call
POST /api/v1/sandbox/inbound-calls
TSvf.simulateInboundCall({ body })
Pyvf.simulate_inbound_call(body)
Move the sandbox on in time
POST /api/v1/sandbox/time-travel
TSvf.timeTravel({ body })
Pyvf.time_travel(body)
Simulate a back-order delivery
POST /api/v1/sandbox/backorders/{id}/deliver
TSvf.deliverBackorder({ path: { id }, body })
Pyvf.deliver_backorder(id, body=None)
Set the sandbox wallet
POST /api/v1/sandbox/wallet
TSvf.setSandboxWallet({ body })
Pyvf.set_sandbox_wallet(body)
Start the sandbox over
POST /api/v1/sandbox/reset
TSvf.resetSandbox()
Pyvf.reset_sandbox()

Which version am I on?

Each package's version is on npm and PyPI. A new version follows a change to the API: the SDKs are regenerated from the API's own specification, so a new endpoint appears as a new method under the name the reference gives it. Upgrade with npm install @vicifast/api@latest or pip install -U vicifast.