---
title: "List legs"
method: GET
path: "/v1/voice/legs"
tags: ["voice-legs"]
---

# List legs

`GET /v1/voice/legs`

## Request

- Base URL: `https://us1.platform.bird.com`
- URL: `https://us1.platform.bird.com/v1/voice/legs`
- Auth: one of:
  - HTTP bearer
  - API key in cookie `bird_session`

Returns a paginated list of the workspace's legs, ordered by start time
descending.

The `status` filter selects where in the lifecycle you look, and any
combination is a single page: in-flight statuses (`ringing`,
`in_progress`), final ones, or both together. Omit it and you get
completed legs, which is what this list has always returned.

A leg in flight carries no economics yet: `duration_ms`, `billable_ms`,
`ended_at`, and `cost` are null until it ends. It keeps the same `id`
throughout, so the same leg answers under one identity from the first
ring to settlement.

## Query parameters

- `direction` 'inbound' | 'outbound': Whether the call was placed from your side, by your PBX, the API, the browser or Bird dialing onward for you (outbound), or arrived from a remote party (inbound).
- `status` VoiceCallStatus[]
- `call_id` string
- `sip_trunk_id` string
- `from` string
- `to` string
- `number` string
- `tag` string[]
- `started_after` string, date-time
- `started_before` string, date-time
- `limit` integer
- `starting_after` string
- `ending_before` string

## Headers

- `X-Workspace-Id` string
- `X-Organization-Id` string

## Response `200`

Paginated list of leg records.

- VoiceLegList
  - `data` VoiceLeg[], required
    - `id` string, required
    - `call_id` string
    - `workspace_id` string, required
    - `direction` 'inbound' | 'outbound', required: Whether the call was placed from your side, by your PBX, the API, the browser or Bird dialing onward for you (outbound), or arrived from a remote party (inbound).
    - `from` string, required: Calling party number in E.164 format.
    - `to` string, required: Called party number in E.164 format.
    - `actor` Actor
      - `id` string, required: Actor identifier.
      - `type` string, required: New actor types may be added. Treat unrecognized values as future types, not errors. - `user`: a member's own session. - `api_key`: a workspace API key. - `oauth_token`: a token issued to a caller on a member's behalf. - `system`: an action we perform without a customer actor. - `sso`: an organization's SSO connection. - `service_account`: a workspace's connected Integration acting with no member behind it. - `automation`: an automation execution in your workspace.
      - `display_name` string, nullable: The label the actor is shown under: typically a member's name or email address, or the API key's name. Null when it could not be resolved.
    - `sip_trunk_id` string
    - `sip_call_id` string: The SIP `Call-ID` of this leg's signalling. We relay it unchanged, so it matches what the carrier and your own equipment logged for the same leg: the carrier's value on an incoming leg, and your system's value on a leg you place through a SIP trunk. Use it to match this leg against a carrier's records or your PBX logs. Legs recorded before this field existed carry it only if they were answered.
    - `status` 'answered' | 'no_answer' | 'busy' | 'canceled' | 'failed' | 'rejected' | 'unknown' | 'ringing' | 'in_progress', required: Call status. A call that has ended carries one of: - `answered` means it connected and the far end picked up. - `no_answer` means nobody picked up before the call timed out. - `rejected` means it was refused rather than attempted. Either we turned it away before dialing a carrier, in which case `rejection_reason` names the check it failed where there was one, or the far end declined it. - `failed` means it was attempted and did not work, and `sip_response_code` is what came back. - `unknown` means the outcome could not be determined. Contact support with the call `id` if you see one. An active call carries `ringing` before it is picked up and `in_progress` afterward. The call list's `status` filter takes any mix of the two sets. `busy` and `canceled` are reserved for incoming calls delivered to your own numbers: `busy` for a called party that rejected the call as busy, `canceled` for a caller who hung up before it was picked up. Neither is emitted yet and both outcomes are reported as `failed` today.
    - `sip_response_code` integer, nullable: Final SIP response code received from the carrier. `null` when no SIP response was received, for example on timeout or DNS failure.
    - `rejection_reason` 'source_not_allowed' | 'caller_id_not_verified' | 'routing_not_configured' | 'no_route_found' | 'destination_blocked' | 'destination_not_enabled' | 'insufficient_balance' | 'daily_spend_exceeded' | 'concurrent_calls_exceeded' | 'calls_per_second_exceeded' | 'call_not_permitted' | 'number_ownership_not_verified': Why we rejected the leg. Use `rejection_reason` to identify the cause; `sip_response_code` alone cannot distinguish these reasons. You can resolve these issues: - `source_not_allowed`: The leg came from an IP address that is not in the trunk's allowed-address list. Add the address your PBX sends from. - `caller_id_not_verified`: The number in the `From` header is not a verified caller ID for this workspace. Verify it or use a verified caller ID. - `number_ownership_not_verified`: The ownership documents for this purchased number have not yet been accepted under its country's requirements. We block outgoing and incoming legs on the number until verification is complete. Blocked incoming legs never reach your PBX, and their route type is `reject` regardless of the number's configuration. Open the number under **Numbers** and complete its ownership requirements, then retry the leg. - `destination_not_enabled`: Calling to this destination country is disabled. Enable it in your voice destination settings. - `insufficient_balance`: Your wallet balance was too low for the leg. Top up or enable automatic top-ups. - `daily_spend_exceeded`: The leg would exceed your organization's daily voice spend limit. Retry after the limit resets at the start of the next UTC day. - `concurrent_calls_exceeded`: You already have as many legs in progress as your account allows. Wait for one to end or ask support to raise the limit. - `calls_per_second_exceeded`: You placed legs faster than your account allows. Reduce your dialing rate and retry. For all other reasons, contact support and provide the leg `id`: - `routing_not_configured`: This trunk has no dial plan, which can happen on a new trunk. - `no_route_found`: A dial plan is attached, but no rule in it covers this destination. - `destination_blocked`: The destination is blocked by our routing configuration. - `call_not_permitted`: The leg could not be priced for your account.
    - `route` union: The routing choice recorded for an incoming leg. A recorded route does not guarantee that the leg connected. Check `status` for the outcome and `rejection_reason` for the cause when present.
      - VoiceLegInboundRouteReject
        - `type` 'reject' | 'trunk' | 'forward' | 'sequence', required: Which answer handled this incoming leg. - `reject`: the call was refused. - `trunk`: the call was delivered to one of your SIP trunks. - `forward`: the call was forwarded to one of your verified caller IDs. - `sequence`: the call was handled by one of your sequences.
      - VoiceLegInboundRouteTrunk
        - `type` 'reject' | 'trunk' | 'forward' | 'sequence', required: Which answer handled this incoming leg. - `reject`: the call was refused. - `trunk`: the call was delivered to one of your SIP trunks. - `forward`: the call was forwarded to one of your verified caller IDs. - `sequence`: the call was handled by one of your sequences.
        - `trunk_id` string, required
      - VoiceLegInboundRouteForward
        - `type` 'reject' | 'trunk' | 'forward' | 'sequence', required: Which answer handled this incoming leg. - `reject`: the call was refused. - `trunk`: the call was delivered to one of your SIP trunks. - `forward`: the call was forwarded to one of your verified caller IDs. - `sequence`: the call was handled by one of your sequences.
        - `forward_to` string, required: The number the leg was forwarded to, in E.164 format. Recorded as it was at the time, so it may name a number you have since stopped verifying.
        - `forward_as` 'dialed_number' | 'calling_number', required: Which of a forwarded call's two numbers it shows as the caller. `dialed_number` presents the Bird number the caller dialed. Whoever answers sees which of your numbers was called. Older configurations without a stored choice use this value. Carrier screening can still affect delivery. `calling_number` presents the caller's own number, so the phone rings as though they had dialed it directly and the call can be returned from the call log. Because the number is not one you own, some carriers (most often in the US and parts of Europe) mark such calls as unverified, replace the number, or screen them.
      - VoiceLegInboundRouteSequence (unresolved $ref)
    - `tags` Tag[]: Your own `{name, value}` labels for this leg, taken from the `X-Bird-Call-Tag` headers on the INVITE that placed it. Set them to organise legs by a dimension of your own (campaign, queue, agent, cost centre), then filter this list by them with `tag`. Read-only here: a leg is labelled when it is placed, and never afterwards. What is here may be less than what was sent, and the leg still goes through either way: a tag whose name or value breaks the rules below is dropped, anything past the first five is ignored, and a name sent more than once keeps its first value. Absent when the leg carried none, and on legs recorded before this field existed.
      - `name` string, required: Tag name. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 32 characters.
      - `value` string, required: Tag value. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 64 characters.
    - `started_at` string, date-time, required: When the leg was initiated.
    - `answered_at` string, date-time, nullable: When the leg was answered (`200` OK received). `null` for unanswered legs.
    - `ended_at` string, date-time, nullable: When the leg ended (BYE or final non-2xx response). `null` for legs that ended abnormally without a recorded end event.
    - `duration_ms` integer, nullable: Total leg duration in milliseconds, measured from the first INVITE to the BYE or final response. `null` while the leg is still in progress and has no final duration yet.
    - `pdd_ms` integer: Post-dial delay in milliseconds: how long the caller heard nothing between dialing and the phone starting to ring at the other end. High values are what callers experience as the leg `not going through`. Absent when the leg never rang, either because it failed first or because the carrier answered it immediately.
    - `billable_ms` integer, nullable: Billable duration in milliseconds, measured from answer to leg end. Zero for unanswered legs, and `null` while the leg is still in progress.
    - `media_quality` VoiceMediaQuality
      - `mos` number, required: Mean opinion score, the single number for how the call sounded, from 1 (unintelligible) to 5 (as good as being in the same room). Anything at or above 4.0 is what most people would call a clear line, and below 3.5 is where callers start asking each other to repeat themselves. The three other fields are the impairments that move it.
      - `jitter_ms` integer, required: Variation in the arrival time of the audio packets, in milliseconds. Audio arriving unevenly is heard as choppiness even when no packets are lost at all.
      - `packet_loss_pct` number, required: Percentage of audio packets that never arrived. Heard as brief gaps or clipped words, and the impairment that degrades a call fastest.
      - `round_trip_time_ms` integer, required: Round-trip time between the two ends, in milliseconds. It does not distort the audio. Above roughly 300 ms, the two parties start talking over each other.
    - `cost` VoiceLegCost: What was charged for a leg, split into the components that make it up.
      - `amount` string, required: Total charged, as a decimal string: the sum of the components below. Net of tax, which applies to your wallet balance rather than to an individual charge.
      - `currency_code` string, required: ISO 4217 three-letter currency code.
      - `outbound_amount` string, nullable, required: What we charged to carry the leg to the destination network, as a decimal string. `null` until this component is priced.
      - `inbound_amount` string, nullable, required: What we charged to receive the leg from the originating network, as a decimal string. Only a leg that arrived at your number can carry it. `null` until this component is priced.
      - `call_handling_amount` string, nullable, required: What we charged for handling the call itself, as a decimal string. A call is charged for handling once, however many legs it has, so only one leg's record carries it. `null` until this component is priced.
      - `recording_amount` string, nullable, required: What we charged to record the leg, as a decimal string, billed per second over the same billable time as the rest of the leg. `null` until this component is priced.
      - `transcription_amount` string, nullable, required: What we charged to transcribe the leg's audio, as a decimal string, billed per second of recorded audio rather than for the length of the leg. A transcript is produced after the leg ends, so this can appear after the rest of the cost. `null` until this component is priced.
  - `next_cursor` string, nullable, required: Cursor for the next page. Pass back as `starting_after` to advance forward. `null` when no next page exists.
  - `prev_cursor` string, nullable, required: Cursor for the previous page. Pass back as `ending_before` to step backward. `null` when no previous page exists.
  - `refresh_cursor` string, nullable, required: Refresh anchor, the first row of this response. Pass back as `ending_before` to fetch what precedes it in the current sort order. On a newest-first sort those are the items that have appeared since; on any other sort they are the items that sort earlier, so refreshing such a list means re-fetching it instead. Non-`null` whenever `data` is non-empty; `null` only on an empty page. Distinct from `prev_cursor`.

## Other responses

- `400`: Bad request
- `401`: Authentication required
- `403`: Insufficient permissions
- `404`: Resource not found
- `422`: The request has invalid field values, violates a business rule, or carries a query parameter the endpoint does not declare. Field validation errors use `type: validation_error` and include the affected fields in `details`. Business-rule errors identify the failed rule in `type`.
- `429`: Rate limit exceeded
- `500`: Internal server error

---

[API](https://skmtc.dev/messagebird/apis/bird-api.md) · [All operations](https://skmtc.dev/messagebird/apis/bird-api/llms.txt) · [OpenAPI document](https://skmtc.dev/messagebird/apis/bird-api/revisions/3e2a50466454?raw)
