---
title: "Get a two-party telephony bridge session"
method: GET
path: "/voice/bridge/{id}"
tags: ["voice"]
---

# Get a two-party telephony bridge session

`GET /voice/bridge/{id}`

## Request

- Base URL: `https://corebee.ai/api/v1`
- URL: `https://corebee.ai/api/v1/voice/bridge/{id}`
- Auth: HTTP bearer

## Path parameters

- `id` integer, required

## Response `200`

OK

- object
  - `data` BridgeStatus, required: Full status projection plus the state-transition timeline.
    - `id` integer
    - `state` 'created' | 'dialing_business' | 'ivr_nav' | 'amd_verdict' | 'human' | 'voicemail' | 'prep_line' | 'converse' | 'dialing_consumer' | 'bridging' | 'bridged' | 'completed' | 'failed'
    - `failure_reason` string, nullable: Why the bridge failed; null unless state=failed. This is an OPEN set that grows as we learn to tell failures apart, treat an unrecognized value as a generic failure rather than switching exhaustively. The values that say WHY a call never connected: business_number_unreachable / consumer_number_unreachable, the number you gave us is not in service or is not routed to anywhere; check it by dialling it from a mobile phone outside your own phone system. business_busy / consumer_busy, the number is fine and the call reached the handset, which then refused it (SIP 486). We CANNOT tell you which of three things happened, because the phone reports them identically: the line was genuinely engaged, somebody pressed decline while it rang, or Do Not Disturb refused it. Do not read this as "they were on another call", a declined call and a busy line are the same code. One weak signal you can use: a line that is engaged with call-waiting off refuses almost instantly, so a busy that arrives several seconds after we dialled means the phone rang first, i.e. something or someone rejected it. business_rejected / consumer_rejected, a carrier or network element refused the call outright (SIP 403), before the handset had any say. This is NOT the value for a person pressing decline, that arrives as busy, above. business_network_error / consumer_network_error, the network on the far side reported itself out of order; the number may be perfectly correct, so retry before changing anything. business_no_answer / consumer_no_answer, it genuinely rang and nobody picked up. This also remains the value we report when the carrier gives us no usable reason, so it means "rang out, or we could not tell". business_dial_timeout / consumer_dial_timeout, it was still ringing when our own dial budget expired. On some calls the AI holds a short conversation with the business (state=converse) instead of only speaking a fixed prep line; these four values only occur on those calls and the consumer is never dialed: business_cannot_help, a real person answered but said they cannot take it (wrong department, closed, nobody available); not a bad number, retry later or reschedule. converse_budget_expired, the call ran out of conversation time without reaching a ready human. Either the AI spent its full budget (~90s) working the menu/rep, or the line went quiet and our own timer ended it. Not a fault of the business and never billed; retry later. converse_confused, the AI could not follow what it heard (noise, unusual menu) and bailed politely rather than guess; retry later. converse_service_failed, a fault inside our conversational service, not the business; fails closed and is never billed, so retry. converse_no_human, the AI reached a voicemail or a dead-end menu; nobody was ever there to speak to. Not a fault of ours and not a bad number: the line answered. Never billed; retry when someone is likely to be in.
    - `amd_result` 'human_residence' | 'human_business' | 'machine' | 'silence' | 'fax_detected' | 'not_sure', nullable: Premium AMD detection.ended result (six-value enum), null until determined.
    - `business_number` string
    - `consumer_number` string
    - `customer_ref` string, nullable: Your own identifier, echoed from metadata.customer_ref at create time. Returned here since this endpoint existed; documented from 2026-08-19.
    - `leg_a_call_control_id` string, nullable: Business leg (partner Telnyx account).
    - `leg_b_call_control_id` string, nullable: Consumer leg (partner Telnyx account).
    - `created_at` string, date-time, nullable
    - `updated_at` string, date-time, nullable
    - `events` BridgeEvent[]
      - `event_type` string
      - `from_state` string, nullable
      - `to_state` string, nullable
      - `occurred_at` string, date-time, nullable: Telnyx's own timestamp where there is one, otherwise our ingest time. Check occurred_at_source before using this for durations.
      - `id` integer: Our event row id. Stable; usable for de-duplication on your side.
      - `occurred_at_source` 'telnyx' | 'ingest': "telnyx" = occurred_at is Telnyx's authoritative event time. "ingest" = there was none (an internal row) and occurred_at is our insert time instead. Only "telnyx" rows feed `timings`.
      - `recorded_at` string, date-time, nullable: When we stored the event. Compare against occurred_at to see delivery/queue lag.
      - `call_control_id` string, nullable: The Telnyx leg this event belongs to; null on internal rows.
      - `leg` 'business' | 'consumer', nullable: Which leg call_control_id resolves to, or null when it resolves to neither.
      - `telnyx_event_id` string, nullable: Telnyx's own event id, for reconciliation against your Telnyx portal.
      - `payload` object, nullable: The raw provider payload as stored, with three read-time redactions and nothing else removed: (1) the query string of any URL-valued field is replaced with "<redacted>", because signed media URLs carry credentials; (2) recording URL containers are replaced with { formats: [...], redacted: true }, this API never serves audio; (3) transcript `lines` are replaced with "<redacted>" unless transcripts are enabled for your account. Everything else (to/from, hangup_cause, sip_hangup_cause, call_quality_stats, codec, AMD result, DTMF digit, stream/speak/playback ids) passes through verbatim. Shapes vary by event_type and are set by the provider, so treat unknown keys as additive. A payload over the size cap is replaced by { truncated: true, bytes, reason }.
    - `events_truncated` boolean: True when the timeline hit the per-session event cap and older events were dropped. False on every ordinary call.
    - `recording_duration_sec` integer, nullable: Seconds of SAVED AUDIO for this call, the length of the mp3, as reported by the carrier (whole seconds). This is NOT the billed duration and it is NOT the whole call: see `joined_seconds` for what you are invoiced for. ⛔ SCOPE: the audio covers the AI-TO-BUSINESS leg ONLY. Recording stops before the consumer is dialed, so the consumer is never in it and was never recorded. Do not label this "call recording" in a UI shown to anyone who might reasonably read that as including the consumer. null when no recording exists for the session, which is the ORDINARY case, not an error: recording is a per-account feature and most sessions have no recording row at all. null ALSO when a recording exists but its length was never stored. It is never 0 for "unknown": a 0 would be a claim about the call, a null is a claim about our knowledge. This field never fails the request and never carries a link to audio.
    - `joined_seconds` number, nullable: Seconds two HUMANS were on the line together, THE BILLED INTERVAL. Identical in name, source and value to `joined_seconds` on GET /voice/bridge/usage, so the operational feed and the billing feed cannot quote you different numbers. Read from the carrier's own call.bridged start_time, NOT from `bridged_at`: that field is stamped by our webhook worker a median 7.73s after the fact and on three historical sessions post-dates the hangup, so differencing it yields a negative conversation. null when the evidence pass has not run over this session or the carrier stamps did not survive its checks. Never negative, never guessed from a clock, and never silently falling back to `bridged_at`. It is unrelated to `recording_duration_sec` and the two routinely disagree: a call can be recorded for 41 seconds and never join anybody.
    - `timings` BridgeTimings: Durations derived from the timeline. Every mark comes from a Telnyx occurred_at (never our ingest time); a value is null when the mark it needs never arrived. Not clamped at zero, a negative value means Telnyx genuinely reported the pair out of order, which is real signal.
      - `business_ring_ms` integer, nullable: Business leg originate -> answer.
      - `business_answered_at` string, date-time, nullable
      - `briefing_ms` integer, nullable: Business answer -> consumer originate: the AI's time on the business leg.
      - `consumer_ring_ms` integer, nullable: Consumer leg originate -> answer.
      - `consumer_answered_at` string, date-time, nullable
      - `consumer_wait_ms` integer, nullable: Dead air the consumer actually heard: their answer -> their own leg being bridged. Measured on the consumer leg, because Telnyx emits one call.bridged PER LEG.
      - `consumer_bridged_at` string, date-time, nullable
      - `talk_ms` integer, nullable: Consumer bridged -> first hangup on either leg.
      - `business_leg_ms` integer, nullable
      - `consumer_leg_ms` integer, nullable
      - `total_ms` integer, nullable: First to last Telnyx event on the session.
      - `first_event_at` string, date-time, nullable
      - `last_event_at` string, date-time, nullable
      - `hangup_first_leg` 'business' | 'consumer', nullable: Which leg hung up first, or null if neither reported a hangup.
    - `recording` BridgeRecording: Recording METADATA. This API never serves audio: audio_available is always false, the stored links are expired presigned URLs and their signatures are stripped. Obtaining audio requires a live re-mint through Telnyx GET /v2/recordings/{id}, an operator action. A saved recording covers the agent-to-business leg only: recording stops BEFORE the consumer is dialed, so it never contains the consumer.
      - `saved_events` integer, required: How many recording-saved events this session produced. 0 on almost every call, recording is off by default and additionally requires consent.
      - `started_at` string, date-time, nullable
      - `ended_at` string, date-time, nullable
      - `duration_ms` integer, nullable
      - `format` string, nullable
      - `channels` string, nullable
      - `audio_available` boolean, required: Always false from this endpoint.
      - `note` string, required
    - `transcript` object, nullable: Text of the AI<->dealership call leg only, the consumer is NEVER recorded or transcribed (recording stops before the consumer is dialed). Present only when the recording + transcript features are enabled for your account AND a lawful recording exists for this call; otherwise null. Never carries the audio URL. Pull-only via this endpoint, it is not included in the usage feed or partner webhooks.
      - `text` string, nullable: Speaker-labeled transcript, e.g. "Agent: ...\nDealership: ...".
      - `status` 'pending' | 'ready' | 'failed': Transcription state; text is meaningful only when "ready".
      - `transcribed_at` string, date-time, nullable

## Other responses

- `401`: Invalid or missing credential
- `404`: Not found

---

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