---
title: "Migrate from Telserv to Bird"
description: "Move a Telserv partner API integration to the Bird API, map each operation group to its Bird call, and see how each job is done in Bird."
canonical: "https://bird.com/docs/guides/numbers/migrate/telserv"
---

# 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](/docs/api/authentication) 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](/docs/guides/numbers/buying-numbers#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`](/docs/api/reference/list-workspace-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](/docs/api/regions) 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**](https://bird.com/dashboard/w/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](/docs/api/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](/docs/guides/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](/docs/guides/idempotency)                                     |

The Bird API refuses a query parameter it does not recognize with `422` and [`E01029`](/docs/api/errors/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`](/docs/api/reference/list-workspace-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`](/docs/api/reference/list-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](#route-a-number-to-your-sip-trunk) shows the request, and [Managing your phone numbers](/docs/guides/numbers/managing-numbers#find-the-voice-record-that-routes-a-number) 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`](/docs/api/reference/list-available-numbers)        |
| Check one number       | `GET /shop/{stockDnisId}`                                | [`GET /v1/numbers/available/{number}`](/docs/api/reference/get-available-number) |
| Buy a number           | `POST /shop/{stockDnisId}`                               | [`POST /v1/numbers/orders`](/docs/api/reference/create-numbers-order)            |
| Secure a number        | `POST /shop/{stockDnisId}/reserve`, `GET /shop/reserved` | [`POST /v1/numbers/orders`](/docs/api/reference/create-numbers-order)            |

- **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

| Job               | Telserv                                         | Bird                                                                                                                                                                |
| ----------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| List your numbers | `GET /numbers`                                  | [`GET /v1/numbers`](/docs/api/reference/list-workspace-numbers)                                                                                                     |
| Read one number   | `GET /numbers/{dnisId}`                         | [`GET /v1/numbers/{number_id}`](/docs/api/reference/get-workspace-number) and [`GET /v1/voice/numbers/{number_id}`](/docs/api/reference/get-voice-number)           |
| Change a number   | `PATCH /numbers/{dnisId}`                       | [`PATCH /v1/numbers/{number_id}`](/docs/api/reference/update-workspace-number) and [`PATCH /v1/voice/numbers/{number_id}`](/docs/api/reference/update-voice-number) |
| Release a number  | `DELETE /numbers/{dnisId}?requestedCancelDate=` | [`DELETE /v1/numbers/{number_id}`](/docs/api/reference/release-workspace-number)                                                                                    |

- **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`](/docs/api/reference/list-numbers-orders). [Reconcile your numbers](#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](#release-a-number) shows the request.

### Destinations and profiles

| Job                         | Telserv                                                     | Bird                                                                                                                                                            |
| --------------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Send calls to SIP equipment | `POST /numbers/{dnisId}/destinations`                       | [`POST /v1/voice/trunks/{trunk_id}/gateways`](/docs/api/reference/create-voice-trunk-gateway), 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`](/docs/api/reference/list-voice-trunks) and [`GET /v1/voice/trunks/{trunk_id}/gateways`](/docs/api/reference/list-voice-trunk-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. `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](/docs/guides/voice/caller-ids); an unverified number answers `412` with [`E21053`](/docs/api/errors/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

| Job                      | Telserv                                                                  | Bird                                                                                     |
| ------------------------ | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------- |
| Check paperwork progress | `GET /numbers/{dnisId}/documents`                                        | `ownership` on [`GET /v1/numbers/{number_id}`](/docs/api/reference/get-workspace-number) |
| File paperwork           | `POST /numbers/{dnisId}/documents`, `POST /numbers/{dnisId}/enduserdata` | The number on the [**Numbers**](https://bird.com/dashboard/w/numbers) page               |
| Read the requirements    | `GET /products/conditions`, `GET /masterdata/productconditions`          | The 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

| Job             | Telserv                                      | Bird                                                                      |
| --------------- | -------------------------------------------- | ------------------------------------------------------------------------- |
| Browse products | `GET /products`, `GET /products/{productId}` | [`GET /v1/numbers/available`](/docs/api/reference/list-available-numbers) |

Each search result describes its product with `number_type`, `capabilities`, and `ownership_registration_required`. List prices are on the [phone number pricing](/pricing/numbers) 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.

- **Prices.** List prices are on the [phone number pricing](/pricing/numbers) 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:

```bash
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:

```bash
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](/docs/guides/numbers/buying-numbers#poll-an-order-that-did-not-finish) 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`](/docs/api/errors/E03007) means the order is refused because that country and number type has no price. [Buy and release a number](/docs/guides/numbers/buying-numbers#when-an-order-is-refused) 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}`](/docs/api/reference/get-workspace-number) and check that `status` is `active`. If it is pending, file the paperwork from the number on the [**Numbers**](https://bird.com/dashboard/w/numbers) page. [The status of an allocated number](/docs/guides/numbers/overview#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](/docs/guides/voice/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`:

   ```bash
   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}`:

   ```bash
   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](/docs/guides/voice/receiving-calls#what-a-gateway-needs) explains `priority` and the number formats. Creating the same gateway again without an idempotency key answers `409` with [`E21047`](/docs/api/errors/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`:

   ```bash
   curl "$BIRD_API_URL/v1/voice/numbers?search=31612345678" \
     --header "Authorization: Bearer $BIRD_API_KEY"
   ```

4. Point the number at the trunk:

   ```bash
   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`](/docs/api/errors/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](/docs/guides/voice/receiving-calls#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.

```bash
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](/docs/guides/numbers/managing-numbers#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`](/docs/api/reference/list-numbers-orders).

## Release a number

Release a dedicated number with its `nda_` identifier:

```bash
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](/docs/guides/numbers/managing-numbers#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](/docs/guides/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](/docs/guides/voice/caller-ids) of your workspace, and the number under test in `to`:

```json
{
  "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:

```bash
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}`](/docs/api/reference/get-voice-leg), or list the legs placed to the number with [`GET /v1/voice/legs`](/docs/api/reference/list-voice-legs) and its `to` filter, and check that your equipment received the call. [Create a voice call](/docs/guides/voice/create-calls) covers the caller-ID and destination requirements and the retry rules.

## Next steps

- [Buy and release a number](/docs/guides/numbers/buying-numbers)
- [Managing your phone numbers](/docs/guides/numbers/managing-numbers)
- [Receiving calls](/docs/guides/voice/receiving-calls)
- [SIP trunks](/docs/guides/voice/sip-trunks)
- [Idempotency](/docs/guides/idempotency)

## Related resources

- [What is a virtual phone number (VMN)?](/explained/numbers/what-is-a-virtual-phone-number) (answer)
- [SMS numbers](/sms-api/features/numbers) (product)
- [Number types](/docs/guides/numbers/number-types) (docs)

[Get an implementation brief](/learn/workspace?topic=phone-numbers)
