> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Shared Line Appearance (SLA)

> Mirror one line onto a whole roster of phones — an inbound call rings every member, anyone answers, and a colleague can grab the answered call onto their own device. Covers line creation, member management, live status polling, grab, and inbound DID routing.

Shared Line Appearance (SLA) is the deskphone-replacement pattern for receptionist and executive-assistant deployments: one named line is mirrored onto every phone on its roster, an inbound call rings all member devices at once, the first member to pick up owns the call, and anyone on the roster can move it onto their own device later. Law firms, clinics, and executive suites replacing a legacy key system (Cisco, Avaya, Mitel) reach for SLA first. Every part of the lifecycle is in the Voice → Shared lines API — creation, roster management, live status, and the grab action.

Inbound routing to SLA is done per DID — see [section 6](#6-point-a-did-at-the-line). Everything on this page is inbound termination and device-to-device mediation; outbound calling is unaffected.

## 1. When to use SLA

Use a shared line when a team answers one identity together and anyone on the team may take over a live call:

* **Reception desk** — the company's main number rings on every device at the front desk; whoever is free answers.
* **Executive assistant** — the exec's line appears on the assistant's phone; the assistant screens, answers, and can hand the call to the exec without a transfer sequence.
* **Coverage pairs** — two agents covering the same window see the other's line go busy and grab it when one steps away.

SLA is not a queue (no FIFO hold, agents answer simultaneously) and not a ring group (a ring group rings devices, but it has no shared busy indicator and no grab). If you need either of those, see [Pick the right inbound routing for a DID](/guides/inbound-number-routing) for the alternative route types.

## 2. Create a shared line

Create the line first, then add members. `POST /api/v1/voice/shared-lines` requires a role with write access (owner, admin, or developer).

```bash cURL theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/shared-lines" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Reception",
    "description": "Front desk line — rings all reception devices",
    "primaryExtension": "201"
  }'
```

| Field              | Required | Description                                                                                                                                                                |
| ------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`             | yes      | Display name, 1–100 characters. Unique per tenant — a duplicate answers `409`.                                                                                             |
| `primaryExtension` | yes      | The extension the line mirrors, 1–32 characters. This is the line's identity — an internal dial to it is a call on the line, and it cannot repeat on another line (`409`). |
| `description`      | no       | Free-text purpose note, up to 500 characters.                                                                                                                              |
| `status`           | no       | `active` (default) or `inactive`. Only `active` lines ring or allow grabs.                                                                                                 |

The 201 response returns the created line with an empty `members` array — note the `id`, it goes in every later call. `GET /api/v1/voice/shared-lines` lists lines with cursor pagination (`cursor`, `limit` 1–100, default 50) and a per-line `memberCount`; `GET /:id` returns one line with its full roster.

## 3. Manage the member roster

Members are the devices the line rings. `POST /:id/members` and `DELETE /:id/members/:memberId` are write paths (owner/admin/developer).

```bash cURL theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/shared-lines/sharedLine_01h.../members" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "memberKind": "sip_extension",
    "memberValue": "reception-desk-1",
    "canGrab": true,
    "sortOrder": 0
  }'
```

| Field         | Default         | Description                                                                                                                                                                                              |
| ------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `memberKind`  | `sip_extension` | `sip_extension` — `memberValue` names a registered SIP credential username (an extension/device, as created under Voice → Extensions). `user_id` — `memberValue` is an auth user id for softphone users. |
| `memberValue` | —               | The extension username or user id, 1–128 characters.                                                                                                                                                     |
| `canGrab`     | `true`          | Whether this member may pull the answered call onto their device via the grab endpoint. Set `false` for floor devices that should ring but never take a call back.                                       |
| `sortOrder`   | next position   | Ring/display order, 0–999. Omitted = appended after the current roster, so desk phones render in a stable order.                                                                                         |

Shape rules enforced at write time:

* A `sip_extension` member must reference a live SIP credential for your organization — an unknown username answers `422` naming the value. `user_id` members are not existence-checked upfront; the fork layer resolves them at call time.
* Each member value can appear once per line — a double-add answers `409` (otherwise the fork would invite the same phone twice).
* The roster is capped at 50 members — the 51st answers `409`.
* Removing the last member is allowed; the line simply rings nothing until you re-add one (the DID's fallback route then covers it — see [section 6](#6-point-a-did-at-the-line)).

Remove a member:

```bash cURL theme={null}
curl -X DELETE "https://api.orbit.devotel.io/api/v1/voice/shared-lines/sharedLine_01h.../members/sharedLineMember_01h..." \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

Delete returns `204`. Deleting the whole line (`DELETE /api/v1/voice/shared-lines/:id`) removes its roster too — there is no soft delete.

## 4. Poll live status

`GET /:id/status` is a read-only, side-effect-free endpoint the Shared Lines dashboard page polls to draw the busy lamp and decide whether to offer Grab. Front-desk roles can read it (owner, admin, developer, agent).

```bash cURL theme={null}
curl "https://api.orbit.devotel.io/api/v1/voice/shared-lines/sharedLine_01h.../status" \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

```json 200 — line idle theme={null}
{
  "sharedLineId": "sharedLine_01h...",
  "activeCall": null
}
```

```json 200 — line busy theme={null}
{
  "sharedLineId": "sharedLine_01h...",
  "activeCall": {
    "callSid": "c9d4a...",
    "onDeviceUsername": "reception-desk-1",
    "startedAt": "2026-08-30T14:05:12.301Z",
    "direction": "inbound"
  }
}
```

`activeCall` semantics — the busy lamp is accurate on purpose:

* `activeCall: null` means the line is idle. Only a call that actually arrived on the line (dialed to its `primaryExtension`, or arrives via a DID routed to this line) marks it busy. A member's private or outbound call on their own phone never lights the line up.
* `onDeviceUsername` is the SIP username of the member device currently holding the call. It is `null` when the line demonstrably has a call but the answering device hasn't been correlated yet (mid-bridge) — the field is never a guess.
* Poll on a short interval (the dashboard does); the endpoint is safe to hit repeatedly.

## 5. Grab an in-use call

Grab moves the line's currently answered call from whichever device answered onto your own device — the receptionist-assistant handoff without a transfer menu. `POST /:id/grab` is a live-call action open to agent role as well as operators.

```bash cURL theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/shared-lines/sharedLine_01h.../grab" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "grabbingMemberValue": "exec-office-desk" }'
```

`grabbingMemberValue` must be a `sip_extension` member of this line's roster with `canGrab` true — the receiving device, not the device that answered.

The full walkthrough:

1. **Line is active (409 otherwise).** An `inactive` line cannot be grabbed.
2. **You must be on the roster with grab permission (403).** A `grabbingMemberValue` that isn't a `sip_extension` member of this line, or has `canGrab: false`, is rejected with `403` — the membership flag is the gate, not just the roster.
3. **The line must have an answered call (404 otherwise).** Grabbing while `activeCall` is null answers `404`. Only a call on the line itself can be grabbed; a member's private call is invisible to this endpoint, so a private call can never be pulled onto a colleague's phone.
4. **You can't grab the device already holding it (409).** If `onDeviceUsername` was resolved and equals your `grabbingMemberValue`, the request is a no-op `409`.
5. **Only one grabber wins (dedup).** Claims race on a short atomic window, so two members hitting Grab in the same instant get one `200` and one `409`. A retry of the same request id is equally safe — it reports conflict instead of double-moving the call.

A successful grab returns the resolved pair:

```json 200 theme={null}
{
  "sharedLineId": "sharedLine_01h...",
  "callSid": "c9d4a...",
  "fromDeviceUsername": "reception-desk-1",
  "toDeviceUsername": "exec-office-desk"
}
```

This endpoint resolves and claims the call; the call plane completes the actual SIP re-INVITE to move the media. Your device rings and the caller stays connected throughout.

## 6. Point a DID at the line

A shared line has no inbound traffic until a DID routes to it. On the number's routing config, set `type: "shared_line"` with `config.sharedLineId` — the same picker under Numbers → *number* → Routing and the same API as every other route type (full guide: [Pick the right inbound routing for a DID](/guides/inbound-number-routing)).

```bash cURL theme={null}
curl -X PUT "https://api.orbit.devotel.io/api/v1/numbers/+14155550123/routing" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "shared_line",
    "config": { "sharedLineId": "sharedLine_01h...", "ringTimeoutSec": 60 },
    "fallback_type": "voicemail",
    "fallback_config": { "maxDurationSec": 120 }
  }'
```

`config.ringTimeoutSec` is optional (5–120). Set a fallback so a call answers somewhere when the roster is empty or nobody picks up — route-specific notes on fallbacks are in the routing guide. Internal dials straight to the line's `primaryExtension` also reach the line without touching a DID.

## 7. Errors and dedup semantics

| Endpoint                     | Status | Cause                                                       | Handling                                                                                                  |
| ---------------------------- | ------ | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| POST lines / PATCH           | `409`  | `name` or `primaryExtension` already exists on another line | Rename or pick a new extension; retry safe.                                                               |
| POST lines / PATCH / members | `400`  | Body fails validation                                       | Response carries per-field `details`; fix and re-send.                                                    |
| GET/PATCH/DELETE / members   | `404`  | Unknown line or member id                                   | Re-fetch `GET /` list for the current ids.                                                                |
| POST members                 | `422`  | `sip_extension` username not registered for your org        | Create the extension under Voice → Extensions first (see [Extensions vs. Colleagues](/voice/extensions)). |
| POST members                 | `409`  | Member already on this line, or roster cap (50) reached     | Treat as idempotent — the roster is as desired.                                                           |
| POST grab                    | `409`  | Line inactive, grabbing self, or two members raced          | Check `GET /:id/status` first; a raced grab means someone else already holds it.                          |
| POST grab                    | `403`  | Not a roster member, or `canGrab` false                     | Add the member with `canGrab: true`.                                                                      |
| POST grab                    | `404`  | No answered call on the line                                | Poll status before offering Grab, or disable the action while `activeCall` is null.                       |

The grab claim is atomic (a short-window dedup slot, same primitive used across the live-call endpoints), so safe-retry and racing-members semantics hold: one winner, conflict for the rest, and no double-move.

***

**See also:** [Pick the right inbound routing for a DID](/guides/inbound-number-routing) · [Extensions vs. Colleagues](/voice/extensions) · [Paging, ring groups, and call park](/voice/paging-ring-groups-call-park)
