Sign inGet Started

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: numbers at write level for numbers and orders, and voice_management at write level for SIP trunks, voice numbers, and verified numbers. The test call also needs voice:write, and reading call legs needs voice:read.
  • Wallet balance. Orders are paid from the wallet. When an order is refused explains the 402 and 412 refusals.

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

AreaTelserv partner APIBird API
AuthenticationX-ApiKey header (V2) or apiKey parameter (V1), checked against your IP allowlistAuthorization: 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
IdentifiersIntegers. Buying a number gives it a new idPrefixed TypeIDs such as nda_, nor_, vnu_, and spt_. A number keeps its nda_ id while it is allocated to you
Phone numbersDigits without a leading +E.164 with the leading +
Paginationstart and limit offsets, up to 1000 per pageCursors: limit up to 100 (default 25), then starting_after set to the previous page's next_cursor. See Pagination
ErrorserrorCode names an exception class, and 403 covers a missing resource and many validation failureserror.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
EnvironmentsLIVE and SANDBOX, each with its own keysOne environment. Each order buys and charges for a real number, and each test call is a real call
RetriesNo idempotency keyAn 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, nda_, returned by GET /v1/numbers and as an order's number_id. Use it to read, name, reference, and release the number.
  • The voice record, vnu_, returned by GET /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_ 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 nda_ identifier. 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

JobTelservBird
Search numbers on saleGET /shopGET /v1/numbers/available
Check one numberGET /shop/{stockDnisId}GET /v1/numbers/available/{number}
Buy a numberPOST /shop/{stockDnisId}POST /v1/numbers/orders
Secure a numberPOST /shop/{stockDnisId}/reserve, GET /shop/reservedPOST /v1/numbers/orders
  • Search. country_code is required, so a search covers one country. prefix takes the national digits after the country code: Telserv prefix=3170 becomes country_code=NL&prefix=70. Narrow by area code with prefix in place of city, and by number_type and capabilities in place of productId.
  • Paperwork in search results. Each result carries ownership_registration_required in place of the excludeNumbersRequireVerification filter. A true value means the number is billed from purchase but carries no voice calls until its paperwork is approved.
  • Check one number. It is addressed by its E.164 number.
  • Buy. Send number and an optional reference. The order is paid from your wallet. Most orders answer 201; an order that waits on a carrier answers 202, and you poll it.
  • Secure. Ordering the number secures it. Check a candidate first, then order it.

Numbers

JobTelservBird
List your numbersGET /numbersGET /v1/numbers
Read one numberGET /numbers/{dnisId}GET /v1/numbers/{number_id} and GET /v1/voice/numbers/{number_id}
Change a numberPATCH /numbers/{dnisId}PATCH /v1/numbers/{number_id} and PATCH /v1/voice/numbers/{number_id}
Release a numberDELETE /numbers/{dnisId}?requestedCancelDate=DELETE /v1/numbers/{number_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 and ownership paperwork are on the allocation, and the route is on the voice record.
  • Change. name and reference are 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. destinationDistributionType becomes gateway priority on the SIP trunk: FailOver gives each gateway a different priority, and RoundRobin gives them the same one.
  • Release. The release is immediate: the number stops working at once, and its subscription ends with the period you already paid for instead of renewing. Release a number shows the request.

Destinations and profiles

JobTelservBird
Send calls to SIP equipmentPOST /numbers/{dnisId}/destinationsPOST /v1/voice/trunks/{trunk_id}/gateways, then route trunk
Forward calls to a phonePOST /numbers/{dnisId}/destinationsRoute forward
Stop taking callsDELETE /numbers/{dnisId}/destinationsRoute reject
Read destination profilesGET /destinationprofiles, GET /destinationprofiles/{id}GET /v1/voice/trunks and GET /v1/voice/trunks/{trunk_id}/gateways
Apply a profile to a numberPOST /numbers/{dnisId}/destinations/profileRoute 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. sortOrder becomes priority. Apply ring time and time windows on your SIP equipment.
  • Forward. Set forward_to and forward_as. forward_to must be one of your verified numbers; an unverified number answers 412 with E21053. forward_as chooses 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. distributionType becomes priority: FailOver gives each gateway a different priority, and RoundRobin gives them the same one. Distribution is set per trunk, so numbers that need different distribution need different trunks. The {dnis} placeholder becomes {number} in destination_format, and a prefixCLIP becomes the text before {number} in origination_format.
  • Apply a profile. A trunk is addressed by its spt_ identifier, where Telserv addressed a profile by name.

Documents and end-user data

JobTelservBird
Check paperwork progressGET /numbers/{dnisId}/documentsownership on GET /v1/numbers/{number_id}
File paperworkPOST /numbers/{dnisId}/documents, POST /numbers/{dnisId}/enduserdataThe number on the Numbers page
Read the requirementsGET /products/conditions, GET /masterdata/productconditionsThe number on the Numbers page, when you file

ownership.status reports progress, and the filed documents are on the number in the dashboard. ownership is usually null when the number needs no paperwork.

Products and master data

JobTelservBird
Browse productsGET /products, GET /products/{productId}GET /v1/numbers/available

Each search result describes its product with number_type, capabilities, and ownership_registration_required. List prices are on the phone number pricing page.

Telserv number types map onto Bird's number_type like this:

Telserv numberTypeBird number_type
DIDlocal or national, depending on the number
FreePhonetoll_free
Mobilemobile

What works differently today

These Telserv jobs are done differently in Bird. Plan for them before you move an integration that depends on one.

  • Prices. List prices are on the phone number pricing page.
  • Paperwork. File documents and end-user data from the number in the dashboard, and follow their progress in ownership over the API.
  • 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. Keep the cancellation dates you sent to Telserv on your side, and release each number on its date.
  • 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:

Code example
curl "$BIRD_API_URL/v1/numbers/available?country_code=NL&number_type=mobile&capabilities=voice&limit=10" \
  --header "Authorization: Bearer $BIRD_API_KEY"

To confirm a candidate 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:

Code example
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. A 404 with E03007 means the order is refused because that country and number type has no price. Buy and release a number explains the other 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 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 paperwork from the number on the Numbers page. 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.

Route a number to your SIP trunk

A new number starts on the reject route, so it turns callers away until you route it. 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.

  1. Create the trunk with inbound calling on. Keep the id from the response, the trunk's spt_ identifier, as TRUNK_ID:

    Code example
    curl --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}'
  2. 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, set destination_format for the dialed number and origination_format for the calling number to {number}:

    Code example
    curl --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 priority and the number formats. Creating the same gateway again without an idempotency key answers 409 with E21047.

  3. Find the number's voice record. Search with the number's digits and take the id of the entry whose provider.number_id is your nda_ identifier. Keep it as VOICE_NUMBER_ID:

    Code example
    curl "$BIRD_API_URL/v1/voice/numbers?search=31612345678" \
      --header "Authorization: Bearer $BIRD_API_KEY"
  4. Point the number at the trunk:

    Code example
    curl --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 200 response shows the new route. A trunk with inbound calling off answers 412 with E21052.

Turning inbound calling off on a trunk, or deleting the trunk, puts each number routed to it back on reject, 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.

Code example
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 /numbersBird filter on GET /v1/numbers
number, which matches the start of the numberprefix without country_code, which matches the digits from the start of the full number
prefix, which equals the access and area code such as 3170country_code with prefix set to the national digits: country_code=NL&prefix=70
reference with a partial valuesearch, which matches part of the number, name, or reference, ignoring case
reference with a quoted exact valuereference, which returns the numbers with that exact reference. The match is case-sensitive
New in Birdnumber 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.

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

Release a dedicated number with its nda_ identifier:

Code example
curl --request DELETE "$BIRD_API_URL/v1/numbers/$NUMBER_ID" \
  --header "Authorization: Bearer $BIRD_API_KEY" \
  --header "Idempotency-Key: release-CUST-778812"

A 204 response means the number is released, effective immediately. The request takes no date, so keep the schedule you sent to Telserv as requestedCancelDate on your side and send the release on the day. The number stops working at once, and its subscription ends with the period you already paid for instead of renewing. Release a number covers what a release 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:

Code example
{
  "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:

Code example
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.json

Set 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

Continue with the documentation, guides and examples for this topic.