Migrate from Telserv to Bird
This guide is for integrations that call the Telserv partner API, V1 or V2, to buy, route, and release numbers, and that now move to the Bird API.
The Bird API serves every Bird product, so its numbers and voice operations follow Bird's own conventions instead of Telserv's. Most capabilities your integration uses have a Bird call. The endpoints, payloads, identifiers, and authentication differ, so plan to rewrite the integration. Where Bird does a job differently, this guide shows how.
This guide is a work in progress. It covers what Bird does today, and it grows as the migration does. If a Telserv operation you use is not listed here, it still needs scoping and work before it can be added; ask your migration contact about it.
Before you start
- A Bird organization with completed identity verification, which the first number purchase requires.
- An API key with the scopes the recipes use:
numbersat write level for numbers and orders,complianceat write level for the standalone ownership-paperwork operations, andvoice_managementat write level for SIP trunks, voice numbers, and verified numbers. The test call also needsvoice:write, and reading call legs needsvoice:read. - Wallet balance. Orders are paid from the wallet. When an order is refused explains the
402and412refusals.
The numbers your integration manages in Telserv do not need to be bought again. Once your numbers are moved to your Bird workspace, they appear in GET /v1/numbers. Your migration contact confirms when that happens. To get the nda_ identifier of a moved number, look it up with GET /v1/numbers?number=<digits>, giving the digits with their country code, and keep the id as NUMBER_ID.
The examples read your key from BIRD_API_KEY and its regional host from BIRD_API_URL: https://eu1.platform.bird.com for a bk_eu1_ key, or https://us1.platform.bird.com for a bk_us1_ key. The other variables, such as TRUNK_ID and NUMBER_ID, hold an identifier from an earlier response; each step names the one it sets.
What changes in the contract
| Area | Telserv partner API | Bird API |
|---|---|---|
| Authentication | X-ApiKey header (V2) or apiKey parameter (V1), checked against your IP allowlist | Authorization: Bearer with an API key whose scopes name the product areas it may use. An IP allowlist is optional: set it under IP restrictions on the key in API keys |
| Identifiers | Integers. Buying a number gives it a new id | Prefixed TypeIDs such as nda_, nor_, vnu_, and spt_. A number keeps its nda_ id while it is allocated to you |
| Phone numbers | Digits without a leading + | E.164 with the leading + |
| Pagination | start and limit offsets, up to 1000 per page | Cursors: limit up to 100 (default 25), then starting_after set to the previous page's next_cursor. See Pagination |
| Errors | errorCode names an exception class, and 403 covers a missing resource and many validation failures | error.code is a stable E code with a documentation page and, where a recovery is known, a remediation. A missing resource answers 404, an unmet precondition 412, and invalid input 422 with field details. See Errors |
| Environments | LIVE and SANDBOX, each with its own keys | One environment. Each order buys and charges for a real number, and each test call is a real call |
| Retries | No idempotency key | An Idempotency-Key header on writes. The same key and body replay the first response with Idempotency-Replay: true; the same key with a different body answers 409. See Idempotency |
The Bird API refuses a query parameter it does not recognize with 422 and E01029. Remove Telserv-only filters such as city from your requests instead of passing them through.
Two identifiers for one number
A voice-capable number allocated to your workspace has two records, each with its own identifier:
- The allocation, returned by
GET /v1/numbers. Itsidstarts withnda_for a dedicated number andnal_for a shared one, and an order'snumber_idis thenda_identifier of the number it bought. Use it to read, name, and reference the number, and to release a dedicated one. - The voice record,
vnu_, returned byGET /v1/voice/numbers. Use it to read and change where the number's incoming calls go. A number without voice capability, such as an SMS-only number, has no voice record.
The voice operations refuse an nda_ or nal_ identifier with 422. To find a number's voice record, list voice numbers with search set to the number's digits and take the entry whose provider.number_id equals the allocation's id. Route a number to your SIP trunk shows the request, and Managing your phone numbers covers the lookup.
Telserv's GET /numbers/{dnisId} becomes two reads in Bird: the allocation for status and ownership paperwork, and the voice record for the route.
Map Telserv operations to Bird
Each table lists a job, the Telserv call that did it, and the Bird call that does it now. The notes under each table cover what changes in the request or the response.
Shop
| Job | Telserv | Bird |
|---|---|---|
| Search numbers on sale | GET /shop | GET /v1/numbers/available |
| Check one number | GET /shop/{stockDnisId} | GET /v1/numbers/available/{number} |
| Buy a number | POST /shop/{stockDnisId} | POST /v1/numbers/orders |
| Secure a number | POST /shop/{stockDnisId}/reserve, GET /shop/reserved | POST /v1/numbers/orders |
- Search.
country_codeis required, so a search covers one country.prefixtakes the national digits after the country code: Telservprefix=3170becomescountry_code=NL&prefix=70. Narrow by area code withprefixin place ofcity, and bynumber_typeandcapabilitiesin place ofproductId. - Paperwork in search results. Each result carries
ownership_registration_requiredin place of theexcludeNumbersRequireVerificationfilter. Atruevalue means the number is billed from purchase but sends no SMS and carries no inbound or outbound voice calls until its paperwork is approved. - Check one number. It is addressed by its E.164 number.
- Prices. Telserv returned
pricing.staticPricingon the detailed reads of a number and of a product. In Bird, each search result carries its price for your organization inpricing:setup_pricein place ofnrc, andrecurring_priceperrecurring_periodin place ofmrc, withintervals_per_chargefor how many periods one charge bills. Search and buy a number with your reference describes the price fields. - Buy. Send
numberand an optionalreference. The order is paid from your wallet. Most orders answer201; an order that waits on a carrier answers202, and you poll it. - Secure. Ordering the number secures it. Check a candidate first, then order it.
Numbers
| Job | Telserv | Bird |
|---|---|---|
| List your numbers | GET /numbers | GET /v1/numbers |
| Read one number | GET /numbers/{dnisId} | GET /v1/numbers/{number_id} with the allocation's nda_ or nal_ id, and GET /v1/voice/numbers/{number_id} with the voice record's vnu_ id |
| Change a number | PATCH /numbers/{dnisId} | PATCH /v1/numbers/{number_id} with the allocation's nda_ or nal_ id, and PATCH /v1/voice/numbers/{number_id} with the voice record's vnu_ id |
| Release a number | DELETE /numbers/{dnisId}?requestedCancelDate= | DELETE /v1/numbers/{number_id} at the end of the paid period, or POST /v1/numbers/{number_id}/release now, both with the allocation's nda_ id |
- List. The list holds the numbers allocated to you now. A released number you ordered in Bird stays visible through its order in
GET /v1/numbers/orders. Reconcile your numbers maps the filters. - Read. Status, ownership paperwork, and the price are on the allocation, and the route is on the voice record. The allocation's
pricingobject, withrecurring_priceperrecurring_periodandintervals_per_charge, takes the place ofpricing.staticPricing.mrc, and the list carries it too, where Telserv'sGET /numbersdid not. - Change.
nameandreferenceare on the allocation, and the route is on the voice record. Set a forced codec and a cap on concurrent calls on your SIP equipment.destinationDistributionTypebecomes gatewaypriorityon the SIP trunk:FailOvergives each gateway a differentpriority, andRoundRobingives them the same one. - Release.
DELETEkeeps the number until the end of the period you already paid for and releases it then.POST …/releasereleases it at once. Neither takes a date. Release a number shows both requests.
Destinations and profiles
| Job | Telserv | Bird |
|---|---|---|
| Send calls to SIP equipment | POST /numbers/{dnisId}/destinations | POST /v1/voice/trunks/{trunk_id}/gateways, then route trunk |
| Forward calls to a phone | POST /numbers/{dnisId}/destinations | Route forward |
| Stop taking calls | DELETE /numbers/{dnisId}/destinations | Route reject |
| Read destination profiles | GET /destinationprofiles, GET /destinationprofiles/{id} | GET /v1/voice/trunks and GET /v1/voice/trunks/{trunk_id}/gateways |
| Apply a profile to a number | POST /numbers/{dnisId}/destinations/profile | Route trunk with trunk_id |
- SIP equipment. A gateway belongs to the trunk and serves each number routed to it, so numbers that need different destination lists need different trunks.
sortOrderbecomespriority. Apply ring time and time windows on your SIP equipment. - Forward. Set
forward_toandforward_as.forward_tomust be one of your verified numbers; an unverified number answers412withE21053.forward_aschooses which number the forwarded call shows as its caller. A number has one route at a time, so to fall back from SIP equipment to a phone number, forward the call from your SIP equipment. - Stop taking calls. The number rejects calls until you route it again. To keep a standing default in place of a default profile, route the number to a trunk that holds your default gateways.
- Profiles.
distributionTypebecomespriority:FailOvergives each gateway a differentpriority, andRoundRobingives them the same one. Distribution is set per trunk, so numbers that need different distribution need different trunks. The{dnis}placeholder becomes{number}indestination_format, and aprefixCLIPbecomes the text before{number}inorigination_format. - Apply a profile. A trunk is addressed by its
spt_identifier, where Telserv addressed a profile by name.
Documents and end-user data
| Job | Telserv | Bird |
|---|---|---|
| Read the requirements | GET /products/conditions, GET /masterdata/productconditions | GET /v1/compliance/requirements for the number, or with program and no subject_id for the form a country asks for before you hold a number |
| Upload a document | POST /numbers/{dnisId}/documents | POST /v1/compliance/attachments, then cite the attachment in the submission. See File the ownership paperwork |
| Send end-user data | POST /numbers/{dnisId}/enduserdata | POST /v1/compliance/submissions, with the documents, in one request. See File the ownership paperwork |
| Replace a document | POST /numbers/{dnisId}/documents again | POST /v1/compliance/submissions/{submission_id}/resupply with the rejected or expired answers |
| Check paperwork progress | GET /numbers/{dnisId}/documents | ownership on GET /v1/numbers/{number_id}, and the submission for each answer's verdict |
- Requirements. Telserv's conditions catalogue covered every product at once; Bird reads one form at a time. Ask for one number's requirements with
subject_kind=number, itsnda_identifier assubject_id, and itscountry_code, or read a form before you buy withprogramset to the number type and nosubject_id. Each entry infieldstakes the place of a Telserv condition or end-user label: itskeyis what you answer under, anddata_typedocumentmarks one that takes a file. - Documents. Upload the file as
multipart/form-datain place of a base64fileContent; Upload each document lists the accepted files. Uploading the same file twice returns the same attachment. An upload alone files nothing: the submission that cites itsattachment_iddoes. - End-user data and documents together. Telserv took each document in its own call, and the end-user data as one list of labels in another. Bird takes every answer, typed and document, in one submission, and there are no drafts: the request files the answers for review. Then follow
ownership.next, which names resupply for corrections and a new submission only when one is needed (Submit every answer). - Replacements. Send only the answers you replace, and the others keep their review. Telserv took a new file for a condition while the number was pending verification, and the file reset that condition's review; Bird replaces any answer you change until the submission is approved, including one still under review or already accepted, and sends it back to review. After approval it replaces only an answer that is rejected, expired, or not yet supplied, or one marked
editable_for_verifier_repairwhile a correction we requested is open (Correct a rejected answer). - Progress.
ownership.statusandownership.nextreport where the number stands. Each answer has its ownstate, and a rejected one carries the reviewer'sreason. Bird also emails each result to your organization's owners and to the person who last filed or corrected the submission.ownershipis usuallynullwhen the number needs no paperwork.
File the ownership paperwork shows the requests, and File a number's ownership paperwork covers the whole flow, including filing from the dashboard and with the order.
Products and master data
| Job | Telserv | Bird |
|---|---|---|
| Browse products | GET /products, GET /products/{productId} | GET /v1/numbers/available, which requires country_code and returns the numbers on sale in that one country. Bird has no global product catalogue, and nothing corresponds to productId. No API lists the countries and number types on sale; the phone number pricing page lists Bird's standard prices by country. |
Each search result describes its product with number_type, capabilities, and ownership_registration_required, and carries the price for your organization in place of the product's pricing.staticPricing (nrc and mrc). Bird's standard prices are on the phone number pricing page. Telserv's per-minute sessionPricing has no counterpart on these reads; voice rates are on the voice pricing page.
Telserv number types map onto Bird's number_type like this:
Telserv numberType | Bird number_type |
|---|---|
DID | local or national, depending on the number |
FreePhone | toll_free |
Mobile | mobile |
What works differently today
These Telserv jobs are done differently in Bird. Plan for them before you move an integration that depends on one.
- Paperwork. File a number's documents and end-user data in one submission, with no drafts, and correct rejected answers with resupply.
- Reservations. Order the number when you have chosen it; the order secures it.
- Per-number call settings. Apply a forced codec, a cap on concurrent calls, ring time, and time windows on your SIP equipment.
- A release date. Bird releases a number at the end of its paid period or at once, not on a date you choose. Keep the cancellation dates you sent to Telserv on your side, and act on each one when it comes due.
- Wallet balance. Check the balance and top up in the dashboard.
Search and buy a number with your reference
Search one country. Combine number_type, capabilities, and prefix to narrow the results:
curl "$BIRD_API_URL/v1/numbers/available?country_code=NL&number_type=mobile&capabilities=voice&limit=10" \
--header "Authorization: Bearer $BIRD_API_KEY"Each result carries a pricing object with the price your organization pays for the number, net of tax and in your billing currency, with any prices you agreed with Bird applied:
setup_priceis charged once when you order the number. A published fee of zero has anamountof"0". The field itself isnullwhen no setup fee applies, and also wheneverrecurring_priceisnull, because that number cannot be ordered.recurring_priceis the rate perrecurring_period.intervals_per_chargeis how manyrecurring_periods one charge bills. The recurring charge before tax isrecurring_pricemultiplied byintervals_per_charge, taken when the number is assigned to your workspace and at each renewal; the setup fee is separate. For an order that waits on a carrier, assignment happens when the order completes.
The API reference shows an example result. A result with pricing.recurring_price: null does not quote a recurring price. Contact sales to have that country and number type priced for you before ordering.
The dashboard and API use the same organization price, including conversion to your billing currency. A price read does not lock a quote: tariff or exchange-rate changes before a later charge can change its amount. If the lookup fails, the search returns 503 with E14009. Retry the read with exponential backoff; a lookup failure never means the number costs nothing.
To confirm a candidate and its price before you order it, read it with GET /v1/numbers/available/{number}. A 404 means it is no longer for sale.
Order the number with the reference you used in Telserv, and choose an idempotency key you keep for any retry of this purchase:
curl --request POST "$BIRD_API_URL/v1/numbers/orders" \
--header "Authorization: Bearer $BIRD_API_KEY" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: order-CUST-778812" \
--data '{"number": "+31612345678", "reference": "CUST-778812"}'A 201 response has status: completed and a number_id, the number's nda_ identifier. Keep it as NUMBER_ID. A 202 response means the order waits on a carrier: poll the order until its status is completed or failed. A setup fee already charged is not refunded if the order then fails. The reference is set on the number when the order completes. It holds up to 100 characters and does not need to be unique.
Retrying an order for a number your workspace has allocated returns the existing order, so a retry does not charge twice. Buy and release a number explains the refusals.
To set or change a reference later, send reference (and optionally name) to PATCH /v1/numbers/{number_id}. Send null to clear a field.
Check that a number can carry calls
A number whose status is pending_ownership_registration is allocated and billed, but it sends no SMS and rejects incoming and outgoing voice calls until its ownership paperwork is approved. Before you route a number or place a test call to it, read it with GET /v1/numbers/{number_id} and check that status is active. If it is pending, file the ownership paperwork. The status of an allocated number explains each value.
The examples below use a Dutch mobile number. Check the status of your own number in the same way, whatever its country.
File the ownership paperwork
Telserv took a number's paperwork with one POST /numbers/{dnisId}/documents per document and one POST /numbers/{dnisId}/enduserdata for all the labels. Bird takes both in one submission, after you upload the documents.
-
Read the number's
country_codeand keep it asCOUNTRY_CODE. The command extracts it withjq; read it by hand if you do not usejq:Code exampleCOUNTRY_CODE=$(curl -s "$BIRD_API_URL/v1/numbers/$NUMBER_ID" \ --header "Authorization: Bearer $BIRD_API_KEY" | jq -r .country_code) -
Read what the number's country asks for. Each entry in
fieldshas thekeyyou answer under, adata_type, andrequired, which says whether you must answer it. Keep theprogramthe response names asPROGRAM:Code examplecurl "$BIRD_API_URL/v1/compliance/requirements?subject_kind=number&subject_id=$NUMBER_ID&country_code=$COUNTRY_CODE" \ --header "Authorization: Bearer $BIRD_API_KEY"A
422withE19001means there is no ownership form for that country. -
Upload each document a
documentfield asks for, as a file in place of base64fileContent. Keep theidfrom the response, the attachment'stca_identifier, asATTACHMENT_ID:Code examplecurl --request POST "$BIRD_API_URL/v1/compliance/attachments" \ --header "Authorization: Bearer $BIRD_API_KEY" \ --form "file=@registration-certificate.pdf" -
Submit every answer at once: one entry for every field whose
requiredistrue, and for each optional field you have a real answer for, with the end-user data asvalueand each document asattachment_id. Leave an optional field out rather than invent a value for it. The example shows two answers; add the rest of the required ones before you send it. This request files the answers for review. Keep theidfrom the response, the submission'scsb_identifier, asSUBMISSION_ID:Code examplecurl --request POST "$BIRD_API_URL/v1/compliance/submissions" \ --header "Authorization: Bearer $BIRD_API_KEY" \ --header "Content-Type: application/json" \ --header "Idempotency-Key: ownership-CUST-778812" \ --data '{"subject_kind": "number", "subject_id": "'"$NUMBER_ID"'", "program": "'"$PROGRAM"'", "country_code": "'"$COUNTRY_CODE"'", "answers": [{"key": "business_registration_number", "value": "90012768"}, {"key": "business_registration_certificate", "attachment_id": "'"$ATTACHMENT_ID"'"}]}'A
201response returns the submission. A422files nothing: correct the body and send it under a new idempotency key, because the key replays the refusal. Submit every answer explains each refusal. -
When a reviewer rejects an answer, replace only that answer, as Correct a rejected answer describes:
Code examplecurl --request POST "$BIRD_API_URL/v1/compliance/submissions/$SUBMISSION_ID/resupply" \ --header "Authorization: Bearer $BIRD_API_KEY" \ --header "Content-Type: application/json" \ --header "Idempotency-Key: ownership-fix-1-CUST-778812" \ --data '{"answers": [{"key": "business_registration_number", "value": "90012769"}]}'
To file while you buy, send the same answers in the compliance block of POST /v1/numbers/orders. That order uses the existing numbers scope at write level, including when it carries paperwork.
Follow the review with ownership.status on GET /v1/numbers/{number_id}. The answers are a legal representation made in your business's name, so file only answers someone responsible for them has confirmed. File a number's ownership paperwork covers each request, the review, and filing with the order.
Route a number to your SIP trunk
A new number has no route of its own, so it follows your workspace's default route. That default starts as reject, but a workspace that already set one answers a new number's calls by it as soon as the number is allocated. To deliver its calls to your equipment, create a SIP trunk with inbound calling on, give it a gateway, and point the number at the trunk. SIP trunks covers connecting your equipment to the trunk. If you configure the trunk in the dashboard instead, follow Set up a SIP trunk: a gateway added there rings only when it sits in a group that a rule or the trunk's default route sends calls to. Once that routing is saved, a gateway you add over the API also rings only after you put it in a group.
-
Create the trunk with inbound calling on. Keep the
idfrom the response, the trunk'sspt_identifier, asTRUNK_ID:Code examplecurl --request POST "$BIRD_API_URL/v1/voice/trunks" \ --header "Authorization: Bearer $BIRD_API_KEY" \ --header "Content-Type: application/json" \ --header "Idempotency-Key: trunk-production-pbx" \ --data '{"name": "Production PBX", "inbound_enabled": true}' -
Add a gateway. By default the gateway receives the dialed and calling numbers in E.164 with their
+. If your equipment expects numbers without the+, as Telserv's{dnis}placeholder did, setdestination_formatfor the dialed number andorigination_formatfor the calling number to{number}:Code examplecurl --request POST "$BIRD_API_URL/v1/voice/trunks/$TRUNK_ID/gateways" \ --header "Authorization: Bearer $BIRD_API_KEY" \ --header "Content-Type: application/json" \ --header "Idempotency-Key: gateway-pbx-primary" \ --data '{"sip_uri": "sip:pbx.example.com:5060", "priority": 0, "destination_format": "{number}", "origination_format": "{number}"}'For failover, add a second gateway; What a gateway needs explains
priorityand the number formats. Creating the same gateway again without an idempotency key answers409withE21047. -
Find the number's voice record. Search with the number's digits and take the
idof the entry whoseprovider.number_idis the allocation'sid. Keep it asVOICE_NUMBER_ID:Code examplecurl "$BIRD_API_URL/v1/voice/numbers?search=31612345678" \ --header "Authorization: Bearer $BIRD_API_KEY" -
Point the number at the trunk:
Code examplecurl --request PATCH "$BIRD_API_URL/v1/voice/numbers/$VOICE_NUMBER_ID" \ --header "Authorization: Bearer $BIRD_API_KEY" \ --header "Content-Type: application/json" \ --data '{"inbound_configuration": {"route": {"type": "trunk", "trunk_id": "'"$TRUNK_ID"'"}}}'A
200response shows the new route. A trunk with inbound calling off answers412withE21052.
Turning inbound calling off on a trunk, or deleting the trunk, returns each number routed to it to your workspace's default route, and turning it on again does not restore those routes. Deliver calls to a SIP trunk explains this and the trunk, forward, and reject routes.
Reconcile your numbers
Walk the list with the largest page size. Pass each page's next_cursor as NEXT_CURSOR to read the next page, until next_cursor is null. The list returns no total count.
curl "$BIRD_API_URL/v1/numbers?limit=100" \
--header "Authorization: Bearer $BIRD_API_KEY"
curl "$BIRD_API_URL/v1/numbers?limit=100&starting_after=$NEXT_CURSOR" \
--header "Authorization: Bearer $BIRD_API_KEY"Telserv's list filters map onto Bird's like this:
Telserv filter on GET /numbers | Bird filter on GET /v1/numbers |
|---|---|
number, which matches the start of the number | prefix without country_code, which matches the digits from the start of the full number |
prefix, which equals the access and area code such as 3170 | country_code with prefix set to the national digits: country_code=NL&prefix=70 |
reference with a partial value | search, which matches part of the number, name, or reference, ignoring case |
reference with a quoted exact value | reference, which returns the numbers with that exact reference. The match is case-sensitive |
| New in Bird | number returns one whole number given with its country code, with or without + or 00: +31612345678, 31612345678, and 0031612345678 find the same number |
Find a number in your list covers each filter in detail.
Each entry carries pricing, with recurring_price per recurring_period and intervals_per_charge. Unless a price is fixed for the number, recurring_price is your organization's current rate, and each renewal charges the rate in force when it renews, so a charge already billed can differ from it. List every number you hold explains the currency each number is charged in, when pricing or its prices are null, and what to do when pricing.error is set.
A released number leaves this list, and reading it by number_id answers 404. If you ordered it in Bird, its completed order stays in GET /v1/numbers/orders.
Release a number
Both requests apply to a dedicated number, by its nda_ identifier; a shared number is refused with E14002. To keep the number until the end of the period you already paid for, cancel it:
curl --request DELETE "$BIRD_API_URL/v1/numbers/$NUMBER_ID" \
--header "Authorization: Bearer $BIRD_API_KEY" \
--header "Idempotency-Key: cancel-CUST-778812"A 202 response returns the number with releases_at, the end of its paid period. The number keeps its current status until then, its subscription does not renew, and it is released at that time. Asking again returns the same releases_at, and a cancellation cannot be undone. A number with no subscription behind it is released at once, and the response is 200 with the number's status set to released.
To release the number now, including one already scheduled for cancellation:
curl --request POST "$BIRD_API_URL/v1/numbers/$NUMBER_ID/release" \
--header "Authorization: Bearer $BIRD_API_KEY" \
--header "Idempotency-Key: release-CUST-778812"A 204 response means the number is released, effective immediately. The rest of the current period is not refunded.
While a renewal payment for the number is overdue, both requests answer 409 with E03015 until it is paid.
Neither request takes a date, so keep the schedule you sent to Telserv as requestedCancelDate on your side. To keep a number up to its date, cancel it once the date falls within its current billing period: it is released when that period ends, on or after the date. To give it up exactly on the date, release it that day. Release a number covers what each request does to the number, its charges, and a retry.
Place a test call to your number
To check a route end to end, place a call to the number from another number your workspace may present as a caller ID. The number's country must be enabled in your voice destinations, or the call does not go through. The request below runs an inline sequence that hangs up once the call is answered, so no saved sequence is needed. It places a real call, and normal calling charges apply.
Save the request as test-call.json. Put the caller ID you present in from, an owned or verified number of your workspace, and the number under test in to:
{
"from": "+31612345679",
"to": "+31612345678",
"sequence": {
"definition": {
"schema_version": 1,
"expression_environment": "bird.cel.v1",
"nodes": [
{
"id": "entry",
"type": "trigger.start_call",
"type_version": 1,
"config": {},
"input": {},
"connections": { "event": { "node_id": "end", "port": "input" } }
},
{ "id": "end", "type": "voice.hangup", "type_version": 1, "config": {}, "input": {} }
]
},
"entry_node_id": "entry",
"trigger_data": {}
}
}Send it:
curl --request POST "$BIRD_API_URL/v1/voice/calls" \
--header "Authorization: Bearer $BIRD_API_KEY" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: ${TEST_CALL_KEY:?Set TEST_CALL_KEY once for this test call}" \
--data-binary @test-call.jsonSet TEST_CALL_KEY to a new value for each test, and reuse it when you retry the same test after a timeout or an unclear result: the same key and request within three hours replay the first acceptance instead of placing a second call. A 202 response accepts the call and returns its initial_leg_id. Acceptance does not prove the call connected: read the leg with GET /v1/voice/legs/{leg_id}, or list the legs placed to the number with GET /v1/voice/legs and its to filter, and check that your equipment received the call. Create a voice call covers the caller-ID and destination requirements and the retry rules.
Next steps
Related resources
Continue with the documentation, guides and examples for this topic.