> ## 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.

# SIP Credentials API

> SIP Credentials endpoints exposed by the Devotel CPaaS API

# SIP Credentials API

SIP Credentials endpoints exposed by the Devotel CPaaS API

**Base path:** `/api/v1/sip-credentials`

**Endpoint count:** 7

***

### List SIP credentials

<Note>
  `GET /api/v1/sip-credentials`
</Note>

Returns a cursor-paginated list of SIP credentials (softphone registrations) for the authenticated organization. Each credential grants a SIP user agent access to register, route inbound calls, and optionally make outbound calls. Excludes plaintext passwords. Developer, viewer, admin, and owner roles can read; only admin/owner can create/modify.

<ParamField header="X-Test-Mode" type="string (enum: true|false)">
  Sandbox opt-in for Clerk-session-authenticated requests. Set to `true` to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response `meta.test_mode: true`. **Ignored for live API keys (`dv_live_sk_*`)** — server-to-server clients must use a test-prefixed key (`dv_test_sk_*`) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X GET "https://api.orbit.devotel.io/api/v1/sip-credentials" \
      -H "X-API-Key: dv_live_sk_your_key_here" 
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel-orbit/node'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://api.orbit.devotel.io/api/v1/sip-credentials', {
      method: 'GET',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
      },
    })
    console.log(await res.json())


    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    r = requests.get("https://api.orbit.devotel.io/api/v1/sip-credentials", headers=headers)
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("GET", "https://api.orbit.devotel.io/api/v1/sip-credentials", nil)
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))

    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://api.orbit.devotel.io/api/v1/sip-credentials')
    req = Net::HTTP::Get.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']


    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://api.orbit.devotel.io/api/v1/sip-credentials');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),

    ]);

    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {}
  ```
</ResponseExample>

***

### Get a SIP credential

<Note>
  `GET /api/v1/sip-credentials/{id}`
</Note>

Returns a single SIP credential by id, including all settings and metadata but excluding the plaintext password (already shown at creation). Use this to view the current state for configuration or troubleshooting. Developer, viewer, admin, and owner roles can read.

<ParamField path="id" type="string" required>
  —
</ParamField>

<ParamField header="X-Test-Mode" type="string (enum: true|false)">
  Sandbox opt-in for Clerk-session-authenticated requests. Set to `true` to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response `meta.test_mode: true`. **Ignored for live API keys (`dv_live_sk_*`)** — server-to-server clients must use a test-prefixed key (`dv_test_sk_*`) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X GET "https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3" \
      -H "X-API-Key: dv_live_sk_your_key_here" 
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel-orbit/node'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3', {
      method: 'GET',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
      },
    })
    console.log(await res.json())


    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    r = requests.get("https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3", headers=headers)
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("GET", "https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3", nil)
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))

    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3')
    req = Net::HTTP::Get.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']


    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),

    ]);

    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {}
  ```
</ResponseExample>

***

### Create a new SIP credential

<Note>
  `POST /api/v1/sip-credentials`
</Note>

Issues a new SIP credential for a softphone or desk phone. Returns the plaintext password exactly once; save it immediately as it is not recoverable. The credential is identified by a user-friendly label and a generated username. Configure your SIP client with the username, password, realm, and edge host returned in the response. Admin or owner only.

<ParamField header="Idempotency-Key" type="string">
  Stripe-style idempotency token. Pass a stable, client-generated value (1-255 chars) to dedupe retries on transient timeouts. The same key+credential+path replays the original response for 24h on 2xx (5min on 4xx, 30s on 5xx). Returns 409 if a concurrent request with the same key is already in flight; replayed responses include the `Idempotency-Replay: true` response header.
</ParamField>

<ParamField header="X-Test-Mode" type="string (enum: true|false)">
  Sandbox opt-in for Clerk-session-authenticated requests. Set to `true` to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response `meta.test_mode: true`. **Ignored for live API keys (`dv_live_sk_*`)** — server-to-server clients must use a test-prefixed key (`dv_test_sk_*`) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.
</ParamField>

<ParamField body="label" type="string">
  Required. Device label, 1-32 characters. Lowercased and slugified into the SIP username (`&lt;org-prefix&gt;-&lt;label&gt;`), which stays stable across password rotations.
</ParamField>

<ParamField body="extensionNumber" type="string">
  Internal extension, 2-8 characters of digits, `*` or `#`. Unique per organization.
</ParamField>

<ParamField body="outboundEnabled" type="boolean">
  Whether the device may place outbound calls. Defaults to true.
</ParamField>

<ParamField body="outboundCallerIdMode" type="string (enum: fixed|pick_from_owned|inherit_org)">
  How the From: number is chosen on outbound calls: `fixed` always uses `defaultCallerIdE164`, `pick_from_owned` lets the device send any number you own, `inherit_org` uses the organization default. Defaults to `fixed`.
</ParamField>

<ParamField body="defaultCallerIdE164" type="string">
  E.164 number presented as the caller ID, for example `+14155550123`. Must be a number your organization owns, otherwise the request is rejected with 403.
</ParamField>

<ParamField body="outboundDailySpendCapCents" type="integer">
  Daily outbound spend ceiling for this device, in cents (0-100000). Omit for no cap.
</ParamField>

<ParamField body="outboundBlockedCountries" type="string[]">
  ISO 3166-1 alpha-2 country codes this device may not dial, for example `RU` or `KP`. Up to 50 entries.
</ParamField>

<ParamField body="concurrentCallCap" type="integer">
  Maximum simultaneous calls for this device (1-100). Omit for no cap.
</ParamField>

<ParamField body="expiresAt" type="string">
  ISO 8601 timestamp after which the credential stops authenticating, for example `2027-01-31T00:00:00Z`. Useful for contractor or seasonal devices.
</ParamField>

<ParamField body="dnd" type="boolean">
  Create the device in do-not-disturb, so inbound calls skip it. Defaults to false.
</ParamField>

<ParamField body="forceCodec" type="string (enum: PCMU|PCMA|OPUS|G722|AMR-WB)">
  Pin media to a single codec instead of negotiating. Leave unset unless the handset misbehaves during negotiation.
</ParamField>

<ParamField body="forceRecordInbound" type="boolean">
  Always record inbound calls to this device.
</ParamField>

<ParamField body="forceRecordOutbound" type="boolean">
  Always record outbound calls from this device.
</ParamField>

<ParamField body="forwardBusyToUsername" type="string">
  SIP username in the same organization that inbound calls ring when this device is busy.
</ParamField>

<ParamField body="forwardNoAnswerToUsername" type="string">
  SIP username in the same organization that inbound calls ring when this device does not answer.
</ParamField>

<ParamField body="forwardUnavailableToUsername" type="string">
  SIP username in the same organization that inbound calls ring when this device is not registered.
</ParamField>

<ParamField body="allowedIpCidrs" type="string[]">
  Source IP allow-list in CIDR notation, up to 20 entries. Omit to accept registrations from any address.
</ParamField>

<ParamField body="allowedUserAgent" type="string">
  Lock the credential to one SIP User-Agent string, for example `Bria 6`. Registrations from any other client are refused.
</ParamField>

<ParamField body="voicemailPin" type="string">
  4-8 digit passcode the handset must key in on the `*97` voicemail dial-in. Stored hashed and never returned; the read endpoints only report whether one is set.
</ParamField>

<ParamField body="notes" type="string">
  Free-text note kept with the credential, up to 1000 characters.
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://api.orbit.devotel.io/api/v1/sip-credentials" \
      -H "X-API-Key: dv_live_sk_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{}'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel-orbit/node'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://api.orbit.devotel.io/api/v1/sip-credentials', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({}),
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    headers["Content-Type"] = "application/json"
    r = requests.post("https://api.orbit.devotel.io/api/v1/sip-credentials", headers=headers, json={})
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/sip-credentials", bytes.NewBuffer([]byte(`{}`)))
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
    	req.Header.Set("Content-Type", "application/json")
    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://api.orbit.devotel.io/api/v1/sip-credentials')
    req = Net::HTTP::Post.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']
    req['Content-Type'] = 'application/json'
    req.body = {}.to_json
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://api.orbit.devotel.io/api/v1/sip-credentials');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),
      'Content-Type: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
    {}
    JSON);
    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

<ResponseExample>
  ```json 201 theme={null}
  {}
  ```
</ResponseExample>

***

### Rotate a SIP credential password

<Note>
  `POST /api/v1/sip-credentials/{id}/rotate`
</Note>

Generates a new password for an existing SIP credential, invalidating the old one immediately. The username remains the same so the credential does not need to be reconfigured in the user agent — only the password field changes. Returns the new plaintext password exactly once. Admin or owner only.

<ParamField path="id" type="string" required>
  —
</ParamField>

<ParamField header="Idempotency-Key" type="string">
  Stripe-style idempotency token. Pass a stable, client-generated value (1-255 chars) to dedupe retries on transient timeouts. The same key+credential+path replays the original response for 24h on 2xx (5min on 4xx, 30s on 5xx). Returns 409 if a concurrent request with the same key is already in flight; replayed responses include the `Idempotency-Replay: true` response header.
</ParamField>

<ParamField header="X-Test-Mode" type="string (enum: true|false)">
  Sandbox opt-in for Clerk-session-authenticated requests. Set to `true` to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response `meta.test_mode: true`. **Ignored for live API keys (`dv_live_sk_*`)** — server-to-server clients must use a test-prefixed key (`dv_test_sk_*`) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3/rotate" \
      -H "X-API-Key: dv_live_sk_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{}'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel-orbit/node'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3/rotate', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({}),
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    headers["Content-Type"] = "application/json"
    r = requests.post("https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3/rotate", headers=headers, json={})
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3/rotate", bytes.NewBuffer([]byte(`{}`)))
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
    	req.Header.Set("Content-Type", "application/json")
    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3/rotate')
    req = Net::HTTP::Post.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']
    req['Content-Type'] = 'application/json'
    req.body = {}.to_json
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3/rotate');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),
      'Content-Type: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
    {}
    JSON);
    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {}
  ```
</ResponseExample>

***

### Force unregister a SIP credential

<Note>
  `POST /api/v1/sip-credentials/{id}/unregister`
</Note>

Evicts active SIP REGISTER bindings for a credential, disconnecting any currently-registered user agents immediately. The next REGISTER from the device (default \~60s) will recreate the binding. To permanently disconnect, combine this with a PATCH to set enabled=false. Useful when a device is stolen/compromised or stuck due to NAT edge cases. Admin or owner only.

<ParamField path="id" type="string" required>
  —
</ParamField>

<ParamField header="Idempotency-Key" type="string">
  Stripe-style idempotency token. Pass a stable, client-generated value (1-255 chars) to dedupe retries on transient timeouts. The same key+credential+path replays the original response for 24h on 2xx (5min on 4xx, 30s on 5xx). Returns 409 if a concurrent request with the same key is already in flight; replayed responses include the `Idempotency-Replay: true` response header.
</ParamField>

<ParamField header="X-Test-Mode" type="string (enum: true|false)">
  Sandbox opt-in for Clerk-session-authenticated requests. Set to `true` to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response `meta.test_mode: true`. **Ignored for live API keys (`dv_live_sk_*`)** — server-to-server clients must use a test-prefixed key (`dv_test_sk_*`) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3/unregister" \
      -H "X-API-Key: dv_live_sk_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{}'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel-orbit/node'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3/unregister', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({}),
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    headers["Content-Type"] = "application/json"
    r = requests.post("https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3/unregister", headers=headers, json={})
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3/unregister", bytes.NewBuffer([]byte(`{}`)))
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
    	req.Header.Set("Content-Type", "application/json")
    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3/unregister')
    req = Net::HTTP::Post.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']
    req['Content-Type'] = 'application/json'
    req.body = {}.to_json
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3/unregister');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),
      'Content-Type: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
    {}
    JSON);
    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {}
  ```
</ResponseExample>

***

### Update a SIP credential

<Note>
  `PATCH /api/v1/sip-credentials/{id}`
</Note>

Modifies settings on an existing SIP credential — for example to enable/disable it, set call limits, configure call forwarding, or manage voicemail PIN. Username is immutable. Password is rotated separately via the /rotate endpoint. Send only the fields you wish to change; omitted fields are left untouched. Admin or owner only.

<ParamField path="id" type="string" required>
  —
</ParamField>

<ParamField header="X-Test-Mode" type="string (enum: true|false)">
  Sandbox opt-in for Clerk-session-authenticated requests. Set to `true` to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response `meta.test_mode: true`. **Ignored for live API keys (`dv_live_sk_*`)** — server-to-server clients must use a test-prefixed key (`dv_test_sk_*`) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.
</ParamField>

<ParamField body="label" type="string">
  Device label, 1-32 characters. Renaming does not change the SIP username, so registered devices keep working.
</ParamField>

<ParamField body="enabled" type="boolean">
  Set false to refuse further registrations and inbound calls without deleting the credential; set true to re-enable it.
</ParamField>

<ParamField body="extensionNumber" type="string | null">
  Internal extension, 2-8 characters of digits, `*` or `#`. Send `null` to remove the extension.
</ParamField>

<ParamField body="outboundEnabled" type="boolean">
  Whether the device may place outbound calls.
</ParamField>

<ParamField body="outboundCallerIdMode" type="string (enum: fixed|pick_from_owned|inherit_org)">
  How the From: number is chosen on outbound calls: `fixed`, `pick_from_owned` or `inherit_org`.
</ParamField>

<ParamField body="defaultCallerIdE164" type="string | null">
  E.164 caller ID, for example `+14155550123`. Must be a number your organization owns. Send `null` to clear it.
</ParamField>

<ParamField body="outboundDailySpendCapCents" type="integer | null">
  Daily outbound spend ceiling in cents (0-100000). Send `null` to remove the cap.
</ParamField>

<ParamField body="outboundBlockedCountries" type="array | null">
  ISO 3166-1 alpha-2 country codes this device may not dial. Send `null` to allow every destination your organization allows.
</ParamField>

<ParamField body="concurrentCallCap" type="integer | null">
  Maximum simultaneous calls (1-100). Send `null` to remove the cap.
</ParamField>

<ParamField body="expiresAt" type="string | null">
  ISO 8601 timestamp after which the credential stops authenticating. Send `null` so it never expires.
</ParamField>

<ParamField body="dnd" type="boolean">
  Do-not-disturb. Inbound calls skip the device while this is true; the change applies to the next call, not just new registrations.
</ParamField>

<ParamField body="forceCodec" type="string | null">
  Pin media to one of `PCMU`, `PCMA`, `OPUS`, `G722` or `AMR-WB`. Send `null` to negotiate normally.
</ParamField>

<ParamField body="forceRecordInbound" type="boolean">
  Always record inbound calls to this device.
</ParamField>

<ParamField body="forceRecordOutbound" type="boolean">
  Always record outbound calls from this device.
</ParamField>

<ParamField body="forwardBusyToUsername" type="string | null">
  SIP username in the same organization to ring when this device is busy. Send `null` to stop forwarding.
</ParamField>

<ParamField body="forwardNoAnswerToUsername" type="string | null">
  SIP username in the same organization to ring when this device does not answer. Send `null` to stop forwarding.
</ParamField>

<ParamField body="forwardUnavailableToUsername" type="string | null">
  SIP username in the same organization to ring when this device is not registered. Send `null` to stop forwarding.
</ParamField>

<ParamField body="allowedIpCidrs" type="array | null">
  Source IP allow-list in CIDR notation, up to 20 entries. Send `null` to accept registrations from any address.
</ParamField>

<ParamField body="allowedUserAgent" type="string | null">
  Lock the credential to one SIP User-Agent string. Send `null` to accept any client.
</ParamField>

<ParamField body="voicemailPin" type="string | null">
  4-8 digit passcode for the `*97` voicemail dial-in. Send a new value to rotate it, or `null` to remove it. Stored hashed and never returned.
</ParamField>

<ParamField body="notes" type="string | null">
  Free-text note, up to 1000 characters. Send `null` to clear it.
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X PATCH "https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3" \
      -H "X-API-Key: dv_live_sk_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{}'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel-orbit/node'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3', {
      method: 'PATCH',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({}),
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    headers["Content-Type"] = "application/json"
    r = requests.patch("https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3", headers=headers, json={})
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("PATCH", "https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3", bytes.NewBuffer([]byte(`{}`)))
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
    	req.Header.Set("Content-Type", "application/json")
    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3')
    req = Net::HTTP::Patch.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']
    req['Content-Type'] = 'application/json'
    req.body = {}.to_json
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),
      'Content-Type: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
    {}
    JSON);
    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {}
  ```
</ResponseExample>

***

### Delete a SIP credential

<Note>
  `DELETE /api/v1/sip-credentials/{id}`
</Note>

Soft-deletes a SIP credential, immediately disabling it and preventing any further SIP REGISTERs from the associated user agent. The credential is marked deleted but remains in the audit trail. Hard-delete is not exposed for security. Admin or owner only. Returns 204 No Content.

<ParamField path="id" type="string" required>
  —
</ParamField>

<ParamField header="X-Test-Mode" type="string (enum: true|false)">
  Sandbox opt-in for Clerk-session-authenticated requests. Set to `true` to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response `meta.test_mode: true`. **Ignored for live API keys (`dv_live_sk_*`)** — server-to-server clients must use a test-prefixed key (`dv_test_sk_*`) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X DELETE "https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3" \
      -H "X-API-Key: dv_live_sk_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{}'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel-orbit/node'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3', {
      method: 'DELETE',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({}),
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    headers["Content-Type"] = "application/json"
    r = requests.delete("https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3", headers=headers, json={})
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("DELETE", "https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3", bytes.NewBuffer([]byte(`{}`)))
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
    	req.Header.Set("Content-Type", "application/json")
    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3')
    req = Net::HTTP::Delete.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']
    req['Content-Type'] = 'application/json'
    req.body = {}.to_json
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://api.orbit.devotel.io/api/v1/sip-credentials/num_8fb2d1b7c00c4ec9a1d3f5e7b9c1d3');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),
      'Content-Type: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
    {}
    JSON);
    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

**Response:** `204 No Content`

***
