VICIfast

API reference · v1

VICIfast API

Buy and manage phone numbers, place back-orders, read your calls and recordings, and follow your wallet from your own code. JSON over HTTPS, one error shape, signed webhooks, and a sandbox that never charges.

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 and seeing billing 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. Reset the sandbox from API → Keys in the dashboard.

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 } } }
CodeStatusMeaning
invalid_request400Malformed input; `param` names the field.
unauthorized401No API key, or not a valid one.
insufficient_funds402The wallet cannot cover it. Nothing was charged; top up and retry with the same Idempotency-Key.
forbidden403The key lacks the scope this endpoint needs.
ip_not_allowed403The key is restricted to other addresses.
account_not_approved403The account has no approved termination account yet. Test keys still work.
test_mode_unsupported403This endpoint has no sandbox.
not_found404No such thing on this account.
conflict409A request with this Idempotency-Key is still running, or the thing changed state.
numbers_unavailable409Those numbers are no longer for sale, or there are not enough. `details` says which.
unprocessable422Understood but refused (an Idempotency-Key reused for a different request, a server that cannot take numbers).
premium_price_not_accepted422A premium number named without accept_premium_price_cents equal to its price.
limit_exceeded422It would charge more than your max_total_cents or max_charge_cents. Nothing happened.
rate_limited429Over 600 requests a minute on this key. Wait Retry-After seconds.
internal_error500Our fault. Quote X-Request-Id to support.
service_unavailable503Something 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 — 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.

get/api/v1/numbers

List your numbers

The numbers this account holds (or held), newest first.

Needs the numbers:read scope.

Try it in the playground →

Parameters

status"active" | "released" | "all" · queryDefault active.
type"local" | "toll_free" · query
routed"true" | "false" · queryPointed somewhere, or not.
area_codestring · queryOnly numbers in this area code (or toll-free prefix).
order_idstring · queryOnly numbers from this order.
limitinteger · queryPage size, up to 500.
cursorstring · querynext_cursor from the previous page.

200 · A page of numbers.

datarequiredobject[]
objectrequired"phone_number"
idrequiredstring
numberrequiredstringE.164, with the plus.
typerequired"local" | "toll_free"
premiumrequiredbooleanA memorable number with its own price.
statusrequired"active" | "released"
labelrequiredstring | null
notesrequiredstring | null
staterequiredstring | null
area_coderequiredstring | null
rate_centerrequiredstring | null
monthly_price_centsrequiredintegerUS cents.
currencyrequired"usd"
auto_renewrequiredboolean
at_next_renewalrequired"charge" | "release" | nullWhat 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_atrequiredstring
min_term_ends_atrequiredstring
next_renewal_atrequiredstring | null
released_atrequiredstring | null
release_charge_centsrequiredinteger | null
order_idrequiredstring | null
routingrequiredobject | nullNull when it rings nowhere.
server_idrequiredstring
server_hostnamerequiredstring
route_kindrequired"ingroup" | "campaign" | "extension" | "ivr"
targetrequiredstringThe in-group (or other target) calls land on.
staterequired"pending" | "applied" | "failed"Whether the server has taken the change yet.
errorrequiredstring | null
has_morerequiredboolean
next_cursorrequiredstring | null
curl https://vicifast.com/api/v1/numbers?status=active&limit=50 \
  -H "Authorization: Bearer $VICIFAST_KEY"
get/api/v1/numbers/available

Search 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.

Try it in the playground →

Parameters

type"local" | "toll_free" · query
area_codestring · queryFor toll-free, the prefix: 800, 833, 844…
statestring · queryTwo-letter state.
containsstring · queryDigits the number must contain.
include_premium"true" | "false" · queryDefault false.
limitinteger · queryPage size, up to 500.
cursorstring · querynext_cursor from the previous page.

200 · A page of numbers for sale.

datarequiredobject[]
objectrequired"available_number"
numberrequiredstringE.164, with the plus.
typerequired"local" | "toll_free"
staterequiredstring | null
area_coderequiredstring | null
rate_centerrequiredstring | null
freshrequiredbooleanNever owned by anybody before.
premiumrequiredboolean
monthly_price_centsrequiredintegerYour price, from your own terms.
setup_price_centsrequiredintegerUS cents.
has_morerequiredboolean
next_cursorrequiredstring | null
curl https://vicifast.com/api/v1/numbers/available?area_code=305 \
  -H "Authorization: Bearer $VICIFAST_KEY"
post/api/v1/numbers/purchase

Buy 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.

Try it in the playground →

Parameters

Idempotency-Keystring · header

Request body

numbers(string | object)[]Buy these exact numbers. All of them or none. Premium numbers need accept_premium_price_cents.
numberrequiredstringAny common North American form.
accept_premium_price_centsintegerRequired for a premium number: its monthly price, as you were shown it.
quantityintegerOr 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_codestringWith quantity: the area code (toll-free: the prefix).
statestringWith quantity: two-letter state.
containsstringWith quantity: digits the numbers must contain.
max_total_centsintegerRefuse the purchase if it would charge more than this now.
ring_onobjectPoint the new numbers here straight away.
server_idrequiredstring
in_grouprequiredstring
dry_runbooleanReturn 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.

datarequiredobject
objectrequired"purchase_quote"
dry_runrequiredtrue
charge_centsrequiredintegerWhat the purchase would charge now.
balance_centsrequiredintegerUS cents.
numbersrequiredobject[]
numberrequiredstringE.164, with the plus.
typerequired"local" | "toll_free"
premiumrequiredboolean
monthly_price_centsrequiredintegerUS cents.
setup_price_centsrequiredintegerUS cents.

201 · Bought. The numbers are yours.

datarequiredobject
objectrequired"purchase"
order_idrequiredstring
charged_centsrequiredintegerUS cents.
balance_centsrequiredintegerYour wallet after the charge.
numbersrequiredobject[]
objectrequired"phone_number"
idrequiredstring
numberrequiredstringE.164, with the plus.
typerequired"local" | "toll_free"
premiumrequiredbooleanA memorable number with its own price.
statusrequired"active" | "released"
labelrequiredstring | null
notesrequiredstring | null
staterequiredstring | null
area_coderequiredstring | null
rate_centerrequiredstring | null
monthly_price_centsrequiredintegerUS cents.
currencyrequired"usd"
auto_renewrequiredboolean
at_next_renewalrequired"charge" | "release" | nullWhat 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_atrequiredstring
min_term_ends_atrequiredstring
next_renewal_atrequiredstring | null
released_atrequiredstring | null
release_charge_centsrequiredinteger | null
order_idrequiredstring | null
routingrequiredobject | nullNull when it rings nowhere.
server_idrequiredstring
server_hostnamerequiredstring
route_kindrequired"ingroup" | "campaign" | "extension" | "ivr"
targetrequiredstringThe in-group (or other target) calls land on.
staterequired"pending" | "applied" | "failed"Whether the server has taken the change yet.
errorrequiredstring | null
ringingrequiredobject | nullHow pointing went, when ring_on was given.
okrequiredboolean
messagerequiredstring
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" }
  }'
post/api/v1/numbers/release

Give 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.

Try it in the playground →

Parameters

Idempotency-Keystring · header

Request body

numbersrequiredstring[]
dry_runbooleanReturn exactly what releasing would charge, and release nothing.
max_charge_centsintegerRefuse if the minimum-term charge would be more than this.

200 · Released (or, with dry_run, what releasing would cost).

datarequiredobject
objectrequired"release"
dry_runrequiredboolean
charge_centsrequiredintegerThe balance of each number's three-month minimum term. Zero once the term is served.
numbersrequiredobject[]
numberrequiredstringE.164, with the plus.
releasedrequiredboolean
charge_centsrequiredintegerUS cents.
errorrequiredstring | null
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 }'
get/api/v1/numbers/{number}

Get a number

Needs the numbers:read scope.

Try it in the playground →

Parameters

numberrequiredstring · pathThe number, in any common form (+13055551234 URL-encoded as %2B13055551234).

200 · The number.

datarequiredobject
objectrequired"phone_number"
idrequiredstring
numberrequiredstringE.164, with the plus.
typerequired"local" | "toll_free"
premiumrequiredbooleanA memorable number with its own price.
statusrequired"active" | "released"
labelrequiredstring | null
notesrequiredstring | null
staterequiredstring | null
area_coderequiredstring | null
rate_centerrequiredstring | null
monthly_price_centsrequiredintegerUS cents.
currencyrequired"usd"
auto_renewrequiredboolean
at_next_renewalrequired"charge" | "release" | nullWhat 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_atrequiredstring
min_term_ends_atrequiredstring
next_renewal_atrequiredstring | null
released_atrequiredstring | null
release_charge_centsrequiredinteger | null
order_idrequiredstring | null
routingrequiredobject | nullNull when it rings nowhere.
server_idrequiredstring
server_hostnamerequiredstring
route_kindrequired"ingroup" | "campaign" | "extension" | "ivr"
targetrequiredstringThe in-group (or other target) calls land on.
staterequired"pending" | "applied" | "failed"Whether the server has taken the change yet.
errorrequiredstring | null
curl https://vicifast.com/api/v1/numbers/%2B13055550101 \
  -H "Authorization: Bearer $VICIFAST_KEY"
patch/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.

Try it in the playground →

Parameters

numberrequiredstring · pathThe number, in any common form (+13055551234 URL-encoded as %2B13055551234).

Request body

labelstring | null
notesstring | null
auto_renewboolean
routingobject | nullWhere it rings. null unpoints it; it stays yours and keeps billing.
server_idrequiredstring
in_grouprequiredstringThe VICIdial in-group calls should land on.

200 · The number, as it is now.

datarequiredobject
objectrequired"phone_number"
idrequiredstring
numberrequiredstringE.164, with the plus.
typerequired"local" | "toll_free"
premiumrequiredbooleanA memorable number with its own price.
statusrequired"active" | "released"
labelrequiredstring | null
notesrequiredstring | null
staterequiredstring | null
area_coderequiredstring | null
rate_centerrequiredstring | null
monthly_price_centsrequiredintegerUS cents.
currencyrequired"usd"
auto_renewrequiredboolean
at_next_renewalrequired"charge" | "release" | nullWhat 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_atrequiredstring
min_term_ends_atrequiredstring
next_renewal_atrequiredstring | null
released_atrequiredstring | null
release_charge_centsrequiredinteger | null
order_idrequiredstring | null
routingrequiredobject | nullNull when it rings nowhere.
server_idrequiredstring
server_hostnamerequiredstring
route_kindrequired"ingroup" | "campaign" | "extension" | "ivr"
targetrequiredstringThe in-group (or other target) calls land on.
staterequired"pending" | "applied" | "failed"Whether the server has taken the change yet.
errorrequiredstring | null
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" }
  }'
get/api/v1/servers

List your servers

Where numbers can be pointed: use a server id in routing or ring_on.

Needs the numbers:read scope.

Try it in the playground →

200 · Your servers.

datarequiredobject[]
objectrequired"server"
idrequiredstring
hostnamerequiredstring
statusrequiredstring
accepts_numbersrequiredbooleanHas an address, so numbers can be pointed at it. They arrive over the VICIfast trunk, installed on the server when needed.
has_morerequiredboolean
next_cursorrequiredstring | null
curl https://vicifast.com/api/v1/servers \
  -H "Authorization: Bearer $VICIFAST_KEY"

Orders

Purchases and back-orders.

get/api/v1/orders

List orders

Batches of numbers you received: a purchase, or a back-order as it fills.

Needs the orders:read scope.

Try it in the playground →

Parameters

status"open" | "filled" | "routed" | "closed" · query
limitinteger · queryPage size, up to 500.
cursorstring · querynext_cursor from the previous page.

200 · A page of orders.

datarequiredobject[]
objectrequired"order"
idrequiredstring
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
quantityrequiredinteger
active_numbersrequiredinteger
criteriarequiredobject
staterequiredstring | null
area_coderequiredstring | null
containsrequiredstring | null
backorder_idrequiredstring | null
routingrequiredobject | nullWhere the whole order was pointed, when it was pointed as one.
server_idrequiredstring
in_grouprequiredstring | null
created_atrequiredstring
numbersobject[]On GET /v1/orders/{id} only.
objectrequired"phone_number"
idrequiredstring
numberrequiredstringE.164, with the plus.
typerequired"local" | "toll_free"
premiumrequiredbooleanA memorable number with its own price.
statusrequired"active" | "released"
labelrequiredstring | null
notesrequiredstring | null
staterequiredstring | null
area_coderequiredstring | null
rate_centerrequiredstring | null
monthly_price_centsrequiredintegerUS cents.
currencyrequired"usd"
auto_renewrequiredboolean
at_next_renewalrequired"charge" | "release" | nullWhat 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_atrequiredstring
min_term_ends_atrequiredstring
next_renewal_atrequiredstring | null
released_atrequiredstring | null
release_charge_centsrequiredinteger | null
order_idrequiredstring | null
routingrequiredobject | nullNull when it rings nowhere.
server_idrequiredstring
server_hostnamerequiredstring
route_kindrequired"ingroup" | "campaign" | "extension" | "ivr"
targetrequiredstringThe in-group (or other target) calls land on.
staterequired"pending" | "applied" | "failed"Whether the server has taken the change yet.
errorrequiredstring | null
has_morerequiredboolean
next_cursorrequiredstring | null
curl https://vicifast.com/api/v1/orders \
  -H "Authorization: Bearer $VICIFAST_KEY"
get/api/v1/orders/{id}

Get an order, with its numbers

Needs the orders:read scope.

Try it in the playground →

Parameters

idrequiredstring · path

200 · The order.

datarequiredobject
objectrequired"order"
idrequiredstring
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
quantityrequiredinteger
active_numbersrequiredinteger
criteriarequiredobject
staterequiredstring | null
area_coderequiredstring | null
containsrequiredstring | null
backorder_idrequiredstring | null
routingrequiredobject | nullWhere the whole order was pointed, when it was pointed as one.
server_idrequiredstring
in_grouprequiredstring | null
created_atrequiredstring
numbersobject[]On GET /v1/orders/{id} only.
objectrequired"phone_number"
idrequiredstring
numberrequiredstringE.164, with the plus.
typerequired"local" | "toll_free"
premiumrequiredbooleanA memorable number with its own price.
statusrequired"active" | "released"
labelrequiredstring | null
notesrequiredstring | null
staterequiredstring | null
area_coderequiredstring | null
rate_centerrequiredstring | null
monthly_price_centsrequiredintegerUS cents.
currencyrequired"usd"
auto_renewrequiredboolean
at_next_renewalrequired"charge" | "release" | nullWhat 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_atrequiredstring
min_term_ends_atrequiredstring
next_renewal_atrequiredstring | null
released_atrequiredstring | null
release_charge_centsrequiredinteger | null
order_idrequiredstring | null
routingrequiredobject | nullNull when it rings nowhere.
server_idrequiredstring
server_hostnamerequiredstring
route_kindrequired"ingroup" | "campaign" | "extension" | "ivr"
targetrequiredstringThe in-group (or other target) calls land on.
staterequired"pending" | "applied" | "failed"Whether the server has taken the change yet.
errorrequiredstring | null
curl https://vicifast.com/api/v1/orders/:id \
  -H "Authorization: Bearer $VICIFAST_KEY"
get/api/v1/backorders

List back-orders

Needs the orders:read scope.

Try it in the playground →

Parameters

status"open" | "filled" | "cancelled" | "expired" · query
limitinteger · queryPage size, up to 500.
cursorstring · querynext_cursor from the previous page.

200 · A page of back-orders.

datarequiredobject[]
objectrequired"backorder"
idrequiredstring
statusrequired"open" | "filled" | "cancelled" | "expired"
typerequired"local" | "toll_free"
quantityrequiredinteger
filledrequiredinteger
allow_nearbyrequiredboolean
price_each_centsrequiredintegerPrepaid per number; the most any will cost.
paid_centsrequiredintegerUS cents.
refunded_centsrequiredintegerUS cents.
linesrequiredobject[]
area_coderequiredstringArea code, or toll-free prefix.
staterequiredstring | null
quantityrequiredinteger
filledrequiredinteger
ring_onrequiredobject | null
server_idrequiredstring
in_grouprequiredstring
order_idrequiredstring | nullThe order the delivered numbers are in, once the first arrives.
created_atrequiredstring
cancelled_atrequiredstring | null
has_morerequiredboolean
next_cursorrequiredstring | null
curl https://vicifast.com/api/v1/backorders \
  -H "Authorization: Bearer $VICIFAST_KEY"
post/api/v1/backorders

Place 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.

Try it in the playground →

Parameters

Idempotency-Keystring · header

Request body

type"local" | "toll_free"Default local.
linesrequiredobject[]
area_coderequiredstringArea code; for toll-free, the prefix (800, 833…).
quantityrequiredinteger
allow_nearbybooleanWhen an area code runs short, a nearby one in the same state will do (any toll-free prefix, on a toll-free order).
ring_onobjectPoint the numbers here as they arrive.
server_idrequiredstring
in_grouprequiredstring
max_total_centsintegerRefuse the order if it would charge more than this now.
dry_runbooleanReturn exactly what the order would charge and place nothing. Needs no Idempotency-Key.

200 · With dry_run: what it would charge. Nothing placed.

datarequiredobject
objectrequired"backorder_quote"
dry_runrequiredtrue
charge_centsrequiredintegerUS cents.
quantityrequiredinteger
price_each_centsrequiredintegerUS cents.
balance_centsrequiredintegerUS cents.

201 · Placed and paid.

datarequiredobject
objectrequired"backorder"
idrequiredstring
statusrequired"open" | "filled" | "cancelled" | "expired"
typerequired"local" | "toll_free"
quantityrequiredinteger
filledrequiredinteger
allow_nearbyrequiredboolean
price_each_centsrequiredintegerPrepaid per number; the most any will cost.
paid_centsrequiredintegerUS cents.
refunded_centsrequiredintegerUS cents.
linesrequiredobject[]
area_coderequiredstringArea code, or toll-free prefix.
staterequiredstring | null
quantityrequiredinteger
filledrequiredinteger
ring_onrequiredobject | null
server_idrequiredstring
in_grouprequiredstring
order_idrequiredstring | nullThe order the delivered numbers are in, once the first arrives.
created_atrequiredstring
cancelled_atrequiredstring | null
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
  }'
get/api/v1/backorders/{id}

Get a back-order

Needs the orders:read scope.

Try it in the playground →

Parameters

idrequiredstring · path

200 · The back-order.

datarequiredobject
objectrequired"backorder"
idrequiredstring
statusrequired"open" | "filled" | "cancelled" | "expired"
typerequired"local" | "toll_free"
quantityrequiredinteger
filledrequiredinteger
allow_nearbyrequiredboolean
price_each_centsrequiredintegerPrepaid per number; the most any will cost.
paid_centsrequiredintegerUS cents.
refunded_centsrequiredintegerUS cents.
linesrequiredobject[]
area_coderequiredstringArea code, or toll-free prefix.
staterequiredstring | null
quantityrequiredinteger
filledrequiredinteger
ring_onrequiredobject | null
server_idrequiredstring
in_grouprequiredstring
order_idrequiredstring | nullThe order the delivered numbers are in, once the first arrives.
created_atrequiredstring
cancelled_atrequiredstring | null
curl https://vicifast.com/api/v1/backorders/:id \
  -H "Authorization: Bearer $VICIFAST_KEY"
post/api/v1/backorders/{id}/cancel

Cancel a back-order

Stops waiting and refunds the numbers that have not arrived. Delivered numbers stay yours.

Needs the orders:write scope.

Try it in the playground →

Parameters

idrequiredstring · path
Idempotency-Keyrequiredstring · headerRequired. 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.

datarequiredobject
objectrequired"backorder"
idrequiredstring
statusrequired"open" | "filled" | "cancelled" | "expired"
typerequired"local" | "toll_free"
quantityrequiredinteger
filledrequiredinteger
allow_nearbyrequiredboolean
price_each_centsrequiredintegerPrepaid per number; the most any will cost.
paid_centsrequiredintegerUS cents.
refunded_centsrequiredintegerUS cents.
linesrequiredobject[]
area_coderequiredstringArea code, or toll-free prefix.
staterequiredstring | null
quantityrequiredinteger
filledrequiredinteger
ring_onrequiredobject | null
server_idrequiredstring
in_grouprequiredstring
order_idrequiredstring | nullThe order the delivered numbers are in, once the first arrives.
created_atrequiredstring
cancelled_atrequiredstring | null
refunded_now_centsrequiredinteger
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.

get/api/v1/calls

List calls

Outbound calls you made and inbound calls to your numbers, newest first, from the last 30 days at most.

Needs the calls:read scope.

Try it in the playground →

Parameters

direction"inbound" | "outbound" · query
numberstring · queryInbound calls to this one of your numbers.
answered"true" | "false" · query
window"1h" | "24h" | "7d" | "30d" · queryDefault 7d.
limitinteger · queryPage size, up to 500.
cursorstring · querynext_cursor from the previous page.

200 · A page of calls.

datarequiredobject[]
objectrequired"call"
idrequiredstringThe SIP call id.
directionrequired"inbound" | "outbound"
fromrequiredstringThe caller, E.164 when it is a North American number.
torequiredstring
numberrequiredstring | nullInbound: which of your numbers it arrived on.
started_atrequiredstring
answered_atrequiredstring | null
ended_atrequiredstring | null
answeredrequiredboolean
duration_srequiredinteger
billable_srequiredinteger
sip_coderequiredinteger | null
hangup_causerequiredstring
charge_nanosrequiredintegerWhat the call cost, in billionths of a dollar (2000000 = $0.002).
recordingrequiredbooleanA recording can be fetched with GET /v1/calls/{id}/recording.
has_morerequiredboolean
next_cursorrequiredstring | null
curl https://vicifast.com/api/v1/calls?direction=inbound&window=7d \
  -H "Authorization: Bearer $VICIFAST_KEY"
get/api/v1/calls/{id}

Get a call

Needs the calls:read scope.

Try it in the playground →

Parameters

idrequiredstring · path

200 · The call.

datarequiredobject
objectrequired"call"
idrequiredstringThe SIP call id.
directionrequired"inbound" | "outbound"
fromrequiredstringThe caller, E.164 when it is a North American number.
torequiredstring
numberrequiredstring | nullInbound: which of your numbers it arrived on.
started_atrequiredstring
answered_atrequiredstring | null
ended_atrequiredstring | null
answeredrequiredboolean
duration_srequiredinteger
billable_srequiredinteger
sip_coderequiredinteger | null
hangup_causerequiredstring
charge_nanosrequiredintegerWhat the call cost, in billionths of a dollar (2000000 = $0.002).
recordingrequiredbooleanA recording can be fetched with GET /v1/calls/{id}/recording.
curl https://vicifast.com/api/v1/calls/:id \
  -H "Authorization: Bearer $VICIFAST_KEY"

Billing

The wallet.

get/api/v1/billing/balance

Get the wallet balance

Needs the billing:read scope.

Try it in the playground →

200 · The wallet.

datarequiredobject
objectrequired"balance"
balance_centsrequiredintegerUS cents.
currencyrequired"usd"
credit_limit_centsrequiredintegerHow far below zero renewals may take the wallet so numbers keep ringing. Purchases need the funds.
low_balance_alertrequiredobject
enabledrequiredboolean
threshold_centsrequiredinteger | null
auto_top_uprequiredobject
enabledrequiredboolean
curl https://vicifast.com/api/v1/billing/balance \
  -H "Authorization: Bearer $VICIFAST_KEY"
get/api/v1/billing/transactions

List wallet transactions

Needs the billing:read scope.

Try it in the playground →

Parameters

typestring · queryOne type or several, comma-separated: charge_did_purchase,topup_stripe
direction"credit" | "debit" · query
fromstring · query
tostring · query
limitinteger · queryPage size, up to 500.
cursorstring · querynext_cursor from the previous page.

200 · A page of transactions, newest first.

datarequiredobject[]
objectrequired"transaction"
idrequiredstring
typerequiredstring
amount_centsrequiredintegerSigned: positive credits the wallet.
balance_after_centsrequiredintegerUS cents.
descriptionrequiredstring | null
reference_idrequiredstring | null
created_atrequiredstring
has_morerequiredboolean
next_cursorrequiredstring | null
curl https://vicifast.com/api/v1/billing/transactions \
  -H "Authorization: Bearer $VICIFAST_KEY"
get/api/v1/billing/renewals

List upcoming renewals

What the wallet will be charged, and when: your numbers and wallet-billed servers.

Needs the billing:read scope.

Try it in the playground →

Parameters

daysinteger · queryDefault 30.

200 · Upcoming renewals, soonest first.

datarequiredobject[]
objectrequired"renewal"
kindrequired"number" | "server"
descriptionrequiredstringThe number, or the server hostname.
due_atrequiredstring
actionrequired"charge" | "release"release: a number with auto-renew off, past its minimum term, ends that day.
amount_centsrequiredintegerUS cents.
has_morerequiredboolean
next_cursorrequiredstring | null
curl https://vicifast.com/api/v1/billing/renewals \
  -H "Authorization: Bearer $VICIFAST_KEY"

Webhook endpoints

Where events are sent, their signing secrets, and what was delivered.

get/api/v1/webhook-endpoints

List webhook endpoints

This key's mode only: a live key sees live endpoints, a test key test ones.

Needs the webhooks:read scope.

Try it in the playground →

200 · Every endpoint, oldest first.

datarequiredobject[]
objectrequired"webhook_endpoint"
idrequiredstring
urlrequiredstring
descriptionrequiredstring | 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")[]
enabledrequiredbooleanfalse: nothing is sent to it.
livemoderequiredbooleantrue: your account's real events. false: the sandbox's, from test keys. Follows the key that created it.
wallet_low_threshold_centsrequiredinteger | nullwallet.low fires once when the balance drops below this, and again only after it has been back above.
last_statusrequiredinteger | nullHTTP status of the last attempt.
last_errorrequiredstring | null
last_sent_atrequiredstring | null
failing_sincerequiredstring | nullSet while deliveries are failing; cleared by the next one that succeeds.
created_atrequiredstring
has_morerequiredboolean
next_cursorrequiredstring | null
curl https://vicifast.com/api/v1/webhook-endpoints \
  -H "Authorization: Bearer $VICIFAST_KEY"
post/api/v1/webhook-endpoints

Add 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.

Try it in the playground →

Request body

urlrequiredstringhttps only, and on the public internet: private and loopback addresses are refused.
descriptionstring
eventsrequired("numbers.arrived" | "number.released" | "number.renewal_failed" | "call.inbound.completed" | "call.outbound.completed" | "wallet.low" | "backorder.filled" | "number.routing_failed" | "wallet.topped_up")[]
wallet_low_threshold_centsintegerRequired with wallet.low.

201 · Added.

datarequiredobject
objectrequired"webhook_endpoint"
idrequiredstring
urlrequiredstring
descriptionrequiredstring | 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")[]
enabledrequiredbooleanfalse: nothing is sent to it.
livemoderequiredbooleantrue: your account's real events. false: the sandbox's, from test keys. Follows the key that created it.
wallet_low_threshold_centsrequiredinteger | nullwallet.low fires once when the balance drops below this, and again only after it has been back above.
last_statusrequiredinteger | nullHTTP status of the last attempt.
last_errorrequiredstring | null
last_sent_atrequiredstring | null
failing_sincerequiredstring | nullSet while deliveries are failing; cleared by the next one that succeeds.
created_atrequiredstring
secretrequiredstringSigns every delivery (X-VICIfast-Signature). Shown when the endpoint is made and when the secret is rotated; keep it.
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
  }'
get/api/v1/webhook-endpoints/{id}

Get a webhook endpoint

Needs the webhooks:read scope.

Try it in the playground →

Parameters

idrequiredstring · path

200 · The endpoint.

datarequiredobject
objectrequired"webhook_endpoint"
idrequiredstring
urlrequiredstring
descriptionrequiredstring | 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")[]
enabledrequiredbooleanfalse: nothing is sent to it.
livemoderequiredbooleantrue: your account's real events. false: the sandbox's, from test keys. Follows the key that created it.
wallet_low_threshold_centsrequiredinteger | nullwallet.low fires once when the balance drops below this, and again only after it has been back above.
last_statusrequiredinteger | nullHTTP status of the last attempt.
last_errorrequiredstring | null
last_sent_atrequiredstring | null
failing_sincerequiredstring | nullSet while deliveries are failing; cleared by the next one that succeeds.
created_atrequiredstring
curl https://vicifast.com/api/v1/webhook-endpoints/:id \
  -H "Authorization: Bearer $VICIFAST_KEY"
patch/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.

Try it in the playground →

Parameters

idrequiredstring · path

Request body

urlstringhttps only, and on the public internet: private and loopback addresses are refused.
descriptionstring | 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")[]Replaces the list.
enabledboolean
wallet_low_threshold_centsinteger | null

200 · Changed.

datarequiredobject
objectrequired"webhook_endpoint"
idrequiredstring
urlrequiredstring
descriptionrequiredstring | 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")[]
enabledrequiredbooleanfalse: nothing is sent to it.
livemoderequiredbooleantrue: your account's real events. false: the sandbox's, from test keys. Follows the key that created it.
wallet_low_threshold_centsrequiredinteger | nullwallet.low fires once when the balance drops below this, and again only after it has been back above.
last_statusrequiredinteger | nullHTTP status of the last attempt.
last_errorrequiredstring | null
last_sent_atrequiredstring | null
failing_sincerequiredstring | nullSet while deliveries are failing; cleared by the next one that succeeds.
created_atrequiredstring
curl -X PATCH https://vicifast.com/api/v1/webhook-endpoints/:id \
  -H "Authorization: Bearer $VICIFAST_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": false }'
delete/api/v1/webhook-endpoints/{id}

Remove a webhook endpoint

Its undelivered events are dropped with it.

Needs the webhooks:write scope.

Try it in the playground →

Parameters

idrequiredstring · path

200 · Removed.

datarequiredobject
objectrequired"webhook_endpoint"
idrequiredstring
deletedrequiredtrue
curl -X DELETE https://vicifast.com/api/v1/webhook-endpoints/:id \
  -H "Authorization: Bearer $VICIFAST_KEY"
post/api/v1/webhook-endpoints/{id}/rotate-secret

Rotate 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.

Try it in the playground →

Parameters

idrequiredstring · path

200 · The new secret.

datarequiredobject
objectrequired"webhook_secret"
endpoint_idrequiredstring
secretrequiredstringThe new secret. The old one stops working from the next delivery.
curl -X POST https://vicifast.com/api/v1/webhook-endpoints/:id/rotate-secret \
  -H "Authorization: Bearer $VICIFAST_KEY"
post/api/v1/webhook-endpoints/{id}/test

Send 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.

Try it in the playground →

Parameters

idrequiredstring · 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"Which event to send a sample of.

200 · Sent; delivered says whether it arrived.

datarequiredobject
objectrequired"webhook_test"
event_idrequiredstring
delivery_idrequiredstring
deliveredrequiredbooleanWhether your endpoint answered 2xx.
statusrequiredinteger | null
errorrequiredstring | null
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" }'
get/api/v1/webhook-endpoints/{id}/deliveries

List deliveries to an endpoint

Newest first: what was sent, what came back, and what is still being retried.

Needs the webhooks:read scope.

Try it in the playground →

Parameters

idrequiredstring · path
state"pending" | "retrying" | "delivered" | "gave_up" · query
limitinteger · queryPage size, up to 500.
cursorstring · querynext_cursor from the previous page.

200 · A page of deliveries.

datarequiredobject[]
objectrequired"webhook_delivery"
idrequiredstringSent as X-VICIfast-Delivery.
endpoint_idrequiredstring
event_idrequiredstring
event_typerequiredstring
testrequiredbooleanA 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).
attemptsrequiredinteger
last_statusrequiredinteger | null
last_errorrequiredstring | null
created_atrequiredstring
delivered_atrequiredstring | null
next_attempt_atrequiredstring | null
has_morerequiredboolean
next_cursorrequiredstring | 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.

get/api/v1/events

List 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.

Try it in the playground →

Parameters

typestring · queryOne type or several, comma-separated: numbers.arrived,number.released
fromstring · query
tostring · query
limitinteger · queryPage size, up to 500.
cursorstring · querynext_cursor from the previous page.

200 · A page of events.

datarequiredobject[]
idrequiredstring
objectrequired"event"
typerequiredstring
livemoderequiredboolean
created_atrequiredstring
datarequiredobjectShaped by the type; see Webhooks below. Samples sent with "send test" have test: true.
has_morerequiredboolean
next_cursorrequiredstring | null
curl https://vicifast.com/api/v1/events?type=numbers.arrived,number.released&limit=50 \
  -H "Authorization: Bearer $VICIFAST_KEY"
get/api/v1/events/{id}

Get an event, with its deliveries

Needs the webhooks:read scope.

Try it in the playground →

Parameters

idrequiredstring · path

200 · The event.

datarequiredobject
idrequiredstring
objectrequired"event"
typerequiredstring
livemoderequiredboolean
created_atrequiredstring
datarequiredobjectShaped by the type; see Webhooks below. Samples sent with "send test" have test: true.
deliveriesrequiredobject[]
objectrequired"webhook_delivery"
idrequiredstringSent as X-VICIfast-Delivery.
endpoint_idrequiredstring
event_idrequiredstring
event_typerequiredstring
testrequiredbooleanA 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).
attemptsrequiredinteger
last_statusrequiredinteger | null
last_errorrequiredstring | null
created_atrequiredstring
delivered_atrequiredstring | null
next_attempt_atrequiredstring | null
curl https://vicifast.com/api/v1/events/:id \
  -H "Authorization: Bearer $VICIFAST_KEY"
post/api/v1/events/{id}/resend

Send 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.

Try it in the playground →

Parameters

idrequiredstring · path

Request body

endpoint_idstringOnly 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.

datarequiredobject
objectrequired"event_resend"
event_idrequiredstring
deliveriesrequiredobject[]
objectrequired"webhook_delivery"
idrequiredstringSent as X-VICIfast-Delivery.
endpoint_idrequiredstring
event_idrequiredstring
event_typerequiredstring
testrequiredbooleanA 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).
attemptsrequiredinteger
last_statusrequiredinteger | null
last_errorrequiredstring | null
created_atrequiredstring
delivered_atrequiredstring | null
next_attempt_atrequiredstring | null
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_…" }'

Webhooks

Add endpoints in API → Webhooks in the dashboard — 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. X-VICIfast-Delivery stays the same across retries of one event, so you can skip one you have already handled.

{
  "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.arrived

Numbers are yours: bought, delivered against a back-order, or assigned.

data

sourcerequired"purchase" | "backorder" | "assigned"
order_idrequiredstring | null
backorder_idrequiredstring | null
numbersrequiredstring[]
number.released

A number left your account.

data

numberrequiredstringE.164, with the plus.
reasonrequired"customer" | "auto_renew_off" | "account"
charge_centsrequiredinteger
released_atrequiredstring
number.renewal_failed

A renewal could not be paid (sent once per number per billing month; the charge is retried every hour).

data

numberrequiredstringE.164, with the plus.
amount_centsrequiredinteger
due_atrequiredstring
reasonrequired"insufficient_funds" | "error"
call.inbound.completed

An inbound call to one of your numbers finished.

data

objectrequired"call"
idrequiredstringThe SIP call id.
directionrequired"inbound" | "outbound"
fromrequiredstringThe caller, E.164 when it is a North American number.
torequiredstring
numberrequiredstring | nullInbound: which of your numbers it arrived on.
started_atrequiredstring
answered_atrequiredstring | null
ended_atrequiredstring | null
answeredrequiredboolean
duration_srequiredinteger
billable_srequiredinteger
sip_coderequiredinteger | null
hangup_causerequiredstring
charge_nanosrequiredintegerWhat the call cost, in billionths of a dollar (2000000 = $0.002).
recordingrequiredbooleanA recording can be fetched with GET /v1/calls/{id}/recording.
call.outbound.completed

An outbound call you made finished, answered or not. A dialer makes many; each is one delivery..

data

objectrequired"call"
idrequiredstringThe SIP call id.
directionrequired"inbound" | "outbound"
fromrequiredstringThe caller, E.164 when it is a North American number.
torequiredstring
numberrequiredstring | nullInbound: which of your numbers it arrived on.
started_atrequiredstring
answered_atrequiredstring | null
ended_atrequiredstring | null
answeredrequiredboolean
duration_srequiredinteger
billable_srequiredinteger
sip_coderequiredinteger | null
hangup_causerequiredstring
charge_nanosrequiredintegerWhat the call cost, in billionths of a dollar (2000000 = $0.002).
recordingrequiredbooleanA recording can be fetched with GET /v1/calls/{id}/recording.
wallet.low

The wallet dropped below the endpoint's threshold.

data

objectrequired"balance"
balance_centsrequiredinteger
threshold_centsrequiredinteger
currencyrequired"usd"
backorder.filled

Every number a back-order asked for has arrived (sent once, after the numbers.arrived for the last delivery).

data

backorder_idrequiredstring
order_idrequiredstringThe order the numbers were delivered into.
quantityrequiredinteger
refunded_centsrequiredintegerRefunded in all because numbers cost less than the price paid up front.
filled_atrequiredstring
number.routing_failed

A number could not be pointed at your server, so it will not ring there yet. Point it again (PATCH the number) once the cause is fixed..

data

numberrequiredstringE.164, with the plus.
server_idrequiredstring
route_kindrequiredstring
targetrequiredstringThe 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?).
errorrequiredstring | null
wallet.topped_up

Money was added to the wallet: by card, PayPal, crypto, or by us.

data

objectrequired"transaction"
idrequiredstring
typerequiredstring
amount_centsrequiredintegerSigned: positive credits the wallet.
balance_after_centsrequiredintegerUS cents.
descriptionrequiredstring | null
reference_idrequiredstring | null
created_atrequiredstring