> ## Documentation Index
> Fetch the complete documentation index at: https://developers.telnyx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Other APIs llms-full.txt

> Complete machine-readable documentation content for the Other APIs section for AI agents and LLMs

# Telnyx Other APIs — Full Documentation

> Account and billing, video, fax, SETI, OAuth, and platform APIs. Complete page content for the Other APIs section of the Telnyx developer docs ([https://developers.telnyx.com](https://developers.telnyx.com)).
> This file: [https://developers.telnyx.com/docs/development/llms/other-apis-llms-full-txt.md](https://developers.telnyx.com/docs/development/llms/other-apis-llms-full-txt.md) · Root index: [https://developers.telnyx.com/llms.txt](https://developers.telnyx.com/llms.txt)

## Subsections

Focused per-subsection full-content files:

* [Account & Billing](https://developers.telnyx.com/docs/development/llms/other-apis-account-billing-llms-full-txt.md)
* [Video](https://developers.telnyx.com/docs/development/llms/other-apis-video-llms-full-txt.md)
* [SETI](https://developers.telnyx.com/docs/development/llms/other-apis-seti-llms-full-txt.md)
* [Fax](https://developers.telnyx.com/docs/development/llms/other-apis-fax-llms-full-txt.md)
* [OAuth](https://developers.telnyx.com/docs/development/llms/other-apis-oauth-llms-full-txt.md)

## Account & Billing

### Usage Reports

> Source: [https://developers.telnyx.com/docs/reporting/usage-reports.md](https://developers.telnyx.com/docs/reporting/usage-reports.md)

## Overview

The (v2) Usage Reports API is a single endpoint that enables viewing aggregated usage data across all of a customer's Telnyx products. It is built to be flexible and customizable, so that you have programmatic access to view data in a way that makes sense to your business and use cases. It can be used to efficiently monitor usage trends and costs, as well as to directly integrate with your internal systems.

### Query Requirements

#### Products

A product from the Options response (see next section) must be included. At this time, there is not a way to view usage for all or multiple products at once.

Products Supported:

* Sip-Trunking (sip-trunking)
* Messaging (messaging)
* Voice API (call-control)
* Wireless (wireless)
* Cloud Storage (cloud-storage)
* Inference (inference)
* Verify 2fa (verify-2fa)
* Fax API (fax-api)
* WebRTC (webrtc)
* Calls per Second (cps)
* Conference (conference)
* Video API (programmable-video)
* Call Control Features (call-control-features)
* Customer Service Record (customer-service-record)
* Media Streaming (media-streaming)
* Noise Suppression (noise-suppression)
* Text to Speech (text-to-speech)
* Speech to Text (speech-to-text)
* Recording (recording)
* Forking (forking)
* Media Storage (media-storage)
* Answering Machine Detection (amd)

#### Dates

The start and end date parameters should be in the format `YYYY-MM-DDThh:mm:ssTZD` (eg `2022-12-24T19:20:30-05:00`). This allows you to specify the timespan in your local time by including the timezone offset. The `start_date` parameter is inclusive, while the `end_date` parameter is exclusive - e.g. if you wanted data for all of February, the `start_date` would be `2023-02-01T00:00:00-00:00` and the `end_date` would be `2023-03-01T00:00:00-00:00`. Billing usage is calculated using UTC. The maximum range supported is 31 days.

Another way to specify the timespan you want is to utilize the date literals spelled out in our developer docs - [https://developers.telnyx.com/api#date-literals](https://developers.telnyx.com/api#date-literals). This can be achieved by replacing `start_date` & `end_date` with `date_range` like this: `date_range=last_1_weeks`.

To see the requested data broken out by day for the timespan selected, there is a `date` dimension available on all products. Additionally, there’s a breakout by hour available using the `date_time` dimension.

#### Format

Specifying a format is optional. The endpoint returns JSON by default. At this time, the API also supports generating a CSV file from the response by specifying `format=csv` in your query.

## Dimensions & Metrics Options Per Product

To view the products with their dimensions and metrics available for reporting, you can make a request to the endpoint below.

Metrics are the values you’re interested in, for example, the number of calls attempted or the total cost of your usage. Dimensions are how you want your data broken out, for example, by connection, messaging profile, or the traffic’s direction.

Below, you’ll see a sample of the “Options” response for Sip-Trunking. If you already know the product you want and are looking for only its supported dimensions/metrics, you can pass the product parameter as shown in the example. If you want to see all products, simply leave off the product parameter.

##### Request:

*Don't forget to update `YOUR_API_KEY` here.*

```
curl --location 'https://api.telnyx.com/v2/usage_reports/options?product=sip-trunking' \
--header 'Authorization: Bearer YOUR_API_KEY
```

##### Response:

```json theme={null}
{
    "data": [
        {
            "product": "sip-trunking",
            "product_dimensions": [
                "date",
                "tn_type",
                "tags",
                "billing_group_id",
                "country_code",
                "outbound_profile_id",
                "source_tn_type",
                "date_time",
                "connection_id",
                "bundle_id",
                "short_duration_call",
                "source_country_code",
                "currency",
                "is_local_calling",
                "call_type",
                "direction"
            ],
            "product_metrics": [
                "connected",
                "cost",
                "attempted",
                "call_sec",
                "completed",
                "billed_sec"
            ],
            "record_types": null
        }
    ]
}
```

## Product Examples

### SIP Trunking

#### Example 1: Simple SIP Report

Let’s say we’re interested in our sip-trunking usage for the week of January 23rd. And let’s say we want to see the total calls connected versus calls attempted and the total cost. And we need these numbers broken out by direction and country. We first make a query to the Options endpoint listed above to make sure those requirements are all supported and to see how to query for them.

These are our requirements shown next to how we’d construct our query for them:

Product: sip-trunking
product=sip-trunking
Date(s): January 23rd - 28th 2024
start\_date=2024-01-23T00:00:00-00:00end\_date=2024-01-29T00:00:00-00:00
Metrics: calls connected, calls attempted, cotal cost
metrics=connected,attempted,cost
Broken out by destination country*Note: Since we’re filtering on the direction, we must include it.*
dimensions=country\_code,direction
Outbound only
filter\[direction]=outbound
API Response’s Page Size/Number
page\[number]=1page\[size]=20

##### Request:

```
curl --location --request GET 'https://api.telnyx.com/v2/usage_reports?product=sip-trunking&start_date=2024-01-23T00:00:00-00:00&end_date=2024-01-29T00:00:00-00:00&metrics=connected,attempted,cost&dimensions=direction,country_code&filter[direction]=outbound&page[number]=1&page[size]=20' \
--header 'Authorization: Bearer YOUR_TELNYX_KEY'
```

##### Response:

From the response below, we can see that during the week in question, our account only had traffic to the US (country code = 1). Our connected versus attempted is 883,930 vs 1,245,811 calls respectively. And the total cost for those outbound calls was 1911.624 (*Note: If you had wanted the currency, that is another dimension that can be added in the query with* `dimensions=currency`).

```json theme={null}
{
  "data": [
    {
       "country_code": "1",
       "direction": "outbound",
       "product": "sip-trunking",
       "connected": 883930.0,
       "cost": 1911.6248,
       "attempted": 1245811.0
    }
  ],
  "meta": {
    "page_size": 20,
    "page_number": 1,
    "total_results": 1,
    "total_pages": 1
  }
}
```

#### Example 2: Short Duration Calls

Telnyx enforces automatic surcharges for customer accounts whose short duration calls (greater than 0 and less than or equal to 6 seconds) total 15% or more of their outbound traffic. As someone running SIP traffic, I may want to know if I’m in danger of receiving the surcharge at the end of the month. We have exposed a metric solely for the purpose of this calculation.

Product: sip-trunking
product=sip-trunking
Date(s): January 2024
start\_date=2024-01-01T00:00:00-00:00end\_date=2024-02-01T00:00:00-00:00
Metrics: calls completed
metrics=completed
Broken out by SDC (short duration call: true or false)*Note: Since we’re filtering on the direction, we must include it.*
dimensions=short\_duration\_call,direction
Outbound only
filter\[direction]=outbound
API Response’s Page Size/Number
page\[number]=1page\[size]=20

##### Request:

```
curl --location -g --request GET 'https://api.telnyx.com/v2/usage_reports?product=sip-trunking&start_date=2024-01-01T00:00:00-00:00&end_date=2024-02-01T00:00:00-00:00&metrics=completed&dimensions=direction,short_duration_call&filter[direction]=outbound&page[number]=1&page[size]=20' \
--header 'Authorization: Bearer YOUR_TELNYX_KEY'
```

##### Response:

From the response below, we can determine the percentage of our traffic that is currently considered short duration: `414515 / (414515 + 522094) = 44%`. If we wanted to take this a step further, we could see which connections are the biggest offenders by adding “connection\_id” to the dimensions list.

```json theme={null}
{
  "data": [
    {
       "short_duration_call": "false",
       "direction": "outbound",
       "product": "sip-trunking",
       "completed": 522094.0
    },
    {
       "short_duration_call": "true",
       "direction": "outbound",
       "product": "sip-trunking",
       "completed": 414515.0
    }
  ]
  ,
  "meta": {
    "page_size": 20,
    "page_number": 1,
    "total_results": 2,
    "total_pages": 1
  }
}
```

### Messaging

#### Example 1: Simple Messaging Report

In this example, we’ll put together a simple request to view our Messaging usage for a single day.

Product: messaging
product=messaging
Date(s): Today
date\_range=today
Metrics: total cost, total messages sent, total message parts
metrics=cost,count,parts
Broken out by Carrier*Note: Since we’re filtering on the direction, we must include it.*
dimensions=short\_duration\_call,direction
Outbound only
filter\[direction]=outbound
API Response’s Page Size/Number
page\[number]=1page\[size]=20

##### Request:

```
curl --location -g --request GET 'https://api.telnyx.com/v2/usage_reports?product=messaging&dimensions=direction,normalized_carrier&metrics=cost,parts,count&date_range=today&filter[direction]=outbound&page[number]=1&page[size]=20' \
--header 'Authorization: Bearer YOUR_TELNYX_KEY'
```

##### Response (abbreviated):

```json theme={null}
{
  "data": [
    ...
    {
      "normalized_carrier": "AT&T",
      "direction": "outbound",
      "product": "messaging",
      "cost": 132.4653,
      "parts": 33952.0,
      "count": 18088
    },
    {
      "normalized_carrier": "Verizon Wireless",
      "direction": "outbound",
      "product": "messaging",
      "cost": 205.2036,
      "parts": 48924.0,
      "count": 25171
    }
    ...
  ],
  "meta": {
    "page_size": 20,
    "page_number": 1,
    "total_results": 27,
    "total_pages": 2
  }
}
```

#### Example 2: Alphanumeric by MNC/MCC

This example is a little more complex, and lays out a monthly Messaging report of our Alphanumeric traffic per country, broken out by MCC (Mobile Country Code) and MNC (Mobile Network Code).

Product: messaging
product=messaging
Date(s): January 2024
start\_date=2024-01-01T00:00:00-00:00end\_date=2024-02-01T00:00:00-00:00
Metrics: total cost, total message parts
metrics=cost,parts
Dimensions: mcc, mnc, destination country, status\_v2
dimensions=direction,mcc,mnc,country\_iso,product\_name,status\_v2
Filters: alphanumeric, outbound, billable statuses only
filter\[product\_name]=alphanumeric\_idfilter\[direction]=outboundfilter\[status\_v2]=deliveredfilter\[status\_v2]=delivery\_failedfilter\[status\_v2]=delivery\_unconfirmed

##### Request:

```
curl --location -g --request GET 'https://api.telnyx.com/v2/usage_reports?start_date=2024-01-01T00:00:00-00:00&end_date=2024-02-01T00:00:00-00:00&product=messaging&metrics=parts,cost&dimensions=direction,mcc,mnc,country_iso,product_name,status_v2&filter[product_name]=alphanumeric_id&filter[direction]=outbound&filter[status_v2]=delivered&filter[status_v2]=delivery_failed&filter[status_v2]=delivery_unconfirmed' \
--header 'Authorization: Bearer YOUR_TELNYX_KEY'
```

##### Response (abbreviated):

```json theme={null}
{
  "data": [
    ...
    {
      "status_v2": "delivered",
      "mnc": "01",
      "mcc": "270",
      "product_name": "alphanumeric_id",
      "country_iso": "LU",
      "direction": "outbound",
      "product": "messaging",
      "cost": 9.519,
      "parts": 167.0
    },
    {
      "status_v2": "delivered",
      "mnc": "01",
      "mcc": "272",
      "product_name": "alphanumeric_id",
      "country_iso": "IE",
      "direction": "outbound",
      "product": "messaging",
      "cost": 659.2542,
      "parts": 18623.0
    },
    ...
  ],
  "meta": {
    "page_size": 20,
    "page_number": 1,
    "total_results": 100,
    "total_pages": 5
  }
}
```

***

### Session Analysis

> Source: [https://developers.telnyx.com/docs/reporting/session-analysis.md](https://developers.telnyx.com/docs/reporting/session-analysis.md)

# Session Analysis API Usage Guide

Understand the full cost and event tree for any Telnyx AI or voice session — from a single inference call to complex multi-product conversations.

***

## What is Session Analysis?

Session Analysis gives you a complete view of costs and events for any usage session on the Telnyx platform:

* **Full event tree** — parent events, child events, and their relationships
* **Rollup costs** — per-event costs plus cumulative totals across the session
* **Product linkages** — how different Telnyx products interacted in a single session

This is especially valuable for AI-powered sessions, where a single call can involve multiple Telnyx products — AI Voice Assistant, inference, speech-to-text, text-to-speech, call control, SIP trunking, and more — all layered into one conversation.

### When to Use It

| Use Case                      | Why Session Analysis Helps                                                                                         |
| :---------------------------- | :----------------------------------------------------------------------------------------------------------------- |
| **AI session cost breakdown** | See the full cost tree for an AI assistant call, broken down by inference turns, STT/TTS, and infrastructure costs |
| **Troubleshooting**           | Trace the complete event chain to debug unexpected call flow or billing issues                                     |
| **Usage auditing**            | Verify which products and services were consumed during a session                                                  |
| **Cost optimization**         | Identify which components of an AI session drive the most cost and optimize accordingly                            |

***

## Core Concepts

### Record Types

A **record type** identifies the category of usage event for a Telnyx product. Each record type maps to a specific product and has defined relationships to other record types.

Example: `ai-voice-assistant` is the record type for AI Voice Assistant sessions, which have `inference` events as children.

### Sessions

A **session** is a tree of related usage events. The **root event** is the primary event you're analyzing. **Child events** are related usage that occurred as part of that session.

**Example:** An AI Voice Assistant call involves a `call-session` root with a `call-control` child, which in turn has an `ai-voice-assistant` child with `inference` events underneath — each representing one LLM turn in the conversation.

### Relationships

Events are connected via **relationships**:

| Relationship | Direction      | Example                                                                   |
| :----------- | :------------- | :------------------------------------------------------------------------ |
| `child_of`   | Parent → Child | An `ai-voice-assistant` event has `inference` children (one per LLM turn) |
| `parent_of`  | Child → Parent | An `inference` event has a parent `ai-voice-assistant` session            |

Relationships are defined via **field mappings** — the child's `local_field` matches the parent's `parent_field`. For example, `inference` events link to their `ai-voice-assistant` parent via the `conversation_id` field.

### Cost Rollup

Each event node includes:

* **`event_cost`** — Cost of this individual event
* **`cumulative_cost`** — This event's cost plus all descendant costs

The root event's `cumulative_cost` equals the total session cost. For AI sessions, this means the `call-session` cumulative cost includes everything — the AI assistant, each inference turn, STT, TTS, call control, and SIP trunking — rolled up into one number.

***

## Endpoints

### 1. List All Record Types

```
GET /v2/session_analysis/metadata
```

Returns all available record types, their relationships, and query parameter options.

**Response includes:**

* `record_types[]` — Array of all record types with their parent/child relationships
* `query_parameters` — Valid parameters for session analysis queries
* `meta` — Total count and last updated timestamp

**Example:**

```shell theme={null}
curl "https://api.telnyx.com/v2/session_analysis/metadata" \
 -H "Authorization: Bearer YOUR_API_KEY"
```

### 2. Get Record Type Details

```
GET /v2/session_analysis/metadata/{record_type}
```

Returns metadata for a specific record type with additional details:

* Valid child and parent relationships
* Example query URLs
* Recommended max depth for traversal

**Example:**

```shell theme={null}
curl "https://api.telnyx.com/v2/session_analysis/metadata/ai-voice-assistant" \
 -H "Authorization: Bearer YOUR_API_KEY"
```

### 3. Get Session Analysis

```
GET /v2/session_analysis/{record_type}/{event_id}
```

Retrieves the full session analysis tree for a specific event.

**Path Parameters:**

| Parameter     | Type   | Description                                                                              |
| :------------ | :----- | :--------------------------------------------------------------------------------------- |
| `record_type` | string | The record type identifier (see [Record Types Reference](#record-types-reference) below) |
| `event_id`    | UUID   | The event's unique identifier                                                            |

You can find event IDs in the individual webhooks or detail records for the applicable event.

**Which record type should I use?** If you're analyzing a call made with your AI assistant, start with `call-session`. This gives you the full picture — AI assistant sessions, inference events, call control, SIP trunking, and all other products in one tree. Use the metadata endpoint to explore other starting points for different products.

**Query Parameters:**

| Parameter          | Type     | Default  | Description                                                           |
| :----------------- | :------- | :------- | :-------------------------------------------------------------------- |
| `include_children` | boolean  | `true`   | Include child events in the response                                  |
| `max_depth`        | integer  | `2`      | Maximum traversal depth (1-5)                                         |
| `expand`           | string   | `record` | Only `record` (include full record data) or `none` (omit record data) |
| `date_time`        | ISO 8601 | —        | Timestamp to narrow index selection (improves performance)            |

**Example:**

```shell theme={null}
curl "https://api.telnyx.com/v2/session_analysis/call-session/a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d?include_children=true&max_depth=3&date_time=2026-03-17" \
 -H "Authorization: Bearer YOUR_API_KEY"
```

***

## Response Structure

### Top-Level Fields

```json theme={null}
{
 "session_id": "4c9d22b0-1e4b-11f1-83a0-02420aef51a1",
 "cost": {
 "total": "0.009800",
 "currency": "USD"
 },
 "root": { /* EventNode */ },
 "meta": {
 "event_count": 3,
 "products": ["ai-voice-assistant", "inference", "callcontrol-cdrs"]
 }
}
```

| Field              | Description                             |
| :----------------- | :-------------------------------------- |
| `session_id`       | Identifier for the analyzed session     |
| `cost.total`       | Total session cost (sum of all events)  |
| `cost.currency`    | ISO 4217 currency code                  |
| `root`             | Root event node (tree structure)        |
| `meta.event_count` | Total events in the tree                |
| `meta.products`    | Unique products involved in the session |

### Event Node Structure

Each event in the tree follows this structure:

```json theme={null}
{
 "id": "4c9d22b0-1e4b-11f1-83a0-02420aef51a1",
 "product": "callcontrol-cdrs",
 "event_name": "callcontrol-cdrs",
 "relationship": null,
 "cost": {
 "event_cost": "0.000000",
 "cumulative_cost": "0.009800",
 "currency": "USD"
 },
 "links": {
 "self": "/v2/session_analysis/call-control/4c9d22b0-...",
 "records": "/v2/detail_records?record_type=call-control&id=4c9d22b0-..."
 },
 "record": { /* Full detail record */ },
 "children": [ /* Array of child EventNodes */ ]
}
```

| Field                  | Description                                          |
| :--------------------- | :--------------------------------------------------- |
| `id`                   | Event identifier                                     |
| `product`              | Internal product identifier                          |
| `event_name`           | Event type name                                      |
| `relationship`         | How this event relates to its parent (null for root) |
| `cost.event_cost`      | Cost of this individual event                        |
| `cost.cumulative_cost` | This event + all descendants                         |
| `links.self`           | Link to this analysis node                           |
| `links.records`        | Link to underlying detail records                    |
| `record`               | Full detail record data (varies by record type)      |
| `children`             | Child event nodes                                    |

### Relationship Object

For child events, the `relationship` field explains the connection:

```json theme={null}
{
 "type": "child_of",
 "via": {
 "local_field": "conversation_id",
 "parent_field": "conversation_id"
 },
 "parent_id": "4c9063e0-1e4b-11f1-b789-02420aef51a1"
}
```

| Field              | Description                                   |
| :----------------- | :-------------------------------------------- |
| `type`             | Relationship type (`child_of` or `parent_of`) |
| `via.local_field`  | Field on this record that links to parent     |
| `via.parent_field` | Field on the parent record that's matched     |
| `parent_id`        | Identifier of the parent event                |

Note: The `via` fields vary by relationship. AI-related linkages often use `conversation_id`, while voice/telephony relationships typically use `telnyx_session_id` or `call_session_id`.

***

## Example: AI Voice Assistant Session

A customer makes an inbound call to an AI Voice Assistant using the Telnyx Web Dialer. The assistant handles a multi-turn conversation over \~152 seconds.

**Query:**

```shell theme={null}
curl "https://api.telnyx.com/v2/session_analysis/call-session/a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d?max_depth=5&include_children=true&date_time=2026-03-17" \
 -H "Authorization: Bearer YOUR_API_KEY"
```

**Session tree:**

```
call-session (SIP Trunking) event: $0.0000 cumulative: $0.2128
├── recording event: $0.0051 cumulative: $0.0051
├── webrtc event: $0.0052 cumulative: $0.0052
├── sip-trunking event: $0.0052 cumulative: $0.0052
└── call-control event: $0.0052 cumulative: $0.1973
    └── ai-voice-assistant event: $0.1800 cumulative: $0.1921
        ├── inference event: $0.0010
        ├── inference event: $0.0015
        ├── inference event: $0.0029
        ├── inference event: $0.0033
        └── inference event: $0.0034
```

**Summary:**

| Field              | Value                                                                                          |
| :----------------- | :--------------------------------------------------------------------------------------------- |
| Total session cost | \$0.2128                                                                                       |
| Event count        | 11                                                                                             |
| Duration           | \~152 seconds                                                                                  |
| Products involved  | siptrunking-cdrs, recording-cdrs, webrtc-cdrs, callcontrol-cdrs, ai-voice-assistant, inference |

**Key observations:**

* The **call-session** root has \$0.00 event cost — it's a container for the session, not a billable event itself.
* **ai-voice-assistant** is the dominant cost at $0.18 (billed at $0.06/min for 3 billed minutes). Its cumulative cost of \$0.1921 includes 5 inference events underneath.
* Each **inference** event represents a single LLM call during the conversation — one per assistant turn. The cost per turn varies based on the model and token count.
* **call-control** has a $0.0052 event cost but a $0.1973 cumulative cost — the difference is the ai-voice-assistant subtree rolling up underneath it.
* **recording**, **webrtc**, and **sip-trunking** are leaf nodes with no children, so their event cost equals their cumulative cost.

### Understanding the AI Cost Structure

In an AI-powered call, costs come from multiple layers:

| Layer             | Record Type                                     | What You're Billed For                         |
| :---------------- | :---------------------------------------------- | :--------------------------------------------- |
| **AI Assistant**  | `ai-voice-assistant`                            | Time-based (per minute of assistant usage)     |
| **LLM Inference** | `inference`                                     | Per-turn (each LLM call is a separate event)   |
| **Call Control**  | `call-control`                                  | Time-based (per minute of call control usage)  |
| **Transport**     | `sip-trunking`, `webrtc`                        | Time-based (per minute for the voice leg)      |
| **Add-ons**       | `recording`, `speech-to-text`, `text-to-speech` | Per-use or per-minute depending on the product |

Session Analysis rolls all of these into one tree, so you can see exactly where cost accumulates — whether it's the AI assistant minute rate, the number of inference turns, or the underlying voice transport.

***

## Common Use Cases

### Debugging High-Cost AI Sessions

1. Query the session with `max_depth=5` to get the full tree
2. Look at `meta.products` to see which products were involved
3. Compare `event_cost` across children — if `ai-voice-assistant` is the driver, check how many inference turns occurred underneath
4. Check `record` fields for rate information (`rate`, `rate_measured_in`) and duration

### Finding Related Events

1. Start from any event in the tree
2. Use `relationship.via` to understand the field linkage
3. Query the parent or children directly using their `id`

### Exporting for Analysis

1. Use `expand=none` to get just the tree structure without full records
2. Flatten the tree programmatically for spreadsheet import
3. Or keep full records and parse specific fields (e.g., `rate`, `duration_sec`)

***

## Best Practices

### Performance

* **Provide `date_time`** when you know it — this narrows the index search and improves response time
* **Use appropriate `max_depth`** — start with `2`, increase only if needed. AI sessions with inference events typically need `max_depth=4` or `5` to reach the leaf level.
* **Use `expand=none`** if you only need cost rollups, not full records

### Querying

* **Start from the root event** — the most complete picture comes from the top-level event
* **Check metadata first** — `/metadata/&#123;record_type&#125;` shows expected children and recommended depth
* **Use `links.records`** to get the underlying detail records for deeper analysis

### Error Handling

| Status Code | Meaning            | Action                                           |
| :---------- | :----------------- | :----------------------------------------------- |
| 400         | Invalid parameters | Check `record_type` spelling, `event_id` format  |
| 403         | Forbidden          | Verify API key has access to this user's data    |
| 404         | Event not found    | Event may not exist, or `date_time` is incorrect |
| 500         | Internal error     | Retry with exponential backoff                   |

***

## Record Types Reference

### AI & Voice Intelligence

| Record Type                | Product                     | Description                            | Common Children |
| :------------------------- | :-------------------------- | :------------------------------------- | :-------------- |
| `ai-voice-assistant`       | AI Voice Assistant          | AI assistant sessions                  | `inference`     |
| `inference`                | Inference                   | AI inference events (one per LLM turn) | —               |
| `inference-speech-to-text` | Inference                   | STT-specific inference                 | —               |
| `inference-text-to-speech` | Inference                   | TTS-specific inference                 | —               |
| `summarization`            | Inference                   | Text summarization                     | —               |
| `embedding`                | Inference                   | Vector embeddings                      | —               |
| `speech-to-text`           | Speech-to-Text              | STT transcription records              | —               |
| `text-to-speech`           | Text-to-Speech              | TTS synthesis records                  | —               |
| `amd`                      | Answering Machine Detection | AMD invocations                        | —               |
| `noise-suppression`        | Noise Suppression           | Audio enhancement records              | —               |

### Voice & Telephony

| Record Type    | Product                           | Description                                                        | Common Children                                                                                             |
| :------------- | :-------------------------------- | :----------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- |
| `call-session` | N/A                               | Root-level record for grouping related voice product usage records | `call-control`, `sip-trunking`, `recording`, `webrtc`, `siprec-client`, `fax-api`, `conference-participant` |
| `call-control` | Call Control (Programmable Voice) | Voice API call records                                             | `ai-voice-assistant`, `amd`, `media-streaming`, `speech-to-text`, `text-to-speech`, `noise-suppression`     |
| `sip-trunking` | SIP Trunking                      | SIP trunking detail records                                        | —                                                                                                           |
| `webrtc`       | WebRTC                            | WebRTC call records                                                | —                                                                                                           |

### Media & Storage

| Record Type       | Product         | Description             | Common Children |
| :---------------- | :-------------- | :---------------------- | :-------------- |
| `recording`       | Call Recording  | Call recording CDRs     | —               |
| `media_storage`   | Media Storage   | Stored media files      | —               |
| `media-streaming` | Media Streaming | Real-time audio streams | —               |
| `forking`         | Media Forking   | Forked media records    | —               |

### Conferencing

| Record Type              | Product    | Description                    | Common Children          |
| :----------------------- | :--------- | :----------------------------- | :----------------------- |
| `conference`             | Conference | Conference sessions            | `conference-participant` |
| `conference-participant` | Conference | Individual participant records | —                        |

### Messaging

| Record Type | Product   | Description             | Common Children          |
| :---------- | :-------- | :---------------------- | :----------------------- |
| `message`   | Messaging | SMS/MMS message records | `ai-messaging-assistant` |

### Video

| Record Type                      | Product            | Description         | Common Children |
| :------------------------------- | :----------------- | :------------------ | :-------------- |
| `room-session-event`             | Programmable Video | Video room sessions | —               |
| `room-session-participant-event` | Programmable Video | Video participants  | —               |
| `room-session-recording-event`   | Programmable Video | Video recordings    | —               |
| `room-session-composition-event` | Programmable Video | Video compositions  | —               |

### Other Products

| Record Type               | Product         | Description               | Common Children |
| :------------------------ | :-------------- | :------------------------ | :-------------- |
| `verify`                  | Verify (2FA)    | Verification attempts     | —               |
| `fax-api`                 | Fax API         | Fax transmission records  | —               |
| `branded-calling`         | Branded Calling | Caller ID branding        | —               |
| `siprec-client`           | SIPREC          | SIPREC recording sessions | —               |
| `sim_card_usage`          | Wireless        | SIM card data usage       | —               |
| `customer-service-record` | CSR             | Customer service records  | —               |

***

### On-Demand Reports

> Source: [https://developers.telnyx.com/docs/reporting/on-demand-reports.md](https://developers.telnyx.com/docs/reporting/on-demand-reports.md)

On-Demand Reports lets you query your Telnyx usage data using natural language. Instead of building filters and exporting CSVs, describe the report you want in plain English and the system generates it.

***

## Overview

On-Demand Reports uses AI to translate natural language into structured queries against your [Usage Report](/docs/reporting/usage-reports/index) data. You can request specific chart types, date ranges, groupings, and breakdowns — all from a single text input.

***

## Getting started

1. Log in to the [Mission Control Portal](https://portal.telnyx.com).
2. Navigate to **Reporting** > **[On-Demand Reports](https://portal.telnyx.com/#/reporting/on-demand-reports)**.
3. Type a query describing the report you need and submit it.

<img src="https://mintcdn.com/telnyx/q68aljKi6o67cdpt/assets/images/on-demand-reports-interface.png?fit=max&auto=format&n=q68aljKi6o67cdpt&q=85&s=5ab1a4c54b54a9a320ef459a19c7ccfa" alt="On-Demand Reports query interface" width="1194" height="454" data-path="assets/images/on-demand-reports-interface.png" />

***

## Example queries

* "Daily wireless spend for the last 10 days"
* "Number of messages by direction for last week as a pie chart"
* "Weekly total calls last quarter"
* "Average SIP cost for last 2 weeks as line graph"
* "SIP usage for the last month as a table"

***

## What you can do

| Capability                   | Description                                                                |
| ---------------------------- | -------------------------------------------------------------------------- |
| **Natural language queries** | Describe reports in plain English instead of building filters manually     |
| **Chart type selection**     | Request bar charts, pie charts, line graphs, or tabular breakdowns         |
| **Date range control**       | Specify relative ("last 10 days") or absolute ("in February") time periods |
| **Grouping and breakdowns**  | Group data by direction, product, day, week, or other dimensions           |

***

## Current limitations

* Queries run against **v2 Usage Report data** only. Products and dimensions available in Usage Reports are available here.
* **Not a general knowledge assistant** — the system answers questions about your Telnyx usage data, not general questions.
* This is the first release. Coverage and capabilities will expand over time.

***

## Video

### Getting Started

> Source: [https://developers.telnyx.com/docs/video/get-started.md](https://developers.telnyx.com/docs/video/get-started.md)

Telnyx Video Rooms are enabled using the Rooms API. You can create as many rooms as needed using this API.
To access a Telnyx Video Room, a Client Join Token will first need to be
generated in JWT form. You can use our HTTPS API and authenticate using the
API Key associated with your Mission Control account under API Keys. Find out
more about authenticating with API V2 here.
Adding Telnyx Rooms functionality to your app can be done using our JS SDK
(mobile SDKs coming soon). To gain access to a live Video Rooms sample app,
reach out to our sales team today!
Check out the full API reference today!

## Glossary

Room
Resource representing a virtual place where multiple endpoints using one
of Telnyx’s Programmable Video SDKs can connect.
Room Session
Resource representing a moment where multiple Room Participants were
communicating within a given Room.
Room Participant
Resource representing an endpoint using one of Telnyx’s Programmable Video
SDKs to connect to a given Room.
JWT
JSON Web Token. A standard method for representing claims.
Client Join Token (JWT)
A JWT token which contains grants allowing in the Room usecase to join a
Room.
Refresh Token (JWT)
A JWT token which permits to obtain a new Client Token with same grants.
API Key
Secret API Key generated via Portal and used to authenticate Telnyx API
calls.
Video SDK
A library used to provide Video features to your application using Telnyx
Video platform.
Configuration and usage
Telnyx Video is enabled using Video Rooms. A Video Room represents
communications session among multiple endpoints using one of Telnyx’s
Programmable Video SDKs. Connected users (Participants) can share video and
audio Tracks with the Room, and receive video and audio Tracks from other
Participants in the Room. You can as many Rooms as you want. For example you
could create a long lived Room such as "Daily Standup", or Rooms that you
would delete after it's been used like "1-1 with X". To create a Video Room
you can use the REST API V2 documented\{" "}
here. A Video Room can only be joined
if the client owns a Client Join Token, you can create it using the
REST API V2 documented\{" "}
here. The Client Join
Token is short lived and you will be able to refresh it using the\{" "}
Refresh Token provided with it when you request for a Client Join
Token. Once you have a Video Room and an Client Join Token for it, you can
then use our Video SDK on your client side to connect your client to the Room
you just created.

## Concepts

### Architecture

`Video Rooms` is a platform that enables developers to add audio and video capability to Web, Android, and iOS applications.

The platform consists of REST APIs, Client SDKs, and our mission control portal that makes it really easily to capture, stream, record, and render live audio and video.

A video application built with Video Rooms has to parts:

* **Client:** Our client side Javascript, iOS, and Android SDKs used to interact with a `Room` instance
* **Server:** Our REST APIs and portal to create/manage room and session, configuring recording, or leverage our `Participants API` to moderator participants in a `Room`.

### Terms

Understanding the basic concepts of the video SDK will help you understand how it works. These concepts apply in general across all of our platforms.

* A `Room` represents a real time audio/video/screen share session with other people or participants. It is fundamental to building a video application.

* `Room State` tracks the state of the room as it changes making it extremely easy to understand what's happened to a `Room`.
  * `For example`: Room State could change due to a Local Participant has started publishing a stream or because a Remote Participant left.
    A Stream represents the audio/video media streams that are shared by Participants in a Room

* A `Participant` represents a person inside a `Room`. Each `Room` has one `Local Participant` and one or more `Remote Participants`.

* A `Stream` represents the audio/video media streams that are shared by `Participants` in a `Room`
  * A `Stream` is indentified by it's `participantId` and `streamKey`

* A `Subscription` is used to subscribe to a `Stream` belonging to a `Remote Participant`

### Dive a bit deeper

Dive a little bit deeper into our `Video Rooms` platform to get a better understand of what its capable of, and what you can buid. Learn more about our Client SDKs and Server APIs.

### Client SDKs

Our [Javascript SDK API reference](/docs/video/javascript-sdk) which details the API of our SDK including behaviors of the `Room` class and the `Events` that triggers and how they function.

* [Rooms Class API Reference](/docs/video/javascript-sdk/room-events#room)
* [Room Events Reference](/docs/video/javascript-sdk/room-events#room-events)

### Server APIs

* [Rooms](/api-reference/rooms/view-a-list-of-rooms) - manage Rooms
* [Client Access Tokens](/api-reference/rooms-client-tokens/create-client-token-to-join-a-room#create-client-token-to-join-a-room) - manage client access tokens needed to interact with a `Room`
* [Sessions](/api-reference/room-sessions/view-a-list-of-room-sessions) - manage room sessions, end a session, and mute/unmute/kick all participants in a given session.
* [Participants](/api-reference/room-participants/view-a-list-of-room-participants) - search for participants based on a number of filters like `session id`
* [Recordings](/api-reference/room-recordings/view-a-list-of-room-recordings) - manage recordings, including bulk delete.
* [Compositions](/api-reference/room-compositions/view-a-list-of-room-compositions) - create and manage compositions.

***

### Getting Started

> Source: [https://developers.telnyx.com/docs/video/javascript-sdk.md](https://developers.telnyx.com/docs/video/javascript-sdk.md)

The Telnyx Video Client SDK provides all the functionality you need to join and interact with a video room from a browser.

![npm](https://img.shields.io/npm/v/@telnyx/video.svg?color=lightgrey\&style=flat)

## Adding Telnyx to your JavaScript client application

Include the `@telnyx/video` npm module as a dependency:

`npm install @telnyx/video --save`

After pasting the above content, Kindly check and remove any new line added

Then, import `@telnyx/video` in your application code.

```javascript theme={null}
// main.js
import { Room, createLocalParticipant } from '@telnyx/video';
```

After pasting the above content, Kindly check and remove any new line added

Now you are ready to connect to a video room that you created. In order to connect to a video room you will require a client token that has the necessary grants to join the room.

```javascript theme={null}
const room = new Room(roomId, {
  clientToken: '<CLIENT_TOKEN_FOR_THE_ROOM>',
  localParticipant: createLocalParticipant({
    context: JSON.stringify({ name: 'Bob The Builder', id: 1 }), // send data that you can associate with this participant (for e.g., userId for this participant in your DB)
  }),
});

const stateCallback = (state) => {
  // the state object is immutable and can be easily integrated with most modern UI libraries like React, Vue etc.
};

room.on('state_changed', stateCallback);

room.connect().then(() => {
  console.log('You are connected to the room!');
});
```

After pasting the above content, Kindly check and remove any new line added

## Understanding the state of the video room

The `state_changed` event callback contains the state of the SDK at that point in time. This is an immutable object that you can use in most modern UI libraries like React and Vue.

The Typescript definition of the State is helpful in understanding the structure of this object.

```javascript theme={null}
type Status =
  | 'initialized'
  | 'connecting'
  | 'connected'
  | 'disconnecting'
  | 'disconnected';

interface State {
  status: Status;
  localParticipantId: Participant['id'];
  participants: {
    [id: string]: Participant;
  };
  streams: {
    [id: string]: Stream;
  };
}
```

After pasting the above content, Kindly check and remove any new line added

Everytime the state of the SDK changes the `state_changed` callback is invoked with a new immutable state that represents the current state of the SDK. Since most modern UI libraries are able to compare the two immutable states and render only the components that changed it makes it easier to integrate the SDK with them rather than depending on multiple event callbacks.

For e.g., based on how we initialized it in the above code example the initial state of the SDK would result in an object give below. Note that the ID of the local participant is unique and are generated based on UUID v4 standard when you create the local participant.

```javascript theme={null}
{
  status: 'initialized',
  localParticipantId: "8c3bacb5-2e90-4379-8ee8-d5446213fee9",
  participants: {
    "8c3bacb5-2e90-4379-8ee8-d5446213fee9": {
      id: "8c3bacb5-2e90-4379-8ee8-d5446213fee9",
      context: "{\"name\":\"Bob The Builder\",\"id\":1}",
      streams: {},
    },
  },
  streams: {},
};
```

After pasting the above content, Kindly check and remove any new line added

At this point there are no streams being published by the local participant. We will come to that shortly. The participant object follows the TypeScript interface given below.

```javascript theme={null}
interface Participant {
  id: string;
  context?: string;
  streams: {
    [key: string]: Stream['id'];
  };
}
```

After pasting the above content, Kindly check and remove any new line added

When we called the `room.connect()` method in the example code the state of the SDK will change to the following ...

```javascript theme={null}
{
  status: 'connecting',
  localParticipantId: "8c3bacb5-2e90-4379-8ee8-d5446213fee9",
  participants: {
    "8c3bacb5-2e90-4379-8ee8-d5446213fee9": {
      id: "8c3bacb5-2e90-4379-8ee8-d5446213fee9",
      context: "{\"name\":\"Bob The Builder\",\"id\":1}",
      streams: {},
    },
  },
  streams: {},
}
```

After pasting the above content, Kindly check and remove any new line added

This new state will be available to your application via the `state_changed` callback. Since state is immutable the only difference between the initial state and the new state is the `status` property. If you are using a modern UI library like React you can easily rerender the components to show that the application is connecting to the room.

When successfully connected the state changes to ...

```javascript theme={null}
{
  status: "connected",
  localParticipantId: "8c3bacb5-2e90-4379-8ee8-d5446213fee9",
  participants: {
    "8c3bacb5-2e90-4379-8ee8-d5446213fee9": {
      id: "8c3bacb5-2e90-4379-8ee8-d5446213fee9",
      context: "{\"name\":\"Bob The Builder\",\"id\":1}",
      streams: {},
    },
  },
  streams: {},
}
```

After pasting the above content, Kindly check and remove any new line added

Now we are ready to start publishing streams on behalf of the local participant.

## Publishing your local camera and mic stream

In order to publish a stream you need to define the constraints of the media. The simplest form of the constraints is ...

```javascript theme={null}
const constraints = { audio: true, video: true };
```

After pasting the above content, Kindly check and remove any new line added

With these constraints the SDK will try to obtain both audio and video from the local participant. In order to publish the stream as the local participant you have to use the `publish` method available to you via the room object.

```javascript theme={null}
room.publish('self', {
  constraints: { audio: true, video: true },
});
```

After pasting the above content, Kindly check and remove any new line added

The first argument to `publish` is a string that acts as the key that you can use to refer to this specific stream. You can use any valid string for this as long as you are consistent in your application. For e.g. here we're using 'self' for the camera/mic stream but you could use 'presentation' as the key for when you publish video of the screen. We will get to how you can publish your screen in a short while.

When you make the request to publish a stream, the browser will ask for the necessary permissions required to access the camera and mic. Once the permissions are acquired the SDK will configure and publish the stream to the room you are connected to.

At this point you will receive the new state to the `state_changed` event callback with the newly created stream.

```javascript theme={null}
{
  status: "connected",
  localParticipantId: "8c3bacb5-2e90-4379-8ee8-d5446213fee9",
  participants: {
    "8c3bacb5-2e90-4379-8ee8-d5446213fee9": {
      id: "8c3bacb5-2e90-4379-8ee8-d5446213fee9",
      context: "{\"name\":\"Bob The Builder\",\"id\":1}",
      streams: {
        self: "a87dd242-de78-4c19-a09c-23b336c9f25e",
      },
    },
  },
  streams: {
    "a87dd242-de78-4c19-a09c-23b336c9f25e": {
      id: "a87dd242-de78-4c19-a09c-23b336c9f25e",
      key: "self",

      // the constraits that was provided by you
      constraints: {
        audio: true,
        video: true,
      },
      bitrate: 256000, // also configurable when you publish a stream

      audioActive: false, // whether the audio track is being published
      videoActive: false, // whether the video track is being published
      source: MediaStream, // an instance of MediaStream that can be used to render video/audio
      audioTrack: undefined, // this will be an instance of MediaStreamTrack when the track is available
      videoTrack: undefined, // this will be an instance of MediaStreamTrack when the track is available

      isSpeaking: false, // whether the audio level of the track is high enough to consider the participant who owns this stream is speaking or not
      isRemote: false, // whether the stream originates from a remote source or not

      isPublishing: true, // whether the stream is being published
      isConfiguring: false, // whether the SDK is currently negotiating the WebRTC connection

      participantId: "8c3bacb5-2e90-4379-8ee8-d5446213fee9",
    },
  },
}
```

After pasting the above content, Kindly check and remove any new line added

The stream object is the most complex object in the state of the SDK. However you don't have to worry about all of these properties at the moment.

As you can see the `isPublishing` property of the stream is `true` now. This means that the stream is being published, which involves creating the SDP (Session Description Protocol) and establishing the WebRTC connection. You can't publish another stream of the same key ("self" in our case) until this stream is published or removed (by unpublishing).

Once the SDK acquires the media tracks from the camera and mic of the local participant a new state will be returned using the `state_changed` callback. This state will contain the audio and video track from the local media devices.

The streams object at this point will look like this

```javascript theme={null}
  streams: {
    "a87dd242-de78-4c19-a09c-23b336c9f25e": {
      id: "a87dd242-de78-4c19-a09c-23b336c9f25e",
      key: "self",

      // the constraits that was provided by you
      constraints: {
        audio: true,
        video: true,
      },
      bitrate: 256000,

      audioActive: true,
      videoActive: true,
      source: MediaStream,
      audioTrack: MediaStreamTrack,
      videoTrack: MediaStreamTrack,

      isSpeaking: false,
      isRemote: false,

      isPublishing: true,
      isConfiguring: true,

      participantId: "8c3bacb5-2e90-4379-8ee8-d5446213fee9",
    },
  },
```

After pasting the above content, Kindly check and remove any new line added

As you can see at this point you can use the `source` or the media tracks individually from `audioTrack` and `videoTrack` to show the media in your UI.

You can also see that the `audioActive` and `videoActive` flags are now true indicating the stream is publishing audio and video respectively.

Another property that was updated in the new state is the `isConfiguring` flag. You can ignore this in most use-cases as this indicates if the WebRTC connection is being negotiated.

Once the WebRTC connection is negotiated and the stream is successfully published the stream state would look like this

```javascript theme={null}
  streams: {
    "a87dd242-de78-4c19-a09c-23b336c9f25e": {
      id: "a87dd242-de78-4c19-a09c-23b336c9f25e",
      key: "self",

      // the constraints provided by you
      constraints: {
        audio: true,
        video: true,
      },
      bitrate: 256000,

      audioActive: true,
      videoActive: true,
      source: MediaStream,
      audioTrack: MediaStreamTrack,
      videoTrack: MediaStreamTrack,

      isSpeaking: false,
      isRemote: false,

      isPublishing: false,
      isConfiguring: false,

      participantId: "8c3bacb5-2e90-4379-8ee8-d5446213fee9",
    },
  },
```

After pasting the above content, Kindly check and remove any new line added

At this point the stream will be available to be subscribed by other clients connected to the same room.

## Knowing when a new participant joins the room

A video room can have multiple participants and you can get a list of all the participants connected to the room at the time by using the `participants` property in the state.

If the participant is not publishing any streams the `streams` property of that participant will be an empty object.

Let's imagine that another participant joins the same room from a different browser session with the following context

```javascript theme={null}
const room = new Room(roomId, {
  clientToken: '<CLIENT_TOKEN_FOR_THE_ROOM>',
  localParticipant: createLocalParticipant({
    context: JSON.stringify({ name: 'Oswald', id: 2 }), // send data that you can associate with this participant (for e.g., userId for this participant in your DB)
  }),
});
```

After pasting the above content, Kindly check and remove any new line added

Since the room already contains a participant who is publishing a stream the state once this session is connected would look something like ...

```javascript theme={null}
{
  status: "connected",
  localParticipantId: "d42926f5-fe6c-48d5-b24b-7444048fa68e",
  participants: {
    "8c3bacb5-2e90-4379-8ee8-d5446213fee9": {
      id: "8c3bacb5-2e90-4379-8ee8-d5446213fee9",
      context: "{\"name\":\"Bob The Builder\",\"id\":1}",
      streams: {
        self: "a87dd242-de78-4c19-a09c-23b336c9f25e",
      },
    },
    "d42926f5-fe6c-48d5-b24b-7444048fa68e": {
      id: "d42926f5-fe6c-48d5-b24b-7444048fa68e",
      context: "{\"name\":\"Oswald\",\"id\":2}",
      streams: {},
    },
  },
  streams: {
    "a87dd242-de78-4c19-a09c-23b336c9f25e": {
      id: "a87dd242-de78-4c19-a09c-23b336c9f25e",
      key: "self",

      audioActive: true, // whether audio is being published by the remote stream
      videoActive: true, // whether video is bein published by the remote stream
      source: MediaStream,
      audioTrack: undefined, // will be an instance of MediaStreamTrack once subscribed
      videoTrack: undefined, // will be an instance of MediaStreamTrack once subscribed
      videoCodec: "vp8", // the codec used by the remote video
      audioCodec: "opus", // the codec used by the remote audio

      isRemote: true,
      isSpeaking: false, // whether the remote participant is speaking or not

      isConfiguring: false, // whether the WebRTC connection is being negotiated or not

      subscription: {
        status: "unsubscribed", // will change to 'subscribed' once the the client subscribes to this stream
      },

      participantId: "8c3bacb5-2e90-4379-8ee8-d5446213fee9", // the ID of the participant this stream belong to
    },
  },
}
```

After pasting the above content, Kindly check and remove any new line added

As you can see there is already a participant in the `participants` object that maps to the remote particpant from the previous browser session. This remote participant also has a stream with the key "self".

The stream object for "self" has the structure very similar to the one we saw before but has a few new properties.

The ones that are particularly interesting are `subscription`, `videoCodec`, and `audioCodec`. The properties for video and audio codec can be used to decide if the browser has the capabilities to decode the video and audio tracks.

Then there is `subscription.status`, which is `unpublished` at the moment.

## Subscribing to a remote stream

At this point we are ready to subscribe to the remote stream published by the participant. In order to do that we use the `subscribe` method from the room object.

```javascript theme={null}
room.subscribe('a87dd242-de78-4c19-a09c-23b336c9f25e');
```

After pasting the above content, Kindly check and remove any new line added

This will start the WebRTC negotiation to receive the remote stream. Once successful the `subscription.status` will change to `subscribed`.

You can use the `source` property, which is an instance of MediaStream to render the media on the browser.

If the remote participant unpublishes this stream the SDK will automatically unsubscibe from the stream and the stream will disappear from the state.

***

### Room and Events

> Source: [https://developers.telnyx.com/docs/video/javascript-sdk/room-events.md](https://developers.telnyx.com/docs/video/javascript-sdk/room-events.md)

Telnyx's video JavaScript SDK lets you manage room events and join/leave notifications.

We've made it much easier to add video capability to your web application using our Javascript SDK.

The `Room` object and the `Events` that occur in a `Room` is the API you will use to build your video application. We've extensively documented the interface for the `Room` and `Events` objects, below.

## Room

```javascript theme={null}
export type Room = Immutable<{
  /// The unique identifier of the Room instance
  id: string;

  /// Event listener/handler for various events that are triggered in the room.
  /// See the Events interface for a list.
  on<E extends keyof Events>(event: E, callback: Events[E]): Unsubscribe;

  /// Connects a Room instance to the server
  /// The state of the room, specifically the status of the room, will change while it's connecting,
  /// if you can listen to these changes with the state_changed event.
  /// Once the room is connected its local partipant will available in State.
  connect: () => Promise<void>;

  /// Disconnect a Room instance from the server
  /// The status of the state of the room will change while it's disconnecting.
  disconnect: () => Promise<void>;

  /// Updates the client token
  /// The client token is a short lived access token that expires after a time to live in seconds,
  /// which needs to be refreshed using a refresh token.
  /// If you are farmilar with how JWT's work you'll understand, head here for [more details](https://jwt.io)
  /// This was set when the client token was created - the default TTL is 600 seconds or 10 minutes.
  ///
  /// Client tokens need to refreshed before they expire otherise the Room instance will automatically
  /// disconnect.
  /// It's up to the developer to keep the client token fresh.
  /// To create a client token please use the `generate_join_client_token` endpoint
  /// To refresh a token please use `refresh_client_token` endpoint
  updateClientToken: (clientToken: string) => Promise<void>;

  /// Gets the state of the Room
  /// What does the state of the room look like?
  /*
  type Status =
    | 'initialized'
    | 'connecting'
    | 'connected'
    | 'disconnecting'
    | 'disconnected';

    interface State {
      status: Status;
      localParticipantId: Participant['id'];
      participants: {
        [id: string]: Participant;
      };
      streams: {
        [id: string]: Stream;
      };
    }
  */

  /// State is important because there can be lot of things changing in a room at any given moment, especially for really large rooms.
  /// Participants leaving the room, new participants joining, other starting/stopping their video, a moderator kicking
  /// a participant out.
  /// It would be a lot to keep track of manually, which is why we have state.
  /// Don't worry we've abstracted all the accounting, inside our SDK, and provided you with helper methods
  /// like this one, getParticipant(), and getLocalStreams() to make access simple.
  getState: () => State;

  /// Participants and Streams
  /// There's a two entities that are important to understand
  /// and as you can see from the State interface above they are big
  /// part of the State of the Room.
  ///
  /// Stream
  /// A stream represents media stream published by Participant in a Room.
  /// It's identified by it's participantId and streamKey and typically has a audio track or a video
  /// track, or both.

  /// Participants
  /// There are two type of participants: local and remote.
  /// Remote Participant - other participants in the room who are not the local participant.
  /// A local partipant subscribes to streams published by remote participants.
  ///
  /// Local Participant - is the person on a mobile device or desktop that joins the room to publish a stream.
  /// **NOTE**: There can only be one local participant in a room at a given time.

  /// Gets the local participant for a given Room instance
  /// You can use this method to find more details about the local participant in the Room
  ///
  /// What does a Participant look like?
  /*
    export type Participant = Immutable<{
      id: string;
      context?: string;
      streams: {
        [key: string]: Stream['id'];
      };
      canReceiveMessages: boolean;
      origin: 'local' | 'remote' | 'telephony_engine';
    }>;
  */
  getLocalParticipant: () => Participant;

  /// Get the streams associated to the local participant
  getLocalStreams: () => { [key: Stream['key']]: Stream };

  /// Add a stream to a Room
  /// key - is the unique identifier for the stream based on its realted participant.
  /// tracks.audio - the audio track
  /// tracks.video - the video track
  /// Once the promise is completed you can assume that the stream has started publishing in the room.
  addStream: (
    key: Stream['key'],
    tracks?: {
      audio?: MediaStreamTrack;
      video?:
        | MediaStreamTrack
        | { track?: MediaStreamTrack; options?: { enableSimulcast?: boolean } };
    }
  ) => Promise<void>;

  /// Update an existing stream in a Room
  /// key - identifier of the stream
  /// tracks.audio - the audio track
  /// tracks.video - the video track
  ///
  /// Why would you update a Stream?
  /// If you wanted to mute the audio of a particular stream you would update
  /// it and set the track to null
  updateStream: (
    key: Stream['key'],
    tracks?: {
      audio?: MediaStreamTrack;
      video?:
        | MediaStreamTrack
        | { track?: MediaStreamTrack; options?: { enableSimulcast?: boolean } };
    }
  ) => Promise<void>;

  /// Remove a stream
  /// key - identifier of the stream
  /// Removing a stream removes or unpublishes it from the room.
  removeStream: (key: string) => Promise<void>;

  /// Subscriptions
  /// A subscription is used to subscribed to a stream published by a Remote Participant

  /// Subscribe to a stream belonging to a remote participant
  /// participantId - the id of the remote participant
  /// key - the stream key of the stream
  /// config.audio - flag to indicate if you'd like to subscribe to the remote streams' audio
  // config.video - flag to indicate if you'd like to subscribe to the remote streams' video
  /// When the promise is fullfilled a remote stream is added to state.streams
  /// which then can be used to render the audio or video of that stream.
  /// You can access remote participant streams more easily using the getParticipantsStreams
  // and getParticipantStreams helper methods below.
  ///
  /// A developer is not required to subscribe to every remote stream being published in the room instead
  /// they can choose. This is very useful in situations conserving bandwidth and cpu resources is a priority,
  /// For example:
  /// A developer building a vidoe conferencing application and wants to support 100+ participants may only want subscribe
  /// to 15-20 participants at a time display each set in a seperate page, thus significantly saving bandwith and resources.
  addSubscription: (
    participantId: Participant['id'],
    key: Stream['key'],
    config: { audio: boolean; video: boolean }
  ) => Promise<void>;

  /// Update an existing subscription
  /// participantId - the id of the remote participant
  /// key - the stream key of the stream
  /// config.audio - flag to indicate if you'd like to subscribe to the remote streams' audio
  // config.video - flag to indicate if you'd like to subscribe to the remote streams' video
  /// When a remote participant toggles audio or video on a stream that you're subscribed to
  /// you'll need to update that subscription.
  updateSubscription: (
    participantId: Participant['id'],
    key: Stream['key'],
    config: { audio: boolean; video: boolean }
  ) => Promise<void>;

  /// Remove or stop subscribing to a remote participant's stream
  removeSubscription: (
    participantId: Participant['id'],
    key: Stream['key']
  ) => Promise<void>;

  /// Helper method to easily access all streams for a given remote participant
  getParticipantStreams: (
    participantId: Participant['id']
  ) => Map<Stream['key'], Stream>;

  /// Helper method to easily access a remote participant stream
  getParticipantStream: (
    participantId: Participant['id'],
    key: Stream['key']
  ) => Stream | undefined;

  /// Provides statistics for a local or remote stream
  /// It presents the same data you'd get from doing:
  /// RTCRtpSender.getStats() - https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpSender/getStats
  /// RTCRtpReceiver.getStats() - https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpReceiver/getStats
  /// using the standard WebRTC APIs but in a much more readable format.
  getWebRTCStatsForStream: (
    participantId: Participant['id'],
    key: Stream['key']
  ) => Promise<WebRTCStats>;

  /// Send a message to one, more than one, or all participants in the room
  /// message - a message type, current only 'text' is supported
  /// receipients? - an array of participants to send the message, if
  /// null the message is broadcasted to everyone in the room.
  sendMessage: (
    message: Message,
    recipients?: Array<Participant['id']>
  ) => Promise<void>;

  enableNetworkMetricsReport: (
    participantIds: Array<Participant['id']>,
    options?: { includeStreams?: boolean }
  ) => Promise<void>;

  disableNetworkMetricsReport: (
    participantIds?: Array<Participant['id']>
  ) => Promise<void>;
}>;
```

## Room events

```javascript theme={null}
export interface Events {
  /// Triggered when the state of the room changes
  /// For more
  /// TODO
  state_changed: (state: State) => void;

  /// Triggers on a Room instance when it connects to the server
  connected: (state: State) => void;

  /// Triggers on a Room instance when it disconnects from the server
  disconnected: (state: State) => void;

  /// Triggered when a remote participant joins the room
  /// Only triggered for remote participants the local participant does not
  /// need to be notified that they've joined the room since they've initiated
  /// that action themselves by connecting to the Room
  participant_joined: (participant: Participant['id'], state: State) => void;

  /// Triggered when a  participant is leaving the room because they were kicked
  /// due to a moderator event
  /// Unlike the joined and left events the local participant can be kicked from the room
  participant_leaving: (
    participant: Participant['id'],
    reason: 'kicked' | null,
    state: State
  ) => void;

  /// Triggered when a remote participant leaves the room
  participant_left: (participantId: Participant['id'], state: State) => void;

  /// Triggered after successfully adding or publishing a stream to the room
  /// This event is triggered for local and remote streams.
  /// A local stream is one that's published by the local participant in the Room
  /// A remote stream is one that's published by a remote participant in the Room
  stream_published: (
    participantId: Participant['id'],
    key: Stream['key'],
    state: State
  ) => void;

  /// Triggered after successfully unregistering a stream
  /// This event is triggered for both local and remote streams.
  /// It triggered when the local or remote participant remove or stops publishing a stream.
  /// When a remote participant leaves a room it will trigger for any of their related streams.
  stream_unpublished: (
    participantId: Participant['id'],
    key: Stream['key'],
    state: State
  ) => void;

  /// Triggered when a local stream or a remote stream track has been enabled.
  /// Notifies consumers about remote stream tracks being enabled.
  /// For example: when audio is unmuted or video has started on a remote stream.
  track_enabled: (
    participantId: Participant['id'],
    key: Stream['key'],
    kind: 'audio' | 'video',
    state: State
  ) => void;

  /// The oposite of track_enabled
  /// Triggers when a local stream or a remote stream track has been disabled.
  /// Notifies consumers about remote stream tracks being disabled.
  /// For example: when audio is muted or video has stopped on a remote stream.
  track_disabled: (
    participantId: Participant['id'],
    key: Stream['key'],
    kind: 'audio' | 'video',
    state: State
  ) => void;

  /// Triggered  when a track is censored due to a moderator event
  /// Like the kick moderator event in participant_leaving, both local and remote streams can be
  /// censored.
  ///
  /// Since a Stream consists of an audio and video track, either can be censored by a moderator
  /// If the audio or video track on given stream is null, effectively nothing happens.
  track_censored: (
    participantId: Participant['id'],
    key: Stream['key'],
    kind: 'audio' | 'video',
    state: State
  ) => void;

  /// Triggered when a  track is uncensored due to a moderator event
  /// Opposite of track_censored
  /// Naturally, a track must be censored to order be uncensored.
  track_uncensored: (
    participantId: Participant['id'],
    key: Stream['key'],
    kind: 'audio' | 'video',
    state: State
  ) => void;

  /// Triggered when there is audio activity from a particular stream or participant talking in the Room
  /// When there's audio activity when a participant speaking this event will trigger with the participant id that's talking and the stream key will be null
  audio_activity: (
    participantId: Participant['id'],
    key: Stream['key'] | null,
    state: State
  ) => void;

  /// Triggered a subscription to a remote stream is started
  subscription_started: (
    participantId: Participant['id'],
    key: Stream['key'],
    state: State
  ) => void;

  /// Triggered when the subscription is reconfigured using the updateSubscription method
  subscription_reconfigured: (
    participantId: Participant['id'],
    key: Stream['key'],
    state: State
  ) => void;

  /// Triggered when subscription is removed or ended for a remote stream
  /// A subscription can be ended by calling `removeSubscription(ParticipantId,StreamKey)`
  /// This is also triggered when a remote participant leaves the room.
  subscription_ended: (
    participantId: Participant['id'],
    key: Stream['key'],
    state: State
  ) => void;

  /// onMessageReceived
  /// Triggered when a new message is recieved either by one or more participants or broadcasted to all participants
  /// in the room.
  /// participantId - sender of the message
  /// recipients - an array of participants the message was sent to. when null the message was broadcasted to the all participants in the room.
  message_received: (
    participantId: Participant['id'], // the participant that sent the message
    message: Message,
    recipients: Array<Participant['id']> | null,
    state: State
  ) => void;

  network_metrics_report: (networkMetrics: NetworkMetrics) => void;
}
```

***

### Tutorial

> Source: [https://developers.telnyx.com/docs/video/javascript-sdk/javascript-video-tutorial.md](https://developers.telnyx.com/docs/video/javascript-sdk/javascript-video-tutorial.md)

## In 10 minutes we'll build

A simple web app in vanilla javascript to make a video with audio from a caller to a callee.

## Get an API Key

> You need a Telnx account to create an API key so you can interact with our Rooms API

* If you don't have one please: Sign up for a free account
* Navigate to API Keys section and create an API Key by clicking `Create API Key`  button.
* Copy your API key

## Create a Room

Let's use our Rooms API to create a room.

**Request**

*Don't forget to update `YOUR_API_KEY` here.*

```bash theme={null}
curl -X POST "https://api.telnyx.com/v2/rooms" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  --data-binary '{
  "unique_name": "My room",
  "max_participants": "10",
  "webhook_event_url": "https://example.com",
  "enable_recording": "false"
}'
```

**Response**

```json theme={null}
{
  "data": {
    "active_session_id": null,
    "created_at": "2022-07-29T01:25:51.938184Z",
    "enable_recording": false,
    "id": "599e4d1e-d0aa-40ee-867a-a527f2b2dccd",
    "max_participants": 10,
    "record_type": "room",
    "unique_name": "Jons Room 2",
    "updated_at": "2022-07-29T01:25:51.940294Z",
    "video_codecs": ["h264", "vp8"],
    "webhook_event_failover_url": "",
    "webhook_event_url": "https://example.com",
    "webhook_timeout_secs": null
  }
}
```

Great, grab the response has an `id` field, this is the id for your newly created Room.

## Generate a client token

You'll need a client access token to join the room.

To create one use our [REST API `rooms/create` endpoint.](/api-reference/rooms/create-a-room#create-a-room)

You'll need to replace ROOM\_ID in in the url of the command with your room id, above. As well as YOUR\_API\_KEY as you have in previous steps.

**Request**

```bash theme={null}
curl -X POST \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  --header "Authorization: Bearer YOUR_API_KEY" \
  --data '{"refresh_token_ttl_secs":3600,"token_ttl_secs":600}' \
  https://api.telnyx.com/v2/rooms/ROOM_ID/actions/generate_join_client_token
```

**Response**

```json theme={null}
{
  "data": {
    "recort_type": "client_token",
    "refresh_token": "eyJhb***************************",
    "refresh_token_expires_at": "2022-07-29T02:29:07Z",
    "token": "eyJhb**********************************",
    "token_expires_at": "2022-07-29T01:39:07Z"
  }
}
```

The `token` field in the response is the client access token you'll use to join the room.

## 🏁 Checkpoint - run the app

At this point you should have the following:

* An API Key
* A room ID
* A room client access token

**Let's run the app in a sandbox**

There's a README with instructions inside the sandbox. We can use this sandbox as a template to build out the rest of our application.

## Let's write some code

**Basic concepts of the video SDK**

Here are some basic concepts of the SDK that will help you better understand how it works.

* A `Room` represents a real time audio/video/screen share session with other people or participants. It is fundamental to building a video application.

* A `Participant` represents a person inside a `Room`. Each `Room` has one `Local Participant` and one or more `Remote Participants`.

* A `Stream` represents the audio/video media streams that are shared by `Participants` in a `Room`
  * A `Stream` is indentified by it's `participantId` and `streamKey`

* A `Participant` can have one or more `Stream`'s associated with it.

* A `Subscription` is used to subscribe to a `Stream` belonging to a `Remote Participant`

**Room Events**

There are a few events trigger on a `Room` instance that we'll need to finish building this app that makes a video call.

* `connected` - triggers when a room instance has connected to the server
* `participant_joined` - triggers when a remote participant joins the room
* `stream_published` - triggers when a stream has started being published to the room
* `subscription_started` - triggers when subscription to remote stream has started

Handling an event looks like this:

```javascript theme={null}
room.on("connected", async () => {
  ...
});
```

## 🏁 Checkpoint

> It might be helpful to take a look at the [full list of `Events` available on a `Room` instance.](/docs/video/javascript-sdk/room-events)

## Connect to the room and get local media

`NOTE: ` We can start implementing our code after the `room.initialize` block inside the sandbox above.

Let's connect to the room and use the standard WebRTC API to get the audio and video tracks from the device.

```javascript theme={null}
// connected to the room as the local participant
telnyxVideoClient.on("connected", async () => {
  // use the webrtc api to get media from devices
  let intercomStream = await navigator.mediaDevices.getUserMedia({
    audio: true,
    video: true
  });
  console.log("got local stream using getUserMedia...");

  // Get audio and video tracks from the MediaStream's
  // since the sdk works with MediaStreamTrack
  let intercomAudioTrack = intercomStream.getAudioTracks()[0];
  let intercomVideoTrack = intercomStream.getVideoTracks()[0];
});
```

## Publish the stream and render it

We want to a add/publish a stream the room which consists of the audio and video track we received from the user's device, above.

We'll give the stream a key of "caller", which is how the stream is identified.

```javascript theme={null}
// add/publish a stream with a key "caller" to the room
await telnyxVideoClient.addStream("intercom", {
  audio: callerAudioTrack,
  video: callerVideoTrack
});
console.log("published local stream to the room...");
```

**Render the stream to the DOM**

Now, that we have the caller's stream we want to render it to the DOM.

The entire `room.connected` block looks like this:

```javascript theme={null}
// connected to the room as the local participant
room.on("connected", async () => {
  // use the webrtc api to get media from devices
  let callerStream = await navigator.mediaDevices.getUserMedia({
    audio: true,
    video: true
  });
  console.log("got local stream using getUserMedia...");

  // Get audio and video tracks from the MediaStream's
  // since the sdk works with MediaStreamTrack
  let callerAudioTrack = callerStream.getAudioTracks()[0];
  let callerVideoTrack = callerStream.getVideoTracks()[0];

  // add/publish the stream with the key "caller" to the room
  await room.addStream("caller", {
    audio: callerAudioTrack,
    video: callerVideoTrack
  });
  console.log("published local stream to the room...");

  // render the caller stream to the page/DOM
  let videoElement = document.getElementById("caller");
  videoElement.srcObject = callerStream;
});
```

Great, we're now publishing the local partipants'/caller's stream, to the room which the callee can subscribe to.

## Subscribe to the callee's remote stream

As a caller, how do we know when the callee has joined the room or has started publishing a stream?

That's where the `participant_joined` and the `stream_published` [events that we went over earlier](#lets-write-some-code), come into play.

We need to listen for teh `stream_published` event like this:

```javascript theme={null}
// a stream has been published to the room
room.on(
  "stream_published",
  async (participantId, streamKey, state) => {
  ...
  }
);
```

**Understanding how subscriptions work**

\_\_The `stream_published` event is triggered for both local and remote streams. \_\_

In our case, we want to know when the callee's remote stream starts publishing so we can subscribe to it. We already know that the caller has published a "caller" stream so we can ignore that event.

**Subscribing to a stream**

When a stream is published in a room it doesn't mean it's audio and video tracks are accessible, yet. In order to access a remote stream's track we must explicitly subscribe to that stream and wait for the `subscription_started` event.

```javascript theme={null}
// a stream has been published to the room
room.on("stream_published", async (participantId, streamKey, state) => {
  // ignore streams that are published by the local participant
  // we only care about remote stream from other remote participant
  let participant = state.participants.get(participantId);
  if (participant.origin === "local") {
    return;
  }

  // the remote stream is identified using the participantId and streamKey
  // you need to subscribe to a remote stream in order to access it's `MediaStreamTrack`s
  await room.addSubscription(participantId, streamKey, {
    audio: true,
    video: true
  });
});
```

## Render the callee's remote stream

We're almost there! Now, we can listen to the `subscription_started` event and use args from that handler to get the callee's remote stream and render it to the DOM.

```javascript theme={null}
// a subscription to a remote stream has started
room.on("subscription_started", (participantId, streamKey, state) => {
  console.log(
    `subscription to the: ${participantId} ${streamKey} stream started...`
  );

  // use a helper method to easily access a remote participants' stream
  let remoteStream = room.getParticipantStream(participantId, streamKey);

  // create a MediaStream object from the remote stream's track so we can render it
  // to a video element
  let remoteMediaStream = new MediaStream([
    remoteStream.audioTrack,
    remoteStream.videoTrack
  ]);
  const calleeVideo = document.getElementById("callee");
  calleeVideo.srcObject = remoteMediaStream;
});
```

## Run the app

To run the app:

* Open the sandbox, the browser here represents the caller's app
* Open the app in another tab, this represents the callee's application which you'll need to interact with.

**From the caller app**

* Click the call button
* You should be prompted by the browser for camera/micrphone access please allow.
* You should also see video from your device's camera and hear audio from the microphone
* You should see some console log, if things are working property
  * If you see errors your [client token has expired and you need to regenerate it.](#generate-a-client-token)

**From the callee's app**

* Click the call button
* Notice in the caller's app a stream subscription was logged
* The callee's video/audio stream is rendered.

***

### Android SDK

> Source: [https://developers.telnyx.com/docs/video/android-client-sdk.md](https://developers.telnyx.com/docs/video/android-client-sdk.md)

The Telnyx Video Android SDK is simple to use and makes it easy to get started with video calling. With this SDK, you can easily add video calling to your app with just a few lines of code.

It provides all the functionality you need to join and interact with a Telnyx Room from an Android application.

#### A link to the repo can be found here:

Android SDK

## Project structure

* SDK project: sdk module, containing all Telnyx SDK components as well as tests.
* Demo application: app module, containing a sample demo application utilizing the sdk module.

## Adding the SDK to your Android client application

Add Jitpack.io as a repository within your root level build file:

```
allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}
```

Add the dependency within the app level build file:

```
dependencies {
    implementation 'com.github.team-telnyx:telnyx-video-android:<tag>'
}
```

Tag should be replaced with the release version.

Then, import the TelnyxVideo SDK into your application code at the top of the class:

```
import com.telnyx.video.sdk.*
```

The '\*' symbol will import the whole SDK which will then be available for use within that class.

Remember to add and handle INTERNET, RECORD\_AUDIO and ACCESS\_NETWORK\_STATE permissions in order to properly use the SDK.

```
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
```

## Before connecting to a Room

## Get an API Key

You'll need an API key which is associated with your Mission Control Portal account under **API Keys**. You can learn how to do that [here](/docs/development/api-fundamentals/create-api-keys).

An API key is your credential to access our API. It allows you to:

* to authenicate to the Rest API
* to manage your access tokens

## Create a Room to join (if it doesn't exist)

In order to join a room you must create it, if it doesn't already exist. See our [Room Rest API](/api-reference/rooms/view-a-list-of-rooms) to create one.

There's also additional resources on other endpoints available to perform basic operations on a `Room`.

## Generate an a client token to join a room

In order to join a room you must have a client token for that `Room`. The `client token` is short lived and you will be able to refresh it using the `refresh token` provided with it when you  request a `client token`.

Please see the [docs here](/api-reference/rooms-client-tokens/create-client-token-to-join-a-room#create-client-token-to-join-a-room) to learn how to create a `client token`.

Now you are ready to connect to a video room that you previously created using the REST API.

## Connect to Room

To connect, you'll need to provide a **participantName** that will identify your user in that room.

You'll also need to provide an instance of *ExternalData* that will contain a *username* of type String and an Integer *id*. You will also provide your Android application's context in **context**

```
room = Room(
    context = context,
    roomId = UUID.fromString(roomId),
    roomToken = tokenInfo.token,
    externalData = ExternalData(id = 1234, username = "Android Participant")
    enableMessages = false
)
...

...
room.connect()

```

## Publish video/audio stream

To publish as video or audio stream, we will need an instance of *PublishConfigHelper* with the *application context*, camera *direction*, *streamKey* (unique for each stream published), and *streamId* (unique for each stream published)

```java theme={null}
//AUDIO
publishConfigHelper =
        PublishConfigHelper(
            context = requireContext(),
            direction = CameraDirection.FRONT,
            streamKey = SELF_STREAM_KEY // a key to identify this stream i.e: "self"
            streamId = SELF_STREAM_ID // RANDOM id to this stream i.e: "qlkj323kj423"
        )
    publishConfigHelper.createAudioTrack(true, // isTrackEnabled?
                                        AUDIO_TRACK_KEY // i.e: "myMic", "002"
                                        )
...

...
room.addStream(publishConfigHelper) // This stream is new. addStream() is called.
```

New streams can be created via the PublishConfigHelper class for both audio and video. They can be created together or added later independently.

```java theme={null}
//VIDEO

//NOTE: in this case, video is published in the same stream as above
publishConfigHelper.setSurfaceView(selfSurfaceRenderer) //Provide SurfaceRenderer

publishConfigHelper.createVideoTrack(
            CapturerConstraints.WIDTH.value,// i.e: 1280
            CapturerConstraints.HEIGHT.value,// i.e: 720
            CapturerConstraints.FPS.value, //i.e: 30 (fps)
            true, // isTrackEnabled?
            VIDEO_TRACK_KEY //i.e: i.e "cameraFeed", "001"
        )
...

...
room.updateStream(publishConfigHelper) // Stream already created, therefore updateStream is called.
```

Since stream is already created, it is only necessary to add the video track to PublishConfigHelper, and "update" the stream.

## Remove video/audio track

To remove a video or audio track, publishConfigHelper has to be modified in order to remove the unwanted track

````java theme={null}
//Considering publishConfigHelper is the same instance as above

publishConfigHelper?.let {
            it.stopCapture()   //In case of video, we "stop the capture", update the stream, and release the surface
            roomsViewModel.updateStream(it)
            selfSurface?.let { surface -> it.releaseSurfaceView(surface) }
        }
...

```java
publishConfigHelper?.let &#123;
            it.disposeAudio()    //In case of audio, we "dispose" audio and update the stream.
            roomsViewModel.updateStream(it)
        &#125;
````

## Remove stream

By removing the stream, we will remove all tracks added to it.

```java theme={null}
room.removeStream(SELF_STREAM_KEY) // a key to identify this stream i.e: "self"
```

## Video/Audio Observables

The Telnyx Video SDK for android works with mutable live data, and all the information you need to build your UI is provided through **observables** that will contain the most up to date information of the state of the room, such as **current participant list**, **talking events**, **stream information**, **participants added**, **participants leaving**, etc

## State Observable

```java theme={null}
    room.getStateObservable()
    // MutableLiveData
```

This observable will provide the current state of a room at any given moment.
We will receive a State object that will contain:

```java theme={null}
data class State(
    val action: String,
    val status: String,
    val participants: List,
    val streams: HashMap,
    val publishers: List,
    val subscriptions: List
)
```

**action** -> \`\`val action: String\`
is the cause of the latest change of State

```java theme={null}
enum class StateAction(val action: String) &#123;
    INITIALIZING_ROOM("initializing room"),
    STATUS_CHANGED("status changed"),
    ADD_PARTICIPANT("add participant"),
    REMOVE_PARTICIPANT("remove participant"),
    ADD_STREAM("add stream"),
    REMOVE_STREAM("remove stream"),
    ADD_SUBSCRIPTION("add subscription"),
    UPDATE_SUBSCRIPTION("update subscription"),
    REMOVE_SUBSCRIPTION("remove subscription"),
    PUBLISH("publish"),
    UNPUBLISH("unpublish"),
    UPDATE_PUBLISHED_STREAM("update published stream"),
    AUDIO_ACTIVITY("audio activity")
&#125;
```

**status** -> `val status: String`
is the status of the Room session, when that action happened

<a id="status-header" />

```java theme={null}
enum class Status(val status: String) &#123;
    INITIALIZED("initialized"),
    CONNECTING("connecting"),
    CONNECTED("connected"),
    DISCONNECTING("disconnecting"),
    DISCONNECTED("disconnected")
&#125;
```

**participants** -> `val participants: List<Participant>` <a id="participant-header" />
is the list of participants present in a room. A Participant is a UI representation in variables, for an attendee to the room session.

<a id="participant-header" />

```java theme={null}
data class Participant(
    var id: Long,
    val participantId: String,
    var externalUsername: String? = null,
    val isSelf: Boolean,
    var streams: MutableList = mutableListOf(),
    var isTalking: String?,
    var isAudioCensored: Boolean? = false,
    var audioBridgeId: Long? = null,
    var canReceiveMessages: Boolean = false
) : Serializable

data class ParticipantStream(
    val publishingId: Long? = null,
    var streamKey: String? = null,
    var audioEnabled: StreamStatus = StreamStatus.UNKNOWN,
    var videoEnabled: StreamStatus = StreamStatus.UNKNOWN,
    var audioTrack: AudioTrack? = null,
    var videoTrack: VideoTrack? = null
)

enum class StreamStatus(val request: String) &#123;
    UNKNOWN("unknown"),
    ENABLED("enabled"),
    DISABLED("disabled")
&#125;

```

**streams** -> `val streams: HashMap<Long, Stream>`
this hash map, contains a track of the currently available streams and the **id** of the **publisher** streaming them.
A stream, will contain tracks for video, audio or both.

```java theme={null}
data class Stream(
    val id: String,
    val key: String,
    val participantId: String,
    val origin: String,
    var isAudioEnabled: Boolean? = null,
    var isVideoEnabled: Boolean? = null,
    var isAudioCensored: Boolean? = null,
    var isVideoCensored: Boolean? = null
)
```

**publishers** -> `val publishers: List<Publisher>`

This will track all publishers in the room. A Publisher is an instance of an attendee or participant sharing some stream content in the room.

<Callout type="info">
  A single Participant, sharing multiple streams can be also linked to multiple Publisher ids.
</Callout>

```java theme={null}
data class Publisher(
    val audio_codec: String?,
    val video_codec: String?,
    val display: String, // see DisplayParameters.kt
    val id: Long,
    val talking: Boolean,
    val audio_moderated: Boolean? // aka Censored
)

data class DisplayParameters(
    val participantId: String,
    val telephonyEngineParticipant: Boolean? = null,
    val external: String? = null,
    val stream: StreamData? = null,
    val canReceiveMessages: Boolean? = null
)
```

**subscriptions** -> `val subscriptions: List<Subscription>`

Each time a publisher starts streaming information, we will have the option of subscribe/unsubscribe to/from it. This list will track all the subscriptions.

```java theme={null}
data class Subscription(
    var publisherId: Long,
    var status: SubscriptionStatus
)

enum class SubscriptionStatus(val status: String) &#123;
    NEVER_REQUESTED("never_requested"),
    PENDING("pending"),
    STARTED("started"),
    PAUSED("paused")
&#125;

```

## Event observables

Some observables will have a MutableLiveData of Event. Event is a wrapper of LiveData, and provide the means to ensure we only handle an observable once, no matter how many times we're set to observe it.
If the contents have already been handled, we won't get that content again, unless we *peekContent()* instead.

```java theme={null}
/**
 * Used as a wrapper for data that is exposed via a LiveData that represents an event.
 */
open class Event(private val content: T) &#123;

    var hasBeenHandled = false
        private set // Allow external read but not write

    /**
     * Returns the content and prevents its use again.
     */
    fun getContentIfNotHandled(): T? &#123;
        return if (hasBeenHandled) &#123;
            null
        &#125; else &#123;
            hasBeenHandled = true
            content
        &#125;
    &#125;

    /**
     * Returns the content, even if it's already been handled.
     */
    fun peekContent(): T = content
&#125;
```

## Participants Observable

```java theme={null}
room.getParticipantsObservable()
// MutableLiveData>
```

This mutable list will be received as soon as we join a Room session, and contains a list of the participants already present in the room, including yourself. The SDK will keep this list updated but won't post the changes, so this observer won't be fired again. See [Participant](#participant-header)

By receiving this list we can initialize a recycler adapter in order to show participants:

```java theme={null}
roomsViewModel.getParticipants().observe(viewLifecycleOwner) &#123; participants ->
  participants.let &#123; participantList ->
    participantAdapter.setData(participantList)
    if (participantList.size > 0)&#123;
      selfParticipantId = participantList[0].participantId
      selfParticipantHandleId = participantList[0].id
    &#125;
  &#125;
&#125;
```

## Joined Room Observable

```java theme={null}
    room.getJoinedRoomObservable()
    // MutableLiveData>
```

This mutable will fire an event as soon as we have connected to a room, and we have retrieved an *initial* list of [Participants](#participant-header)

It is useful when we want to update UI as soon as we have joined the room sucessfully.

<Callout type="info">
  This will be different to Status-CONNECTED that will be issued when we have successfully joined our plugins to handle session audio. See [Status](#status-header).
</Callout>

## Joined Participant Observable

```java theme={null}
    room.getJoinedParticipant()
    //MutableLiveData>
```

We receive the participant that has joined the room after client has already joined.
We can add the this reference to the list used in our adapter as:

```java theme={null}
roomsViewModel.getJoinedParticipant().observe(viewLifecycleOwner) &#123; participantJoined ->
  participantJoined?.let &#123; joinedParticipantEvent ->
    joinedParticipantEvent.getContentIfNotHandled()?.let &#123;
      participantsAdapter.addParticipant(it)
    &#125;
  &#125;
&#125;
```

## Leaving participant id Observable

```java theme={null}
    room.getLeavingParticipantId()
    //MutableLiveData>
```

We receive the publisherId that has leaved the room, and a reason for its exit ("Left" or "Kicked").

```java theme={null}
roomsViewModel.getLeavingParticipantId()
    .observe(viewLifecycleOwner) &#123; participantLeavingId ->
        participantLeavingId?.let &#123; (id, reason) ->
            participantAdapter.removeParticipant(id)
            if (id == selfParticipantHandleId && reason == "kicked") &#123;
                //It's ourselves, remove from the room.
                goBack(wasKicked = true)
                Toast.makeText(requireContext(), "You were kicked!", Toast.LENGTH_LONG).show()
            &#125;
        &#125;
    &#125;
&#125;
```

## Connected to room Observable

```java theme={null}
    room.getConnectionStatus()
    //LiveData
```

Receive true when we have opened a webSocket and connected to the room

```java theme={null}
roomsViewModel.connectedToRoomObservable().observe(this.viewLifecycleOwner) &#123;
    it?.let &#123; isConnected ->
        if (isConnected) &#123;
            buttonCreateRoom.isEnabled = true
        &#125;
    &#125;
&#125;
```

## Participant stream changed Observable

```java theme={null}
    room.getParticipantStreamChanged()
    //MutableLiveData>
```

We will receive here an event with the participant that has recently changed its video and/or audio stream status. Mutable list of participants will also be updated with this change, but here the specific individual is received.

## Stream status

```java theme={null}
enum class StreamStatus(val request: String) &#123;
    UNKNOWN("unknown"),
    ENABLED("enabled"),
    DISABLED("disabled")
&#125;
```

These status apply for audio, video and shared screen:

***UNKNOWN*** : initial status. We don't have information on whether this participant is sharing audio/video/screen

***ENABLED*** : the participant is sharing audio/video/screen and we can subscribe to a stream for it like:

***DISABLED*** : the participant is not sharing audio/video/screen. If we were subscribed to that participant audio/video/screen we can unsubscribe from it.

## Subscribe to a video stream

In order to subscribe to a **video stream**, there are 3 actions that needs to be performed:

```java theme={null}
StreamStatus.ENABLED -> &#123;
    // This notifies the WebRTC connection we're ready to receive stream information
    participantTileListener.subscribeTileToStream(model.participantId, "self")

    itemView.participant_tile_surface.visibility = View.VISIBLE
    itemView.participant_tile_place_holder.visibility = View.GONE

    // This ensures surfaces are initialized in an EglContext provided inside WebRTC connection
    participantTileListener.notifyTileSurfaceId(
        itemView.participant_tile_surface,
        model.participantId,
        "self"
    )

    model.streams.find &#123; it.streamKey == "self" &#125;?.videoTrack?.let &#123;
        if (viewHolderMap[holder] != it) &#123;  // NOTE: keep a map of surfaces to release

            // Updates only if previous register differs from what we need
            viewHolderMap[holder]?.removeSink(holder.itemView.participant_tile_surface)
            holder.itemView.participant_tile_surface.release()
            viewHolderMap[holder] = it

            it.addSink(itemView.participant_tile_surface)
            it.setEnabled(true)
        &#125;
    &#125;
&#125;
```

1 Provide the SDK with the same instance of *SurfaceViewRenderer* you want to *init*:

```java theme={null}
    room.setParticipantSurface(
        participantId: String,
        surface: SurfaceViewRenderer,
        streamKey: String //Stream key to indentify the webrtc connection to init this surface
    )
```

This will use the proper **WebRTC Connection** to provide an *EglContext* for the surface to be *init*

2 Use method *addSink()* to add a *SurfaceViewRenderer* instance to the videoTrack provided for a [Participant](#participant-header), and set that track to enable *videoTrack?.setEnabled(true)*

3 Subscribe to the stream a participant is providing

```java theme={null}

    participantTileListener.subscribeTileToStream(model.participantId, "self")

    // Eventually calls:

    room.addSubscription(
        participantId: String,
        streamKey: String,
        streamConfig: StreamConfig,
    )
```

*participantId* is the participant id that uniquely identifies a single participant in the room

*streamKey* i.e: "SharingSubscriptions" "CameraSubscriptions"

*streamConfig* whether we want to subscribe to audio/video or both. By default we will attempt both.

## Remove subscription to a \*video stream

To remove a subscription we need to issue

```java theme={null}
    room.removeSubscription(participantId: String, streamKey: String)
```

A good practice when handling surfaces is to make sure you remove this surface properly from the rendering context before eliminating or removing the surface from the UI:

```
    // Here, order is important
    videoTrack.removeSink(surfaceViewRenderer)
    surfaceViewRenderer.release()

```

## Participant Talking Observable

```java theme={null}
    room.getParticipantTalking()
    MutableLiveData>
```

We will receive the participant that has updated its talking status, and the stream key. This information is also modified in the [Participants list](#participants-observable)

```java theme={null}
data class Participant(
...
    var isTalking: String?, // Can either be "talking" or "stopped-talking"
...
) : Serializable
```

## Stats

We provide the method *getWebRTCStatsForStream()* to retrieve WebRTC stats
This request brings stats one time only, so if you want to keep receiving stats, you will need to use some sort of runnable or coroutine to recursively request for them such as:

```java theme={null}
mStatsjob = CoroutineScope(Dispatchers.Default).launch &#123;
        while (isActive) &#123;
            room.getWebRTCStatsForStream(participantId, streamKey, callback)
            delay(2000)
    &#125;
```

We have to provide *participantId*, *streamKey* that is the key that identifies the stream we need the stats from, and *callback* that is an **RTCStatsCollectorCallback** we provide to the WebRTC peer connection in order to retrieve the stats.

In Kotlin, method call will look like this

```java theme={null}

room.getWebRTCStatsForStream(participantId, streamKey) &#123; stats ->
    ...
    ...
&#125;
```

We can later parse the information retrieved to obtain the specific information we go after.
In our sample app, you will see the models we use for audio and video, both local and remote

## Remote Video stats

WEBRTC's *RTCStatsReport* can be parsed and mapped to RemoteVideoStreamStats

```java theme={null}
data class RemoteVideoStreamStats(
    val bytesReceived: Int,
    val frameHeight: Int,
    val frameWidth: Int,
    val framesDecoded: Int,
    val framesDropped: Int,
    val framesPerSecond: Double,
    val framesReceived: Int,
    val packetsLost: Int,
    val packetsReceived: Int,
    val totalInterFrameDelay: Double
) : StreamStats()
```

```java theme={null}
stats.statsMap.values.filter &#123; it.type == "inbound-rtp" &#125;
    .findLast &#123; it.toString().contains("mediaType: \"video\"") &#125;
    ?.let &#123; rtcVideoStats ->
        val videoStreamStats =
            gson.fromJson(
                rtcVideoStats.toString(),
                RemoteVideoStreamStats::class.java
            )
        videoStreamStats?.let &#123;
            Timber.tag("RoomFragment")
                .d("ParticipantID: $participantId video STATS: $it")
            // Proceed to use stats
        &#125;
    &#125;
```

## Local Video stats

WEBRTC's *RTCStatsReport* can be parsed and mapped to LocalVideoStreamStats

```java theme={null}
data class LocalVideoStreamStats(
    val bytesSent: Int,
    val codecId: String,
    val frameHeight: Int,
    val frameWidth: Int,
    val framesEncoded: Int,
    val framesPerSecond: Double,
    val framesSent: Int,
    val headerBytesSent: Int,
    val nackCount: Int,
    val packetsSent: Int,
) : StreamStats()
```

```java theme={null}
stats.statsMap.values.filter &#123; it.type == "outbound-rtp" &#125;
    .findLast &#123; it.toString().contains("mediaType: \"video\"") &#125;
    ?.let &#123; rtcVideoStats ->
        val videoStreamStats =
            gson.fromJson(
                rtcVideoStats.toString(),
                LocalVideoStreamStats::class.java
            )
        videoStreamStats?.let &#123;
            Timber.tag("RoomFragment")
                .d("SelfParticipant video STATS: $it")
            // Proceed to use stats
        &#125;
    &#125;
```

## Remote audio stats

WEBRTC's *RTCStatsReport* can be parsed and mapped to RemoteAudioStreamStats

```java theme={null}
data class RemoteAudioStreamStats(
    val audioLevel: Double,
    val bytesReceived: Int,
    val codecId: String,
    val headerBytesReceived: Int,
    val jitter: Double,
    val packetsLost: Int,
    val packetsReceived: Int,
    val totalAudioEnergy: Double,
    val totalSamplesDuration: Double,
    val totalSamplesReceived: Int,
) : StreamStats()
```

```java theme={null}
stats.statsMap.values.filter &#123; it.type == "inbound-rtp" &#125;
    .findLast &#123; it.toString().contains("mediaType: \"audio\"") &#125;
    ?.let &#123; rtcStats ->
        val audioStreamStats =
            gson.fromJson(
                rtcStats.toString(),
                RemoteAudioStreamStats::class.java
            )
        audioStreamStats?.let &#123;
            // Proceed to use stats
        &#125;
    &#125;
```

## Local audio stats

WEBRTC's *RTCStatsReport* can be parsed and mapped to LocalAudioStreamStats

```java theme={null}
data class LocalAudioStreamStats(
    val bytesSent: Int,
    val codecId: String,
    val headerBytesSent: Int,
    val packetsSent: Int,
    val retransmittedBytesSent: Int,
    val retransmittedPacketsSent: Int,
) : StreamStats()
```

```java theme={null}
stats.statsMap.values.filter &#123; it.type == "outbound-rtp" &#125;
    .findLast &#123; it.toString().contains("mediaType: \"audio\"") &#125;
    ?.let &#123; rtcStats ->
        val audioStreamStats =
            gson.fromJson(
                rtcStats.toString(),
                LocalAudioStreamStats::class.java
            )
        audioStreamStats?.let &#123;
            // Proceed to use stats
        &#125;
    &#125;
```

***

### iOS SDK

> Source: [https://developers.telnyx.com/docs/video/ios-client-sdk.md](https://developers.telnyx.com/docs/video/ios-client-sdk.md)

The Telnyx Video iOS SDK provides the functionality you need to join and interact with a video room from an iOS application.

#### A link to the repo can be found here:

iOS SDK

## If you prefer to jump right in

Have a look at our demo app: Telnyx Meet.

## An overview of the Video API

These are important concepts to understand.

* A `Room` represents a real time audio/video/screen share session with other people or participants. It is fundamental to building a video application.

* A `Participant` represents a person inside a `Room`. Each `Room` has one `Local Participant` and one or more `Remote Participants`.

* `Room State` tracks the state of the room as it changes making it extremely easy to understand what's happened to a `Room`.

  * `Room State` could change due to a `Local Participant` has started publishing a stream or because a `Remote Participant`left.

* A `Stream` represents the audio/video media streams that are shared by `Participants` in a `Room`
  * A `Stream` is indentified by it's `participantId` and `streamKey`

* A `Participant` can have one or more `Stream`'s associated with it.

* A `Subscription` is used to subscribe to a `Stream` belonging to a `Remote Participant`

## API of a Room

This should give you high level overview of the Room API and it's functionality.

```
    func connect(statusChanged: @escaping (_ status: RoomStatus) -> Void)

    func disconnect(completion: @escaping () -> Void)

    func updateClientToken(clientToken: String, completion: () -> Void)

    func addStream(key: StreamKey, audio: RTCAudioTrack?, video: RTCVideoTrack?, completion:
    @escaping OnSuccess, onFailed: @escaping OnFailed)

    func updateStream(key: StreamKey, audio: RTCAudioTrack?, video: RTCVideoTrack?, completion: @escaping OnSuccess, onFailed: @escaping OnFailed)

    func removeStream(key: StreamKey, completion: @escaping OnSuccess, onFailed: @escaping OnFailed)

    func addSubscription(participantId: ParticipantId, key: StreamKey, audio: Bool, video: Bool, completion: @escaping OnSuccess, onFailed: @escaping OnFailed)

    func pauseSubscription(participantId: ParticipantId, key: StreamKey, completion: @escaping OnSuccess, onFailed: @escaping OnFailed)

    func resumeSubscription(participantId: ParticipantId, key: StreamKey, completion: @escaping OnSuccess, onFailed: @escaping OnFailed)

    func updateSubscription(participantId: ParticipantId, key: StreamKey, audio: Bool, video: Bool, completion: @escaping OnSuccess, onFailed: @escaping OnFailed)

    func removeSubscription(participantId: ParticipantId, key: StreamKey, completion: @escaping OnSuccess, onFailed: @escaping OnFailed)

    func getWebRTCStatsForStream(participantId: ParticipantId, streamKey: StreamKey, completion: @escaping (_ stats: [String: [String: Any]]) -> Void)

    /// Helpers methods
    func getState() -> State
    func getLocalParticipant() throws -> Participant
    func getLocalStreams() throws -> [StreamKey: Stream]
    func getParticipantStream(participantId: ParticipantId, key: StreamKey) -> Stream?
    func getParticipantStreams(participantId: ParticipantId) throws -> [StreamKey: Stream]
```

After pasting the above content, Kindly check and remove any new line added

## Events that are triggered in a Room

Here's a list of events that will fire as you make API calls.

```
  /// Triggered each time the state is updated.
  var onStateChanged: ((_ state: State) -> Void)?

  /// Triggered when connected to a room.
  var onConnected: (() -> Void)?

  /// Triggered when disconnects from room / leaves room.
  var onDisconnected: (() -> Void)?

  /// Triggered when a remote participant joins the room.
  var onParticipantJoined: ((_ participantId: ParticipantId, _ participant: Participant) -> Void)?

  /// Triggered when a remote participant leaves the room.
  var onParticipantLeft: ((_ participantId: ParticipantId) -> Void)?

  /// Triggered after successfully registering a stream.
  var onStreamPublished: ((_ participantId: ParticipantId, _ streamKey: StreamKey) -> Void)?

  /// Triggered after successfully unregistering a stream.
  var onStreamUnpublished: ((_ participantId: ParticipantId, _ streamKey: StreamKey) -> Void)?

  /// Triggered when a local stream or a remote stream track has been enabled.
  /// Notifies consumers about remote stream tracks being enabled. For example: when audio is unmuted or video has started on a remote stream.
  var onTrackEnabled: ((_ participantId: ParticipantId, _ streamKey: StreamKey, _ kind: String) -> Void)?

  /// The oposite of` onTrackEnabled`. Triggers when a local stream or a remote stream track has been disabled.
  /// Notifies consumers about remote stream tracks being disabled. For example: when audio is muted or video has stopped on a remote stream.
  var onTrackDisabled: ((_ participantId: ParticipantId, _ streamKey: StreamKey, _ kind: String) -> Void)?

  /// Triggered when subscribed to a remote participant's stream.
  var onSubscriptionStarted: ((_ participantId: ParticipantId, _ streamKey: StreamKey) -> Void)?

  /// Triggered when an ongoing subscription is paused.
  var onSubscriptionPaused: ((_ participantId: ParticipantId, _ streamKey: StreamKey) -> Void)?

  /// Triggered when a paused subsription is resumed.
  var onSubscriptionResumed: ((_ participantId: ParticipantId, _ streamKey: StreamKey) -> Void)?

  /// Triggered when the subscription is reconfigured.
  var onSubscriptionReconfigured: ((_ participantId: ParticipantId, _ streamKey: StreamKey) -> Void)?

  /// Triggered when subscription is ended for a remote participant's stream.
  /// The subscription can be ended by calling `removeSubscription(ParticipantId,StreamKey)` or when the remote participant leaves.
  var onSubscriptionEnded: ((_ participantId: ParticipantId, _ streamKey: StreamKey) -> Void)?

  /// onError
  /// Triggered when there's an error processing incoming events from the server.
  var onError: ((_ error: SdkError) -> Void)?
```

After pasting the above content, Kindly check and remove any new line added

## Understanding the state of the Room

Everything in the SDK centers around the `Room` object. When the state of the `Room` changes (e.g. a new participant joins or a remote participant starts publishing a stream) the `onStateChanged` event is triggered.

The event is invoked with a `state` parameter which contains the current of the state of the `Room`.

```
 /// Triggered each time the state is updated.
 var onStateChanged: ((_ state: State) -> Void)?
```

After pasting the above content, Kindly check and remove any new line added

## Before getting started

## Install the SDK

Currently, the Telnyx iOS Video SDK can be installed using CocoaPods. For instructions on that check out our releases repo for iOS

## Get an API Key

You'll need an API key which is associated with your Mission Control Portal account under **API Keys**. You can learn how to do that [here](/docs/development/api-fundamentals/create-api-keys).

An API key is your credential to access our API. It allows you to:

* to authenicate to the Rest API
* to manage your access tokens

## Create a Room to join (if it doesn't exist)

In order to join a room you must create it, if it doesn't already exist. See our [Room Rest API](/api-reference/rooms-client-tokens/create-client-token-to-join-a-room#create-client-token-to-join-a-room) to create one.

There's also additional resources on other endpoints available to perform basic operations on a `Room`.

## Generate an a client token to join a room

In order to join a room you must have a client token for that `Room`. The `client token` is short lived and you will be able to refresh it using the `refresh token` provided with it when you  request a `client token`.

Please see the [docs here](/api-reference/rooms-client-tokens/create-client-token-to-join-a-room#create-client-token-to-join-a-room) to learn how to create a `client token`.

## Code Examples

Enough already let's get to the code. Here are some code examples to get your wet feet on how to start building something with the iOS video SDK.

## Participating in a Room

## Connect to a room

First, you'll need to create a `Room` instance and then connect to it. Once you're connected to a room, you can start sharing audio/video streams with other participant in the rooms.

**Important Note:**
This simply creates an instance of a Room in code it does not use the Rooms Rest API to create a room, mentioned above in "Create a Room to join"\*

```
// Create an instance of a Room

Room.createRoom(
            id: "92f83cf907b6426197ca6ccc83f3cba3",
            clientToken: accessToken,
            context: ["userid": 12345, "username": "jane doe"])
&#123; room in
  // Once a room is created we can connect to it
  room.connect &#123; status in

  &#125;
&#125;
```

After pasting the above content, Kindly check and remove any new line added

Once the room is connected you've joined the room as it's local participant. You can see this more clearly by, after connecting, get the local participant.

**Important Note:**
`Room` only has one `Local Participant` but can have multiple `Remote Participant`s.

```
Room.createRoom(
            id: "92f83cf907b6426197ca6ccc83f3cba3",
            clientToken: accessToken,
            context: ["username": "jane doe"])
&#123; room in
    room.connect &#123; status in
        let localParticipant = room.getLocalParticipant()
    &#125;
&#125;
```

After pasting the above content, Kindly check and remove any new line added

## What is Room context?

Context is any details you want to include about the `LocalParticipant` of the `Room`.

For instance, let's say you want to use `context` to identify a `Participant` with fields from an external system. You could pass a `userId` and `username` as context, like the code snippet above.

These details will be available to all `RemoteParticipant`s in the Room, when they are notified about your presence in the `Room`.

## Working with local media

Publishing audio and/or video from your camera or microphone works by using `MediaDevices`. `MediaDevices` is a helper class that we provide for you to make it easy to grab local media from your device.

```
let stream = MediaDevices.shared().getUserMedia(audio:true, video:true)

// If you want to run your app in a simulator provide a video file name to MediaDevices and it wil be used as the source for the cameraTrack. The video needs to be added to your Main.bundle, for things to work properly.

let cameraTrack = stream.videoTracks.first
let microphoneTrack = stream.audioTracks.first
```

After pasting the above content, Kindly check and remove any new line added

## Setting the quality of the local video

You can set the camera resolution and fps using `MediaDevices`.

```
#if !targetEnvironment(simulator)
guard let camera = RTCCameraVideoCapturer.captureDevices().first(where: &#123;
    $0.position == MediaDevices.shared().cameraPosition &#125;) else &#123;
        return
    &#125;
// Choose a suitable resolution/capture format
guard let captureFormat = RTCCameraVideoCapturer.supportedFormats(for: camera).sorted &#123; (f1, f2) -> Bool in
    let width1 = CMVideoFormatDescriptionGetDimensions(f1.formatDescription).width
    let width2 = CMVideoFormatDescriptionGetDimensions(f2.formatDescription).width
    return width1 &lt; width2
&#125;.first else &#123;
    return
&#125;
// Choose a suitable fps
let fps = captureFormat.videoSupportedFrameRateRanges.sorted &#123; return $0.maxFrameRate &lt; $1.maxFrameRate &#125;.first!
// Set the resolution and fps to `Mediadevices`.
MediaDevices.shared().set(format: captureFormat, fps: Int(fps.maxFrameRate))
#endif
```

After pasting the above content, Kindly check and remove any new line added

**Note: If you choose highest resolution and fps, the local video stream will lag if you have poor internet / bandwith**

## Publishing a stream

Once you have tracks from say, a local media device like video from a camera and/our audio from your micrphone you can use those to create a `Stream` and publish it in the `Room`.

```
room.connect &#123; status in
  let cameraTrack: RTCVideoTrack
  let microphoneTrack: RTCAudioTrack

  // The onStreamPublished will trigger once the stream has started publishing in the room
  room.onStreamPublished = &#123;
          participantId, streamKey in

  &#125;

  room.addStream(
      key: "camera/mic",
      audio: microphoneTrack,
      video: cameraTrack)&#123;

  &#125;
&#125;
```

After pasting the above content, Kindly check and remove any new line added

## Unpublishing a stream

If you can longer want to continue publishing a stream you can `unpublish` it.
Naturally the stream you want to unpublish must be added already.

```
room.connect &#123; status in
  // onStreamUnpublished will trigger once the stream has been unpublished
  room.onStreamUnpublished = &#123;
      participantId, streamKey in

  &#125;

  room.removeStream(key: "camera/mic") &#123;

  &#125;
&#125;
```

After pasting the above content, Kindly check and remove any new line added

## Working with Remote Participants and Streams

## A remote participant who joins or leaves the room

When a remote participant joins a a room you will be notified with the `Room.onParticipantJoined` event. And similiarly with `Room.onParticipantLeaving` when a remote participant leaves.

You can use these events to keep track of participants in the room.

```
room.connect &#123;
  room.onParticipantJoined = &#123;
    participantId in
    // This event will trigger when a remote participant joins the room
  &#125;
&#125;
```

After pasting the above content, Kindly check and remove any new line added

## Remote participants already in the room

When you connect to a `Room` there may already be remote participants in the `Room`. To understand who is in the `Room` after you connect use the `onParticipantJoined` event.

```
room.connect &#123;
  room.onParticipantJoined = &#123;
    participantId in
    // The event triggers for remote participants who are already in the room, just like it does for a new remote participant that joins the room.
  &#125;
&#125;
```

After pasting the above content, Kindly check and remove any new line added

## Display a remote participant's media

In order to understand how to display a remote participants' media let's review on subscriptions work in the API.

It might be helpful to review the Video API overview to get a better understanding of how a `Room` is modeled, especially `Stream` and `Subscription`.

**Major 🔑 about Subscriptions**
In order to display media from a remote stream you need to subscribe to it.

It's important to understand that your `Room` doesn't automatically subscribe to a remote stream being published. It's your **choice to decide whether to subscribe to a given stream.**

## Subscribing to a stream

So let's say the app your building has two users - let's them call them Alice and Bob.

First, Alice joins the `Room` and starts publishing a stream with audio from her microhone and video from her camera like this:
*NOTE: The app on Alice's device runs the following code...*

```
room.connect&#123;
  status in
  // Let's assume that we have the tracks for Alice's camera and microphone already

  // Alice starts publishing a stream in the room
  room.addStream(
      key: "self",
      audio: microphoneTrack,
      video: cameraTrack)&#123;
  &#125;
&#125;
```

After pasting the above content, Kindly check and remove any new line added

Bob wants to get Alice's stream so he can display it. In order to do so he needs to subscribe to Alice's stream.
*NOTE: The app on Bob's device runs the following code...*

```
room.connect &#123; status in
  // onStreamPublished event is triggered notifying him that Alice's stream is being published
  room.onStreamPublished = &#123;
          participantId, streamKey in

          // Bob subscribes to Alice's stream
          room.addSubscription(
            participantId: participantId,
            key: streamKey,
            audio: true,
            video: true
          )

  &#125;

  // onSubscriptionStarted triggers when the subscription to Alice's stream has started
  room.onSubscritionStarted = &#123;
    participantId, streamKey in
    // Bob needs to fetch the stream so he can display it
    let aliceStream = room.getParticipantStream(participantId: participantId, key: streamKey)

    // Alice's stream has a key of 'self' which has the audio track from her microphone and a video from her device's camera. Bob can use these tracks and display them as Alice in his app.
    let aliceCameraTrack = aliceStream.videoTrack
    let aliceMicrophoneTrack = aliceStream.audioTrack
  &#125;
&#125;
```

After pasting the above content, Kindly check and remove any new line added

## Handling remote streams that are already publishing in the Room

After you connect to a Room there may be remote participants already in the `Room` who are publishing streams in the Room.

To deal with that use the `onStreamPublished` event:

```
room.connect &#123; status in
  // After connecting to a room the onStreamPublished event will trigger for remote stream
  // that are already being published in the room
  // The onStreamPublished event will trigger
  room.onStreamPublished = &#123;
          participantId, streamKey in

  &#125;
&#125;
```

After pasting the above content, Kindly check and remove any new line added

## Disconnecting from a Room

To disconect from a room do:

```
room.disconnect &#123;
  // after the room disconnect that it's status is .disconnected
&#125;
```

After pasting the above content, Kindly check and remove any new line added

When you disconnect from a `Room` all `Remote Participant`'s will be notified that you've left the `Room` because the `Room.onParticipantLeft` event will fire on their `Room` instance.

***

## Fax

### Getting Started

> Source: [https://developers.telnyx.com/docs/programmable-fax/get-started.md](https://developers.telnyx.com/docs/programmable-fax/get-started.md)

## Introduction

Welcome to Telnyx Programmable Fax, a powerful tool that allows you to send and receive faxes seamlessly through HTTP endpoints. This guide will introduce you to the basics of setting up and using the Programmable Fax API.

## Prerequisites

Before you begin, you'll need to:

* Set up a Telnyx account. [Sign up](https://telnyx.com/sign-up) here if you haven't already.
* Obtain a phone number and create a Programmable Fax Application following our [Quickstart guide](/docs/programmable-fax/quickstart).
* Generate a Telnyx V2 API Authentication key via the [Telnyx Mission Control Portal](https://portal.telnyx.com/#/app/api-keys).

## Core Concepts

* Programmable Fax Application: Configure inbound traffic and authentication for your phone numbers.
* Outbound Voice Profile: Manage outbound traffic, billing, and allowed destinations.
* Webhooks: HTTP callbacks that notify your server about fax events.

## Hello World Example

Send Your First Fax Using cURL:

1. Authenticate Your Request: Include your API key in the header for authentication.
2. Specify Fax Details: Provide the media URL, connection ID (use your application id), and the 'to' and 'from' fax numbers in E.164 format.
3. Send the Fax:

```bash theme={null}
curl -X POST https://api.telnyx.com/v2/faxes \
--data-raw '&#123;
  "connection_id": "234423",
  "media_url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
  "to": "+13127367276",
  "from": "+13125790015"
&#125;' \
--header 'Authorization: Bearer YourAPIKey'
```

Replace placeholders with your actual data.

1. Verify the Response: Check for an HTTP 202 response indicating successful fax initiation

## Next Steps

* Explore advanced features like receiving faxes and detailed webhook management.
* Check out our detailed tutorials for comprehensive guidance.

## Additional Resources

* Visit our API documentation for in-depth understanding.
* Join our community forums for support and discussion

## Feedback

We value your feedback. If you have questions or need help troubleshooting, our support team is ready to assist. Contact us [here](https://support.telnyx.com/).

## Glossary

1. Programmable Fax Application: A configuration within the Telnyx platform used to manage inbound and outbound fax traffic. It includes settings for authentication and handling of fax transmissions.
2. Outbound Voice Profile: A setting in Telnyx that allows users to initiate outbound traffic, including faxes. It includes configurations for billing, traffic management, and permitted destinations.
3. Fax\_id: A unique identifier assigned to each fax transmission. It is used to track and manage specific fax communications within the Telnyx system.
4. Media\_url: The URL pointing to the document you wish to fax, typically in PDF format. This URL is included in the fax command to specify the fax content.
5. Connection\_id: An identifier for your specific application or connection within Telnyx, used to route the fax correctly and associate it with your account.
6. E.164 Format: An internationally-recognized format for phone numbers, used in specifying the 'to' and 'from' fields in fax commands.
7. Failure\_reason: A field in the webhook payload that provides the reason for a fax's failure, helping in troubleshooting and understanding delivery issues.
8. Ngrok: A tunneling tool used to expose a local server to the internet, often used for testing webhook endpoints in development environments.
9. cURL: A command-line tool used for sending requests to URLs, commonly used for interacting with APIs like Telnyx's Programmable Fax API.

***

### Quickstart

> Source: [https://developers.telnyx.com/docs/programmable-fax/quickstart.md](https://developers.telnyx.com/docs/programmable-fax/quickstart.md)

In this guide, you'll learn how to get started with Telnyx Programmable Fax within the Telnyx Portal. Just follow these simple steps:

1. Sign up for a Telnyx Account.
2. Create a Programmable Fax Application to configure how you connect your calls.
3. Buy or port a Phone Number.
4. Assign your number to the Programmable Fax Application.
5. Create an Outbound Voice Profile to configure your outbound call settings and assign it to your Programmable Fax Application.

***

## Step 1: Sign Up for a Telnyx Mission Control Portal account

Head to
telnyx.com/sign-up
to sign up for your free Telnyx account. It’ll give you access to our Mission Control Portal where you can buy phone numbers, set up and manage Programmable Fax Applications, and more.

## Step 2: Create a programmable fax application in the Telnyx portal

* Select
  "Programmable Fax"
  in the left-hand navigation menu.
* Click "Add New App".
* For testing purposes, you can set the webhook URL using an endpoint you create at
  [https://hookbin.com](https://hookbin.com)
  .

## Step 3: Buy a phone number

You can search for, buy, and provision new numbers, or port existing numbers - all within the Numbers section of the
Telnyx Portal
.

Simply click on "Numbers", then either "Search & Buy Numbers" or "Port Numbers" and follow the prompts.

> You can also do this programmatically via our **RESTful API**. Check out our documentation for number [searching](/api-reference/phone-number-search/list-available-phone-numbers) and [ordering](/api-reference/phone-number-orders/create-a-number-order).

## Step 4: Assign your phone number to the programmable fax application

Once your number has been purchased or ported, assign it to the Programmable Fax Application that you created in step 2. You can do this via the
My Numbers
section of the Portal.

![Docs Images (1)](https://images.ctfassets.net/4b49ta6b3nwj/2BJYzATnJpHLpozAjst37j/208a1637c3b0cefb7a5e40d4ede66226/Docs_Images__1_.png)

## Step 5: Create an outbound voice profile (not required for Inbound)

To initiate an outbound fax, you must create an Outbound Voice Profile and assign your Programmable Fax Application to it.

Select
"Outbound Voice Profiles"
on the left-hand navigation menu, click "+Add New Profile" and set up your profile name. Add the Programmable Fax Application you just created above, the traffic type, the service plan, and your desired billing method.

> You can also do this programmatically via our **RESTful API**. Check out our documentation for [Outbound Voice Profiles](/docs/voice/outbound-voice-profiles).

That’s it. You're all set to start integrating Telnyx with your applications.

## Where to next?

Head over to our tutorials to learn how to [send a fax](/docs/programmable-fax/send-a-fax-api) and [receive a fax](/docs/programmable-fax/receive-a-fax-api) via API.

After you [setup your development environment](/development) you can also dive deeper into [sending commands](/docs/programmable-fax/sending-commands) and [receiving webhooks](/docs/programmable-fax/receiving-webhooks).

***

### Receiving Webhooks

> Source: [https://developers.telnyx.com/docs/programmable-fax/receiving-webhooks.md](https://developers.telnyx.com/docs/programmable-fax/receiving-webhooks.md)

When you send a Programmable Fax command and receive a successful response (i.e. 200 OK), you can expect to receive a webhook. The webhook will be delivered to the primary URL specified on the Application associated with the call. If that URL does not resolve, or your application returns a non 200 OK response, the webhook will be delivered to the failover URL, if one has been specified.

In order to minimize webhook delivery time, Telnyx:

* does not enforce the order in which webhooks are delivered
* retries webhook delivery if your application does not respond within a certain time threshold.

As a result, you may encounter:

* out-of-order webhooks
* simultaneous (or near simultaneous) webhooks
* duplicate webhooks

Duplicate webhooks may cause your application to issue duplicate commands. You can instruct Telnyx to ignore duplicate commands by sending a command\_id parameter as part of your commands. Commands with duplicate `command_ids` within 60 seconds will be ignored.

## Example: Receiving a webhook from an outbound fax

When you place an outbound fax, you will receive a number of webhooks indicating the current status of the fax. The first webhook you will receive will have A queued status indicating that Telnyx successfully received the request to send the fax.

```json theme={null}
&#123;
  "data": &#123;
    "event_type": "fax.queued",
    "id": "3691d047-d22a-424d-80ed-fe871981aa6d",
    "occurred_at": "2020-04-22T19:32:12.538002Z",
    "record_type": "event",
    "payload": &#123;
      "call_duration_secs": 50,
      "connection_id": "7267xxxxxxxxxxxxxx",
      "direction": "outbound",
      "fax_id": "b679398e-8b4c-46bd-8630-6797f1ab5228",
      "from": "+35319605860",
      "original_media_url": "https://www.telnyx.com/telnyx-fax/1.pdf",
      "partial_content": true,
      "status": "queued",
      "to": "+13129457420",
      "user_id": "a5b1dfa3-fd2e-4e02-8ea4-xxxxxxxxxxxx"
    &#125;
  &#125;,
  "meta": &#123;
    "attempt": 1,
    "delivered_to": "http://example.com/webhooks"
  &#125;
&#125;
```

FIELD
VALUE
record\_type
Description of the record.
id
unique id for the webhook
event\_type
The type of event
occurred\_at
ISO-8601 datetime of when event occured
to
Destination number or SIP URI of the call
from
Number or SIP URI placing the call
fax\_id
Unique ID for the Programmable Fax
client\_state
Configurable state to track commands
status
Can be one of queued, media.processed, sending.started, delivered, failed

## Example: Receiving a webhook on successful fax delivery

```bash theme={null}
&#123;
  "event_type": "fax.delivered",
  "id": "3320554f-6b74-4138-a74b-a1e2ec7eaf8b",
  "occurred_at": "2022-01-07T10:01:43.677850Z",
  "payload": &#123;
    "call_duration_secs": 79,
    "connection_id": "1232154810234",
    "direction": "outbound",
    "fax_id": "c62be5bc-9b13-4b6c-abda-34dd8b541287",
    "from": "+19459457421",
    "original_media_url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
    "page_count": 1,
    "status": "delivered",
    "to": "+13129457420",
    "user_id": "bdaa1f9f-1018-4156-867d-6c4ac9f556eb"
  &#125;
&#125;
```

## Example: Receiving a webhook on failed fax delivery

```bash theme={null}
&#123;
  "event_type": "fax.failed",
  "id": "d906ecda-db21-428e-9ca0-74dae7e7c144",
  "occurred_at": "2022-01-05T22:23:46.888808Z",
  "payload": &#123;
    "connection_id": "1232154810234",
    "direction": "outbound",
    "failure_reason": "user_busy",
    "fax_id": "f7b303ed-674c-4962-951b-848380510893",
    "from": "+19459457421",
    "original_media_url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
    "status": "failed",
    "to": "+13129457420",
    "user_id": "417b0cc2-39e0-4ab9-b116-e56543649aa9"
  &#125;
&#125;
```

## Possible failure reasons

Inspect the `failure_reason` field in webhook's payload to debug failed deliveries of your faxes. The possible failure reasons are:

* `account_disabled`
* `connection_channel_limit_exceeded`
* `destination_invalid`
* `destination_not_in_countries_whitelist`
* `destination_not_in_service_plan`
* `destination_unreachable`
* `fax_initial_communication_timeout`
* `fax_signaling_error`
* `invalid_ecm_response_from_receiver`
* `no_outbound_profile`
* `outbound_profile_channel_limit_exceeded`
* `outbound_profile_daily_spend_limit_exceeded`
* `receiver_call_dropped`
* `receiver_communication_error`
* `receiver_decline`
* `receiver_incompatible_destination`
* `receiver_invalid_number_format`
* `receiver_no_answer`
* `receiver_no_response`
* `receiver_recovery_on_timer_expire`
* `receiver_unallocated_number`
* `service_unavailable`
* `unverified_destination_not_allowed`
* `unverified_origination_number`
* `user_busy`
* `user_channel_limit_exceeded`

***

### Sending Commands

> Source: [https://developers.telnyx.com/docs/programmable-fax/sending-commands.md](https://developers.telnyx.com/docs/programmable-fax/sending-commands.md)

A Programmable Fax API command is sent with a fax\_id. The fax\_id allows a user to communicate to Telnyx the fax the user wants to take an action on.

The Telnys Programmable Fax API supports PDF files. To initiate sending the fax, we need to send the request to the Telnyx Programmable Fax API endpoint [https://api.telnyx.com/v2/faxes](https://api.telnyx.com/v2/faxes).

## Authentication

With the request we need to send additional parameters containing authentication information so Telnyx knows which account to send the fax from and information about the destination and file being sent.

HEADER
DESCRIPTION
media\_url
Authorization: Bearer YOUR\_API\_KEY
connection\_id
Authorization: Bearer YOUR\_API\_KEY
to
Authorization: Bearer YOUR\_API\_KEY
from
Authorization: Bearer YOUR\_API\_KEY
Authorization: Bearer
The prefix to your API V2 key created in Step 2.

## Available commands and their expected Webhooks

Telnyx sends webhooks to update on the status of Programmable Fax. Webhooks will also be sent in response to requests to list and delete faxes.

COMMAND
ASSOCIATED WEBHOOKS
Send a fax
fax.queued
fax.media.processed
fax.sending.started
fax.delivered
fax.failed

## Response when sending command

When you send a Programmable Fax API Command, you will immediately receive an http response. Responses include, but are not limited to:

HTTP STATUS CODE
MESSAGE
DESCRIPTION
202
OK
The request succeeded.
403
Forbidden
The request was valid, however the user is not authorized to perform this action.
404
Not Found
The requested resource could not be found.
422
Invalid Parameters
The request has invalid parameters.

## Example: Sending commands

To send a fax, send a POST request to the [https://api.telnyx.com/v2/faxes](https://api.telnyx.com/v2/faxes) endpoint as shown in the example below.

## WITH A API V2 KEY

```bash theme={null}
curl -X POST https://api.telnyx.com/v2/faxes \
--data-urlencode "media_url=https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf" \
--data-urlencode "connection_id=1232154810234" \
--data-urlencode "to=+13129457420" \
--data-urlencode "from=+19459457421" \
--header "Authorization: Bearer APIAuthKey_fromPortal"
```

After pasting the above content, Kindly check and remove any new line added

***

### Send a fax via API

> Source: [https://developers.telnyx.com/docs/programmable-fax/send-a-fax-api.md](https://developers.telnyx.com/docs/programmable-fax/send-a-fax-api.md)

The Telnyx Programmable Fax API lets you send, receive, and manage faxes through a set of easy-to-use HTTP endpoints.

In this guide, you'll learn how to send a fax using the Programmable Fax API in four simple steps.

## Step 1: Setting up with Telnyx

### Setup a Telnyx account, phone number, and programmable fax application

First, follow our [Quickstart](/docs/programmable-fax/quickstart) guide to create a Telnyx account, phone number, and Fax Application.

## Step 2: API authentication

> If you already have a Telnyx V2 API Authentication key, skip to Step 3.

1. In the Telnyx
   Mission Control Portal
   , in the left menu bar navigate to "
   API Keys
   ".
2. Ensure, API V2 is selected in the horizontal menu bar.
3. Click "Create API key"
4. Copy the API key and save it somewhere safe.

## Step 3: Send a PDF with the programmable fax API using curl

The Telnyx Programmable Fax API supports PDF files. To initiate sending a fax, we need to send the request to the Telnyx Programmable Fax API endpoint `https://api.telnyx.com/v2/faxes`.

We need to send additional parameters containing authentication information with the request, so Telnyx knows which account to send the fax from and information about the destination and file being sent.

Header
Description
media\_url
The URL of the PDF used for the fax's media.
connection\_id
The app ID or connection ID to send the fax with.
to
The fax enabled phone number (in E.164 format), or SIP URI, the fax will be sent to.
from
The phone number, in E.164 format, the fax will be sent from.
Authorizaton: Bearer
The prefix to your API V2 key created in Step 2.

Now that we know what we need to include in our request, we can use a number of different methods to structure and send it. In this example, we are going to use curl straight from the command line. You can also use a client such as
Postman
to structure your request.

```bash theme={null}
curl -X POST https://api.telnyx.com/v2/faxes \
--data-urlencode "media_url=https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf" \
--data-urlencode "connection_id=1232154810234" \
--data-urlencode "to=+13129457420" \
--data-urlencode "from=+19459457421" \
--header "Authorization: Bearer APIAuthKey_fromPortal"
```

After pasting the above content, Kindly check and remove any new line added

Simply copy the above code into your favorite text editor, paste in your parameters from the table in place of the existing data, and execute the command.

## Step 4: Verify HTTP response and receive webhooks

If you have successfully structured your command and the fax has begun sending, the Programmable Fax API will respond with HTTP 202.

If you do not receive a HTTP 202 response, double-check your request and try again. Explanations of other response codes can be found in our [API reference](/docs/programmable-fax/sending-commands#response-when-sending-command).

Once the request to send the fax has been successfullly received by Telnyx, you should begin receiving a series of webhooks to the URL that you specified in your Fax Application.

The webhooks you should receive are:

* `fax.queued`
* `fax.media.processed`
* `fax.sending.started`
* `fax.delivered`
* `fax.failed`

That's it! Once you've received a `fax.delivered` webhook, your fax has been delivered to its destinaion.

## Webhook examples

#### On successful delivery

```json theme={null}
&#123;
  "event_type": "fax.delivered",
  "id": "3320554f-6b74-4138-a74b-a1e2ec7eaf8b",
  "occurred_at": "2022-01-07T10:01:43.677850Z",
  "payload": &#123;
    "call_duration_secs": 79,
    "connection_id": "1232154810234",
    "direction": "outbound",
    "fax_id": "c62be5bc-9b13-4b6c-abda-34dd8b541287",
    "from": "+19459457421",
    "original_media_url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
    "page_count": 1,
    "status": "delivered",
    "to": "+13129457420",
    "user_id": "bdaa1f9f-1018-4156-867d-6c4ac9f556eb"
  &#125;
&#125;
```

After pasting the above content, Kindly check and remove any new line added

#### On failed delivery

```json theme={null}
&#123;
  "event_type": "fax.failed",
  "id": "d906ecda-db21-428e-9ca0-74dae7e7c144",
  "occurred_at": "2022-01-05T22:23:46.888808Z",
  "payload": &#123;
    "connection_id": "1232154810234",
    "direction": "outbound",
    "failure_reason": "user_busy",
    "fax_id": "f7b303ed-674c-4962-951b-848380510893",
    "from": "+19459457421",
    "original_media_url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
    "status": "failed",
    "to": "+13129457420",
    "user_id": "417b0cc2-39e0-4ab9-b116-e56543649aa9"
  &#125;
&#125;
```

After pasting the above content, Kindly check and remove any new line added

#### Possible failure reasons

Inspect the `failure_reason` field in webhook's payload to debug failed deliveries of your faxes. The possible failure reasons are:

* `account_disabled`
* `connection_channel_limit_exceeded`
* `destination_invalid`
* `destination_not_in_countries_whitelist`
* `destination_not_in_service_plan`
* `destination_unreachable`
* `fax_initial_communication_timeout`
* `fax_signaling_error`
* `invalid_ecm_response_from_receiver`
* `no_outbound_profile`
* `outbound_profile_channel_limit_exceeded`
* `outbound_profile_daily_spend_limit_exceeded`
* `receiver_call_dropped`
* `receiver_communication_error`
* `receiver_decline`
* `receiver_incompatible_destination`
* `receiver_invalid_number_format`
* `receiver_no_answer`
* `receiver_no_response`
* `receiver_recovery_on_timer_expire`
* `receiver_unallocated_number`
* `service_unavailable`
* `user_busy`
* `user_channel_limit_exceeded`

## Where to next?

Continue to our next tutorial to learn how to [receive inbound faxes](/docs/programmable-fax/receive-a-fax-api) via API.

***

### Receive a fax via API

> Source: [https://developers.telnyx.com/docs/programmable-fax/receive-a-fax-api.md](https://developers.telnyx.com/docs/programmable-fax/receive-a-fax-api.md)

The Telnyx Programmable Fax API lets you send, receive and manage faxes through a set of easy-to-use HTTP endpoints.

In this guide, you'll learn how to handle receiving a fax using the Programmable Fax API.

## Step 1: Setup with Telnyx

### Setup a Telnyx account, phone number, and programmable fax application

First, follow our [Quickstart](/docs/programmable-fax/quickstart) guide to create a Telnyx account, phone number, and Fax Application.

The phone number that you select and associate with the Programmable Fax Application will receive incoming fax calls. You can also port an existing number to Telnyx and use it for Programmable Fax.

### Step 2: Receiving Webhooks

In order for the webhooks in this tutorial to work, Telnyx must be able to send your web application an HTTP request over the Internet. That means your application needs to have a URL or IP address that Telnyx can reach. Telnyx sends webhooks to the URL or IP address to notify your application of incoming faxes.

For the purpose of this tutorial, we're using
ngrok
, a popular tunneling tool used to expose a locally running application to the internet. ngrok gives you a public URL for a local port on your development machine, which you can use to configure your Telnyx webhooks as described above.

Download and install ngrok, then use it at the command line to create a tunnel to whatever port your web application is running on. For example, this command will create a public URL for a web application listening on port 3000.

```bash theme={null}
ngrok http 3000
```

After executing that command, you will see that ngrok has given your application a public URL that you can use in your webhook connection configuration in the
Telnyx Mission Control Portal
.

<img src="https://mintcdn.com/telnyx/v2FpkbJg6PQ53qHU/assets/images/twiml-conferencing-3.png?fit=max&auto=format&n=v2FpkbJg6PQ53qHU&q=85&s=c1690ae85feaa5bfaa60185ca55ab147" alt="Send and receive fax using Telnyx Programmable Fax API" width="657" height="218" data-path="assets/images/twiml-conferencing-3.png" />

Grab your ngrok public URL and head back to the Programmable Fax Application you configured earlier. Now in the field under "Send a webhook to the URL" enter your new ngrok URL from either of the "Forwarding" URLs ngrok provided us with depending on if you want to use HTTP or HTTPS.

You're now all set up to receive webhooks for events related to inbound faxes. If you use the same Programmable Fax Application for sending faxes, you will receive events to the same ngrok URL you just created.

### Example Webhooks

If you have set everything up correctly, any time an inbound fax is received you can expect to receive the following webhooks:

Webhook Name
Description
fax.receiving.started
The fax has begun transmitting to Telnyx successfully.
fax.media.processing.started
Telnyx has received the fax and is generating the digital PDF file.
fax.received
The PDF has been generated and the file is ready to be downloaded.
fax.failed
Transmission of the fax failed. Check the \<code>failure\_reason\</code> for more details.

### Fax has begun transmitting to Telnyx

```json theme={null}
&#123;
  "data": &#123;
    "event_type": "fax.receiving.started",
    "id": "bc004786-f166-4dd3-8c5d-737990b501bc",
    "occurred_at": "2020-08-27T16:33:29.684247Z",
    "payload": &#123;
      "connection_id": "1447842681660114324",
      "direction": "inbound",
      "fax_id": "9fbc3f0d-5495-42af-9a4e-c57a235d9182",
      "from": "+16132484872",
      "status": "receiving",
      "to": "+17733372863",
      "user_id": "19a75cea-02c6-4b9a-84fa-c9bc8341feb8",
      "caller_id": "+16132484872"
    &#125;,
    "record_type": "event"
  &#125;,
  "meta": &#123;
    "attempt": 1,
    "delivered_to": "https://1a3097.ngrok.io/"
  &#125;
&#125;
```

### Fax transmission is complete and Telnyx is converting to PDF

```json theme={null}
&#123;
  "data": &#123;
    "event_type": "fax.media.processing.started",
    "id": "35e33b02-6365-47d0-93b7-3bfec97c467e",
    "occurred_at": "2020-08-27T16:33:33.175396Z",
    "payload": &#123;
      "connection_id": "1447842681660114324",
      "direction": "inbound",
      "fax_id": "f72eebbe-f9b6-4f0f-b652-03e742e110d5",
      "from": "+16132484850",
      "status": "media.processing",
      "page_count": 2,
      "to": "+17733372863",
      "user_id": "19a75cea-02c6-4b9a-84fa-c9bc8341feb8",
      "caller_id": "+16132484872"
    &#125;,
    "record_type": "event"
  &#125;,
  "meta": &#123;
    "attempt": 1,
    "delivered_to": "https://1a3097.ngrok.io/"
  &#125;
&#125;
```

### PDF has been generated and is ready for download

The media\_url field contains a signed AWS link to a PDF of the received fax. This URL is valid for 10 minutes before the file is no longer accessible so be sure to download the file if you wish to keep it!

```json theme={null}
&#123;
  "data": &#123;
    "event_type": "fax.received",
    "id": "4844b70c-3c6c-4c3a-ba2e-e4c785f02d24",
    "occurred_at": "2020-08-27T16:33:36.843054Z",
    "payload": &#123;
      "call_duration_secs": 50,
      "connection_id": "1447842681660114324",
      "direction": "inbound",
      "fax_id": "f72eebbe-f9b6-4f0f-b652-03e742e110d5",
      "from": "+16132484850",
      "media_url": "https://s3.amazonaws.com/faxes-prod/19a75cea-02c6-4b9a-84fa-c9bc8341feb8/f72eebbe-f9b6-4f0f-b652-03e742e110d5.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...%2F20200827%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20200827T163336Z&X-Amz-Expires=7200&X-Amz-SignedHeaders=host&X-Amz-Signature=...",
      "page_count": 2,
      "partial_content": false,
      "status": "received",
      "to": "+17733372863",
      "user_id": "19a75cea-02c6-4b9a-84fa-c9bc8341feb8",
      "caller_id": "+16132484872"
    &#125;,
    "record_type": "event"
  &#125;,
  "meta": &#123;
    "attempt": 1,
    "delivered_to": "https://1a3097.ngrok.io/"
  &#125;
&#125;
```

### Uh oh, something has gone wrong and the fax failed

Inbound faxes can fail for a variety of reasons. Some of the most common reasons are that the sending party hung up before the fax was finished transmitting or didn't send anything at all.

```json theme={null}
&#123;
  "data": &#123;
    "event_type": "fax.failed",
    "id": "1a7405a6-696c-4369-a0b1-168e4bb7e22c",
    "occurred_at": "2020-08-27T16:17:51.844250Z",
    "payload": &#123;
      "connection_id": "1447842681660114324",
      "direction": "inbound",
      "failure_reason": "sender_call_dropped",
      "fax_id": "181533f3-b0b8-4bcd-ab01-b33cd8698508",
      "from": "+16617480240",
      "status": "failed",
      "to": "+17733372863",
      "user_id": "19a75cea-02c6-4b9a-84fa-c9bc8341feb8",
      "caller_id": "+16132484872"
    &#125;,
    "record_type": "event"
  &#125;,
  "meta": &#123;
    "attempt": 1,
    "delivered_to": "https://1a3097.ngrok.io/"
  &#125;
&#125;
```

### Where to next?

Go back to our previous tutorial to learn how to [send outbound faxes](/docs/programmable-fax/send-a-fax-api) via API.

***

### Email to fax

> Source: [https://developers.telnyx.com/docs/programmable-fax/email-to-fax.md](https://developers.telnyx.com/docs/programmable-fax/email-to-fax.md)

**⏱ 60 minutes build time.**

\_\_🧰 Clone the sample application from our
GitHub repo
\_\_

In this tutorial, you'll learn how to use the Telnyx Fax API to receieve faxes to your email in Python.

Our Programmable Fax API, combined with our Numbers API, provides everything you need to build a robust call tracking application:

The Numbers API enables you to search the Telnyx phone number inventory in real time; filtering by Area Code, City/State, and more to find the perfect local number for your use-case.
Call Control enables you to quickly setup dynamic forwarding numbers, toggle dual-channel recording, join/leave dynamic conferences, and pull post-call analytics.
By following this tutorial, you'll be able to:

> 1. Configure a Telnyx portal account.
> 2. Set up a Telnyx Fax profile and an outbound voice profile.
> 3. How to send a fax to your email using Telnyx.
> 4. How to send a fax using your email and to Telnyx Fax profile.

## What you'll need to get started with email to fax

1. A Telnyx account- take a minute to
   sign up
   to our self-service portal.
2. A Telnyx phone number that's enabled with a
   Telnyx Fax Appliction
   and a Telnyx
   Outbound Voice profile
   .
3. Ability to receive webhooks (with something like [ngrok](/docs/development/development-tools/ngrok-setup/index#ngrok "ngrok"))
4. [Python and PIP](/docs/development/sdk/python "Python and PIP") installed
5. An AWS Account setup with proper profiles and groups with IAM for S3. Check out the
   Quickstart
   for more information.
6. Finally you'll need a
   Mailgun account
   . If you're planning on using this guide to build an email to fax application you'll need an account with the ability to setup
   inbound routes
   .

### Create a Telnyx Mission Control Portal account

To get started, you'll need to
create an account
. Verify your email address and you can log into the
Mission Control Portal
to get started.

### Usage

The following environmental variables need to be set:

Variable
Description
TELNYX\_API\_KEY
Your Telnyx API Key
TELNYX\_PUBLIC\_KEY
Telnyx Public Key
TELNYX\_S3\_BUCKET
The name of the bucket to upload the media attachments
TELNYX\_FAX\_CONNECTION\_ID
The connection ID for your Fax Applications
MAILGUN\_API\_KEY
Your Mailgun API Key
MAILGUN\_DOMAIN
Your Mailgun Domain.
PORT
Defaults to 8000 The port the app will be served

### .env file

This app uses the excellent python-dotenv package to manage environment variables.

Make a copy of .env.sample and save as .env and update the variables to match your creds.

TELNYX\_PUBLIC\_KEY="+kWXUag92mcUMFQopVlff7ctD/m2S/IoXv+AlI1/5a0="
TELNYX\_API\_KEY="KEYI"
TELNYX\_S3\_BUCKET=telnyx-mms-demo
TELNYX\_FAX\_CONNECTION\_ID=36092346987
MAILGUN\_API\_KEY="123-432-123"
MAILGUN\_DOMAIN="sandbox367c5ec1512d458e95f5e5c60f5fe97a.mailgun.org"
PORT=8000

### Callback URLs for Telnyx application

Callback Type
URL
Fax Callbacks
`{ngrok-url}/faxes`
Email Callbacks
`{ngrok-url}/email/inbound`

### Install

Run the following commands to get started:

\$ git clone [https://github.com/team-telnyx/demo-python-telnyx.git](https://github.com/team-telnyx/demo-python-telnyx.git)

### Ngrok

This application is served on the port defined in the runtime environment (or in the .env file). Be sure to launch ngrok for that port:

./ngrok http 8000

ngrok by @inconshreveable                                                                                                                               (Ctrl+C to quit)

Session Status                online
Account                       Little Bobby Tables (Plan: Free)
Version                       2.3.35
Region                        United States (us)
Web Interface                 [http://127.0.0.1:4040](http://127.0.0.1:4040)
Forwarding                    [http://your-url.ngrok.io](http://your-url.ngrok.io) -> [http://localhost:8000](http://localhost:8000)
Forwarding                    [https://your-url.ngrok.io](https://your-url.ngrok.io) -> [http://localhost:8000](http://localhost:8000)

Connections                   ttl     opn     rt1     rt5     p50     p90
0       0       0.00    0.00    0.00    0.00

At this point you can point your application to generated ngrok URL + path:

(Example: `http://{your-url}.ngrok.io/faxes`).

## Run

### High level code overview

#### Receiving fax, sending email

1. Receive the webhook from Telnyx indicating fax is incoming
2. We only only care about the fax.received webhook for inbound faxes
3. Extract the to/from and other information about the fax
4. Download the attachment and save locally
5. Lookup the association between phone\_number and email
6. Create and send an email via Mailgun with downloaded media as an attachment

#### Sending fax

1. Receive webhook from mailgun for incoming email
2. Extract the prefix of the email ([19198675309@MAILGUN\_DOMAIN.com](mailto:19198675309@MAILGUN_DOMAIN.com), we want 19198675309) and prepend the +
3. Lookup the association between email and phone\_number to determine the from phone number.
4. Save the first attachment locally
5. Upload the attachment to S3
6. Create and send a fax to the phone number extracted above

### Update "DB"

The app as provided uses a dumb-hard-coded database to minimize dependencies for the sake of examples. There is an in memory database defined at the top that associates an email to a Telnyx number.

```
    DB = [
&#123;
    "email": "@telnyx.com",
    "phone_number": "+"
&#125;
    ]
```

* phone\_number is a **Telnyx** phone number
* email is the email to associate with that phone number

#### Receiving faxes

```
&#123;+19198675309&#125; ==(faxes)==> &#123;telnyx_phone_number&#125; ==(emails)==> &#123;email_as_defined&#125;
```

#### Sending faxes

```
&#123;email_as_defined&#125; ==(emails)==> &#123;destination_phone_number@MAILGUN_DOMAIN&#125; ==(faxes)==> &#123;destination_phone_number&#125;
```

### Launch the app and update callbacks

Start the server python app.py

When you are able to run the server locally, the final step involves making your application accessible from the Internet. So far, we've set up a local web server. This is typically not accessible from the public internet, making testing inbound requests to web applications difficult.

The best workaround is a tunneling service. They come with client software that runs on your computer and opens an outgoing permanent connection to a publicly available server in a data center. Then, they assign a public URL (typically on a random or custom subdomain) on that server to your account. The public server acts as a proxy that accepts incoming connections to your URL, forwards (tunnels) them through the already established connection and sends them to the local web server as if they originated from the same machine. The most popular tunneling tool is ngrok.

Check out the [ngrok setup](/docs/development/development-tools/ngrok-setup/index#ngrok "ngrok setup") walkthrough to set it up on your computer and start receiving webhooks from inbound faxes to your newly created application.

Once you've set up ngrok or another tunneling service you can add the public proxy URL to your Inbound Settings in the Mission Control Portal. To do this, click the edit symbol \[✎] next to your Fax Profile. In the "Inbound Settings" > "Webhook URL" field, paste the forwarding address from ngrok into the Webhook URL field. Add faxes to the end of the URL to direct the request to the webhook endpoint in your server.

For now you'll leave “Failover URL” blank, but if you'd like to have Telnyx resend the webhook in the case where sending to the Webhook URL fails, you can specify an alternate address in this field.

Once everything is setup, you should now be able to:

* Fax your phone number and receive an email
* Email `{19198675309}@domain.com` an attachment to send a fax to `{19198675309}`

## Fax follow-Ons

Now that you've successfully sent a fax to your email and phone number, you can explore more fax features and discover ideas to build new applications.

Our developer Slack community is full of Python developers like you - be sure to join to see what your fellow developers are building!

***

## API Reference (Other APIs)

### AutoRechargePreferences

* [List auto recharge preferences](https://developers.telnyx.com/api-reference/autorechargepreferences/list-auto-recharge-preferences.md): Returns the payment auto recharge preferences.
* [Update auto recharge preferences](https://developers.telnyx.com/api-reference/autorechargepreferences/update-auto-recharge-preferences.md): Update payment auto recharge preferences.

### x402 Payment Transactions

* [Create a payment quote](https://developers.telnyx.com/api-reference/x402-payment-transactions/create-a-payment-quote.md): Creates a payment quote for the specified USD amount. Returns payment details including the x402 payment requirements, network, and expiration time. The quote…
* [Settle a payment](https://developers.telnyx.com/api-reference/x402-payment-transactions/settle-a-payment.md): Settles an x402 payment using the quote ID and a signed payment authorization. The payment signature can be provided via the `PAYMENT-SIGNATURE` header or the…

### Notifications

* [List notification channels](https://developers.telnyx.com/api-reference/notifications/list-notification-channels.md): List notification channels.
* [Create a notification channel](https://developers.telnyx.com/api-reference/notifications/create-a-notification-channel.md): Create a notification channel.
* [Delete a notification channel](https://developers.telnyx.com/api-reference/notifications/delete-a-notification-channel.md): Delete a notification channel.
* [Get a notification channel](https://developers.telnyx.com/api-reference/notifications/get-a-notification-channel.md): Get a notification channel.
* [Update a notification channel](https://developers.telnyx.com/api-reference/notifications/update-a-notification-channel.md): Update a notification channel.
* [List all Notifications Events Conditions](https://developers.telnyx.com/api-reference/notifications/list-all-notifications-events-conditions.md): Returns a list of your notifications events conditions.
* [List all Notifications Events](https://developers.telnyx.com/api-reference/notifications/list-all-notifications-events.md): Returns a list of your notifications events.
* [List all Notifications Profiles](https://developers.telnyx.com/api-reference/notifications/list-all-notifications-profiles.md): Returns a list of your notifications profiles.
* [Create a notification profile](https://developers.telnyx.com/api-reference/notifications/create-a-notification-profile.md): Create a notification profile.
* [Delete a notification profile](https://developers.telnyx.com/api-reference/notifications/delete-a-notification-profile.md): Delete a notification profile.
* [Get a notification profile](https://developers.telnyx.com/api-reference/notifications/get-a-notification-profile.md): Get a notification profile.
* [Update a notification profile](https://developers.telnyx.com/api-reference/notifications/update-a-notification-profile.md): Update a notification profile.
* [List notification settings](https://developers.telnyx.com/api-reference/notifications/list-notification-settings.md): List notification settings.
* [Add a Notification Setting](https://developers.telnyx.com/api-reference/notifications/add-a-notification-setting.md): Add a notification setting.
* [Delete a notification setting](https://developers.telnyx.com/api-reference/notifications/delete-a-notification-setting.md): Delete a notification setting.
* [Get a notification setting](https://developers.telnyx.com/api-reference/notifications/get-a-notification-setting.md): Get a notification setting.

### Addresses

* [List all addresses](https://developers.telnyx.com/api-reference/addresses/list-all-addresses.md): Returns a list of your addresses.
* [Creates an address](https://developers.telnyx.com/api-reference/addresses/creates-an-address.md): Creates an address.
* [Deletes an address](https://developers.telnyx.com/api-reference/addresses/deletes-an-address.md): Deletes an existing address.
* [Retrieve an address](https://developers.telnyx.com/api-reference/addresses/retrieve-an-address.md): Retrieves the details of an existing address.
* [Accepts this address suggestion as a new emergency address for Operator Connect and finishes the uploads of the numbers associated with it to Microsoft.](https://developers.telnyx.com/api-reference/addresses/accepts-this-address-suggestion-as-a-new-emergency-address-for-operator-connect-and-finishes-the-uploads-of-the-numbers-associated-with-it-to-microsoft.md): Accept the validated address suggestion for this address, replacing the original values, and finish uploading the numbers associated with it to Microsoft for O…
* [Validate an address](https://developers.telnyx.com/api-reference/addresses/validate-an-address.md): Validates an address for emergency services.

### Billing

* [Get user balance details](https://developers.telnyx.com/api-reference/billing/get-user-balance-details.md): Retrieve the current balance, available credit, and credit limit for your account.

### Charges

* [Get monthly charges summary](https://developers.telnyx.com/api-reference/get-monthly-charges-summary.md): Retrieve a summary of monthly charges for a specified date range. The date range cannot exceed 31 days.
* [Get monthly charges breakdown](https://developers.telnyx.com/api-reference/get-monthly-charges-breakdown.md): Retrieve a detailed breakdown of monthly charges for phone numbers in a specified date range. The date range cannot exceed 31 days.

### Stored Payment Transactions

* [Create a stored payment transaction](https://developers.telnyx.com/api-reference/stored-payment-transactions/create-a-stored-payment-transaction.md): Create a transaction that charges a stored payment method on the account.

### Pricing

* [List products](https://developers.telnyx.com/api-reference/pricing/list-products.md): Returns the full product catalog with pagination. Each entry contains a slug, display name, and description. Use the slug to fetch per-product pricing via GET…
* [Get product pricing](https://developers.telnyx.com/api-reference/pricing/get-product-pricing.md): Returns pricing entries for a single product. Most products return standard rate entries with fields like rate, unit, country\_iso, direction, and tiers. Infere…

### Authentication Providers

* [List all SSO authentication providers](https://developers.telnyx.com/api-reference/authentication-providers/list-all-sso-authentication-providers.md): Returns a list of your SSO authentication providers.
* [Creates an authentication provider](https://developers.telnyx.com/api-reference/authentication-providers/creates-an-authentication-provider.md): Creates an authentication provider.
* [Deletes an authentication provider](https://developers.telnyx.com/api-reference/authentication-providers/deletes-an-authentication-provider.md): Deletes an existing authentication provider.
* [Retrieve an authentication provider](https://developers.telnyx.com/api-reference/authentication-providers/retrieve-an-authentication-provider.md): Retrieves the details of an existing authentication provider.
* [Update an authentication provider](https://developers.telnyx.com/api-reference/authentication-providers/update-an-authentication-provider.md): Updates settings of an existing authentication provider.

### IP Addresses

* [List all Access IP Addresses](https://developers.telnyx.com/api-reference/ip-addresses/list-all-access-ip-addresses.md): Retrieve a paginated list of access IP addresses configured on your account.
* [Create new Access IP Address](https://developers.telnyx.com/api-reference/ip-addresses/create-new-access-ip-address.md): Create a new access IP address entry on your account.
* [Delete access IP address](https://developers.telnyx.com/api-reference/ip-addresses/delete-access-ip-address.md): Delete an access IP address entry from your account.
* [Retrieve an access IP address](https://developers.telnyx.com/api-reference/ip-addresses/retrieve-an-access-ip-address.md): Retrieve the details of a specific access IP address.

### IP Ranges

* [List all Access IP Ranges](https://developers.telnyx.com/api-reference/ip-ranges/list-all-access-ip-ranges.md): Retrieve a paginated list of access IP ranges configured on your account.
* [Create new Access IP Range](https://developers.telnyx.com/api-reference/ip-ranges/create-new-access-ip-range.md): Create a new access IP range on your account.
* [Delete access IP ranges](https://developers.telnyx.com/api-reference/ip-ranges/delete-access-ip-ranges.md): Delete an access IP range from your account.

### Managed Accounts

* [Lists accounts managed by the current user.](https://developers.telnyx.com/api-reference/managed-accounts/lists-accounts-managed-by-the-current-user.md): Lists the accounts managed by the current user. Users need to be explictly approved by Telnyx in order to become manager accounts.
* [Create a new managed account.](https://developers.telnyx.com/api-reference/managed-accounts/create-a-new-managed-account.md): Create a new managed account owned by the authenticated user. You need to be explictly approved by Telnyx in order to become a manager account.
* [Retrieve a managed account](https://developers.telnyx.com/api-reference/managed-accounts/retrieve-a-managed-account.md): Retrieves the details of a single managed account.
* [Update a managed account](https://developers.telnyx.com/api-reference/managed-accounts/update-a-managed-account.md): Update a single managed account.
* [Disables a managed account](https://developers.telnyx.com/api-reference/managed-accounts/disables-a-managed-account.md): Disables a managed account, forbidding it to use Telnyx services, including sending or receiving phone calls and SMS messages. Ongoing phone calls will not be…
* [Enables a managed account](https://developers.telnyx.com/api-reference/managed-accounts/enables-a-managed-account.md): Enables a managed account and its sub-users to use Telnyx services.
* [Update the amount of allocatable global outbound channels allocated to a specific managed account.](https://developers.telnyx.com/api-reference/managed-accounts/update-the-amount-of-allocatable-global-outbound-channels-allocated-to-a-specific-managed-account.md): Update the amount of allocatable global outbound channels allocated to a specific managed account.
* [Display information about allocatable global outbound channels for the current user.](https://developers.telnyx.com/api-reference/managed-accounts/display-information-about-allocatable-global-outbound-channels-for-the-current-user.md): Display information about allocatable global outbound channels for the current user. Only usable by account managers.

### Organization Users

* [List organization users](https://developers.telnyx.com/api-reference/organization-users/list-organization-users.md): Returns a list of the users in your organization.
* [Get organization user](https://developers.telnyx.com/api-reference/organization-users/get-organization-user.md): Returns a user in your organization.
* [Delete organization user](https://developers.telnyx.com/api-reference/organization-users/delete-organization-user.md): Deletes a user in your organization.
* [Get organization users groups report](https://developers.telnyx.com/api-reference/organization-users/get-organization-users-groups-report.md): Returns a report of all users in your organization with their group memberships. This endpoint returns all users without pagination and always includes group i…

### Invoices

* [List invoices](https://developers.telnyx.com/api-reference/list-invoices.md): Retrieve a paginated list of invoices.
* [Get invoice by ID](https://developers.telnyx.com/api-reference/get-invoice-by-id.md): Retrieve a single invoice by its unique identifier.

### Billing Groups

* [List all billing groups](https://developers.telnyx.com/api-reference/billing-groups/list-all-billing-groups.md): Retrieve a paginated list of billing groups on your account.
* [Create a billing group](https://developers.telnyx.com/api-reference/billing-groups/create-a-billing-group.md): Create a new billing group, which can be used to organize resources for billing purposes.
* [Delete a billing group](https://developers.telnyx.com/api-reference/billing-groups/delete-a-billing-group.md): Delete a billing group from your account.
* [Get a billing group](https://developers.telnyx.com/api-reference/billing-groups/get-a-billing-group.md): Retrieve the details of a specific billing group.
* [Update a billing group](https://developers.telnyx.com/api-reference/billing-groups/update-a-billing-group.md): Update the properties of an existing billing group.

### Detail Records

* [Search detail records](https://developers.telnyx.com/api-reference/detail-records/search-detail-records.md): Search for any detail record across the Telnyx Platform

### MDR Detailed Reports

* [Get all MDR detailed report requests](https://developers.telnyx.com/api-reference/mdr-detailed-reports/get-all-mdr-detailed-report-requests.md): Retrieves all MDR detailed report requests for the authenticated user
* [Create a new MDR detailed report request](https://developers.telnyx.com/api-reference/mdr-detailed-reports/create-a-new-mdr-detailed-report-request.md): Creates a new MDR detailed report request with the specified filters
* [Get a specific MDR detailed report request](https://developers.telnyx.com/api-reference/mdr-detailed-reports/get-a-specific-mdr-detailed-report-request.md): Retrieves a specific MDR detailed report request by ID
* [Delete a MDR detailed report request](https://developers.telnyx.com/api-reference/mdr-detailed-reports/delete-a-mdr-detailed-report-request.md): Deletes a specific MDR detailed report request by ID

### CDR Reports

* [Get all CDR report requests](https://developers.telnyx.com/api-reference/cdr-reports/get-all-cdr-report-requests.md): Retrieves all CDR report requests for the authenticated user
* [Create a new CDR report request](https://developers.telnyx.com/api-reference/cdr-reports/create-a-new-cdr-report-request.md): Creates a new CDR report request with the specified filters
* [Get a specific CDR report request](https://developers.telnyx.com/api-reference/cdr-reports/get-a-specific-cdr-report-request.md): Retrieves a specific CDR report request by ID
* [Delete a CDR report request](https://developers.telnyx.com/api-reference/cdr-reports/delete-a-cdr-report-request.md): Deletes a specific CDR report request by ID
* [Get available CDR report fields](https://developers.telnyx.com/api-reference/cdr-reports/get-available-cdr-report-fields.md): Retrieves all available fields that can be used in CDR reports

### MDR Usage Reports

* [List MDR usage reports](https://developers.telnyx.com/api-reference/mdr-usage-reports/list-mdr-usage-reports.md): Fetch all previous requests for MDR usage reports.
* [Create a new legacy usage V2 MDR report request](https://developers.telnyx.com/api-reference/mdr-usage-reports/create-a-new-legacy-usage-v2-mdr-report-request.md): Creates a new legacy usage V2 MDR report request with the specified filters
* [Get an MDR usage report](https://developers.telnyx.com/api-reference/mdr-usage-reports/get-an-mdr-usage-report.md): Fetch single MDR usage report by id.
* [Delete a V2 legacy usage MDR report request](https://developers.telnyx.com/api-reference/mdr-usage-reports/delete-a-v2-legacy-usage-mdr-report-request.md): Deletes a specific V2 legacy usage MDR report request by ID
* [Fetch all Messaging usage reports](https://developers.telnyx.com/api-reference/mdr-usage-reports/fetch-all-messaging-usage-reports.md): Fetch all messaging usage reports. Usage reports are aggregated messaging data for specified time period and breakdown
* [Create MDR Usage Report](https://developers.telnyx.com/api-reference/mdr-usage-reports/create-mdr-usage-report.md): Submit request for new new messaging usage report. This endpoint will pull and aggregate messaging data in specified time period.
* [Retrieve messaging report](https://developers.telnyx.com/api-reference/mdr-usage-reports/retrieve-messaging-report.md): Fetch a single messaging usage report by id
* [Delete MDR Usage Report](https://developers.telnyx.com/api-reference/mdr-usage-reports/delete-mdr-usage-report.md): Delete messaging usage report by id
* [Generate and fetch MDR Usage Report](https://developers.telnyx.com/api-reference/mdr-usage-reports/generate-and-fetch-mdr-usage-report.md): Generate and fetch messaging usage report synchronously. This endpoint will both generate and fetch the messaging report over a specified time period. No polli…

### Telco Data Usage Reports

* [List telco data usage reports](https://developers.telnyx.com/api-reference/telco-data-usage-reports/list-telco-data-usage-reports.md): Retrieve a paginated list of telco data usage reports
* [Submit telco data usage report](https://developers.telnyx.com/api-reference/telco-data-usage-reports/submit-telco-data-usage-report.md): Submit a new telco data usage report
* [Get telco data usage report by ID](https://developers.telnyx.com/api-reference/telco-data-usage-reports/get-telco-data-usage-report-by-id.md): Retrieve a specific telco data usage report by its ID
* [Delete telco data usage report](https://developers.telnyx.com/api-reference/telco-data-usage-reports/delete-telco-data-usage-report.md): Delete a specific telco data usage report by its ID

### Speech to text Usage Reports

* [Get speech to text usage report](https://developers.telnyx.com/api-reference/speech-to-text-usage-reports/get-speech-to-text-usage-report.md): Generate and fetch speech to text usage report synchronously. This endpoint will both generate and fetch the speech to text report over a specified time period.

### CDR Usage Reports

* [List CDR usage reports](https://developers.telnyx.com/api-reference/cdr-usage-reports/list-cdr-usage-reports.md): Fetch all previous requests for cdr usage reports.
* [Create a new legacy usage V2 CDR report request](https://developers.telnyx.com/api-reference/cdr-usage-reports/create-a-new-legacy-usage-v2-cdr-report-request.md): Creates a new legacy usage V2 CDR report request with the specified filters
* [Get a CDR usage report](https://developers.telnyx.com/api-reference/cdr-usage-reports/get-a-cdr-usage-report.md): Fetch single cdr usage report by id.
* [Delete a V2 legacy usage CDR report request](https://developers.telnyx.com/api-reference/cdr-usage-reports/delete-a-v2-legacy-usage-cdr-report-request.md): Deletes a specific V2 legacy usage CDR report request by ID
* [Generates and fetches CDR Usage Reports](https://developers.telnyx.com/api-reference/cdr-usage-reports/generates-and-fetches-cdr-usage-reports.md): Generate and fetch voice usage report synchronously. This endpoint will both generate and fetch the voice report over a specified time period. No polling is ne…

### Speech to Text Batch Reports

* [Get all Speech to Text batch report requests](https://developers.telnyx.com/api-reference/speech-to-text-batch-reports/get-all-speech-to-text-batch-report-requests.md): Retrieves all Speech to Text batch report requests for the authenticated user
* [Create a new Speech to Text batch report request](https://developers.telnyx.com/api-reference/speech-to-text-batch-reports/create-a-new-speech-to-text-batch-report-request.md): Creates a new Speech to Text batch report request with the specified filters
* [Get a specific Speech to Text batch report request](https://developers.telnyx.com/api-reference/speech-to-text-batch-reports/get-a-specific-speech-to-text-batch-report-request.md): Retrieves a specific Speech to Text batch report request by ID
* [Delete a Speech to Text batch report request](https://developers.telnyx.com/api-reference/speech-to-text-batch-reports/delete-a-speech-to-text-batch-report-request.md): Deletes a specific Speech to Text batch report request by ID

### MDR Detail Reports

* [Fetch all Mdr records](https://developers.telnyx.com/api-reference/mdr-detail-reports/fetch-all-mdr-records.md): Fetch all Mdr records

### Usage Reports (BETA)

* [Get Telnyx product usage data (BETA)](https://developers.telnyx.com/api-reference/usage-reports-beta/get-telnyx-product-usage-data-beta.md): Get Telnyx usage data by product, broken out by the specified dimensions
* [Get Usage Reports query options (BETA)](https://developers.telnyx.com/api-reference/usage-reports-beta/get-usage-reports-query-options-beta.md): Get the Usage Reports options for querying usage, including the products available and their respective metrics and dimensions

### WDR Detail Reports

* [Fetches all Wdr records](https://developers.telnyx.com/api-reference/wdr-detail-reports/fetches-all-wdr-records.md): Fetch all Wdr records

### Session Analysis

* [Get metadata overview](https://developers.telnyx.com/api-reference/session-analysis/get-metadata-overview.md): Returns all available record types and supported query parameters for session analysis.
* [Get record type metadata](https://developers.telnyx.com/api-reference/session-analysis/get-record-type-metadata.md): Returns detailed metadata for a specific record type, including relationships and examples.
* [Get session analysis](https://developers.telnyx.com/api-reference/session-analysis/get-session-analysis.md): Retrieves a full session analysis tree for a given event, including costs, child events, and product linkages.

### Webhooks

* [List webhook deliveries](https://developers.telnyx.com/api-reference/webhooks/list-webhook-deliveries.md): Lists webhook\_deliveries for the authenticated user
* [Find webhook\_delivery details by ID](https://developers.telnyx.com/api-reference/webhooks/find-webhook_delivery-details-by-id.md): Provides webhook\_delivery debug data, such as timestamps, delivery status and attempts.

### Audit Logs

* [List Audit Logs](https://developers.telnyx.com/api-reference/audit-logs/list-audit-logs.md): Retrieve a list of audit log entries. Audit logs are a best-effort, eventually consistent record of significant account-related changes.

### User Tags

* [List User Tags](https://developers.telnyx.com/api-reference/user-tags/list-user-tags.md): List all user tags.

### UserAddresses

* [List all user addresses](https://developers.telnyx.com/api-reference/useraddresses/list-all-user-addresses.md): Returns a list of your user addresses.
* [Creates a user address](https://developers.telnyx.com/api-reference/useraddresses/creates-a-user-address.md): Creates a user address.
* [Retrieve a user address](https://developers.telnyx.com/api-reference/useraddresses/retrieve-a-user-address.md): Retrieves the details of an existing user address.

### Integration Secrets

* [List integration secrets](https://developers.telnyx.com/api-reference/integration-secrets/list-integration-secrets.md): Retrieve a list of all integration secrets configured by the user.
* [Create a secret](https://developers.telnyx.com/api-reference/integration-secrets/create-a-secret.md): Create a new secret with an associated identifier that can be used to securely integrate with other services.
* [Delete an integration secret](https://developers.telnyx.com/api-reference/integration-secrets/delete-an-integration-secret.md): Delete an integration secret given its ID.

### Room Compositions

* [View a list of room compositions.](https://developers.telnyx.com/api-reference/room-compositions/view-a-list-of-room-compositions.md): Returns a paginated list of room compositions. Filter compositions by creation date, room session, or processing status.
* [Create a room composition.](https://developers.telnyx.com/api-reference/room-compositions/create-a-room-composition.md): Asynchronously create a room composition.
* [View a room composition.](https://developers.telnyx.com/api-reference/room-compositions/view-a-room-composition.md): Returns the composition identified by `room_composition_id`, including its room and session, processing status, media details, video layout, lifecycle timestam…
* [Delete a room composition.](https://developers.telnyx.com/api-reference/room-compositions/delete-a-room-composition.md): Synchronously delete a room composition.

### Room Participants

* [View a list of room participants.](https://developers.telnyx.com/api-reference/room-participants/view-a-list-of-room-participants.md): Returns a paginated list of room participants across sessions. Filter participants by session, join, update, or leave date and by participant context.
* [View a room participant.](https://developers.telnyx.com/api-reference/room-participants/view-a-room-participant.md): Returns the participant identified by `room_participant_id`, including its session, context, and join, update, and leave timestamps.

### Room Recordings

* [View a list of room recordings.](https://developers.telnyx.com/api-reference/room-recordings/view-a-list-of-room-recordings.md): Returns a paginated list of room recordings. Filter recordings by room, session, participant, recording type, status, duration, or start and end dates.
* [Delete several room recordings in a bulk.](https://developers.telnyx.com/api-reference/room-recordings/delete-several-room-recordings-in-a-bulk.md): Deletes the room recordings that match the supplied filters and returns the number of recordings affected. Filters support room, session, participant, recordin…
* [View a room recording.](https://developers.telnyx.com/api-reference/room-recordings/view-a-room-recording.md): Returns the recording identified by `room_recording_id`, including its room, session, participant, status, media details, lifecycle timestamps, and download UR…
* [Delete a room recording.](https://developers.telnyx.com/api-reference/room-recordings/delete-a-room-recording.md): Synchronously delete a Room Recording.

### Room Sessions

* [View a list of room sessions.](https://developers.telnyx.com/api-reference/room-sessions/view-a-list-of-room-sessions.md): Returns a paginated list of room sessions across the account. Filter sessions by room, creation, update, or end date and active status, and use \`include\_partic…
* [View a room session.](https://developers.telnyx.com/api-reference/room-sessions/view-a-room-session.md): Returns the room session identified by `room_session_id`, including its room, active status, and lifecycle timestamps. Use `include_participants` to include it…
* [End a room session.](https://developers.telnyx.com/api-reference/room-sessions/end-a-room-session.md): Note: this will also kick all participants currently present in the room
* [Kick participants from a room session.](https://developers.telnyx.com/api-reference/room-sessions/kick-participants-from-a-room-session.md): Removes the selected participants from the specified room session. Apply the action to a list of participant IDs or to `all`, with optional participant IDs exc…
* [Mute participants in room session.](https://developers.telnyx.com/api-reference/room-sessions/mute-participants-in-room-session.md): Mutes the selected participants in the specified room session. Apply the action to a list of participant IDs or to `all`, with optional participant IDs exclude…
* [Unmute participants in room session.](https://developers.telnyx.com/api-reference/room-sessions/unmute-participants-in-room-session.md): Unmutes the selected participants in the specified room session. Apply the action to a list of participant IDs or to `all`, with optional participant IDs exclu…
* [View a list of room participants.](https://developers.telnyx.com/api-reference/room-sessions/view-a-list-of-room-participants.md): Returns a paginated list of participants for the specified room session. Filter participants by join, update, or leave date and by participant context.

### Rooms

* [View a list of rooms.](https://developers.telnyx.com/api-reference/rooms/view-a-list-of-rooms.md): Returns a paginated list of rooms. Filter the results by creation or update date and unique name, and use `include_sessions` to include each room’s sessions.
* [Create a room.](https://developers.telnyx.com/api-reference/rooms/create-a-room.md): Synchronously create a Room.
* [View a room.](https://developers.telnyx.com/api-reference/rooms/view-a-room.md): Returns the room identified by `room_id`, including its participant limit, recording and webhook configuration, and active session identifier. Use \`include\_ses…
* [Update a room.](https://developers.telnyx.com/api-reference/rooms/update-a-room.md): Synchronously update a Room.
* [Delete a room.](https://developers.telnyx.com/api-reference/rooms/delete-a-room.md): Synchronously delete a Room. Participants from that room will be kicked out, they won't be able to join that room anymore, and you won't be charged anymore for…
* [View a list of room sessions.](https://developers.telnyx.com/api-reference/rooms/view-a-list-of-room-sessions.md): Returns a paginated list of sessions for the specified room. Filter sessions by creation, update, or end date and active status, and use `include_participants`…

### Rooms Client Tokens

* [Create Client Token to join a room.](https://developers.telnyx.com/api-reference/rooms-client-tokens/create-client-token-to-join-a-room.md): Synchronously create an Client Token to join a Room. Client Token is necessary to join a Telnyx Room. Client Token will expire after `token_ttl_secs`, a Refres…
* [Refresh Client Token to join a room.](https://developers.telnyx.com/api-reference/rooms-client-tokens/refresh-client-token-to-join-a-room.md): Synchronously refresh an Client Token to join a Room. Client Token is necessary to join a Telnyx Room. Client Token will expire after `token_ttl_secs`.

### SETI Observability

* [Retrieve Black Box Test Results](https://developers.telnyx.com/api-reference/seti-observability/retrieve-black-box-test-results.md): Returns the results of the various black box tests

### Programmable Fax Applications

* [List all Fax Applications](https://developers.telnyx.com/api-reference/programmable-fax-applications/list-all-fax-applications.md): This endpoint returns a list of your Fax Applications inside the 'data' attribute of the response. You can adjust which applications are listed by using filter…
* [Creates a Fax Application](https://developers.telnyx.com/api-reference/programmable-fax-applications/creates-a-fax-application.md): Creates a new Fax Application based on the parameters sent in the request. The application name and webhook URL are required. Once created, you can assign phon…
* [Deletes a Fax Application](https://developers.telnyx.com/api-reference/programmable-fax-applications/deletes-a-fax-application.md): Permanently deletes a Fax Application. Deletion may be prevented if the application is in use by phone numbers.
* [Retrieve a Fax Application](https://developers.telnyx.com/api-reference/programmable-fax-applications/retrieve-a-fax-application.md): Return the details of an existing Fax Application inside the 'data' attribute of the response.
* [Update a Fax Application](https://developers.telnyx.com/api-reference/programmable-fax-applications/update-a-fax-application.md): Updates settings of an existing Fax Application based on the parameters of the request.

### Programmable Fax Commands

* [View a list of faxes](https://developers.telnyx.com/api-reference/programmable-fax-commands/view-a-list-of-faxes.md): Retrieve a paginated list of faxes sent or received on your account.
* [Send a fax](https://developers.telnyx.com/api-reference/programmable-fax-commands/send-a-fax.md): Send a fax. Files have size limits and page count limit validations. If a file is bigger than 50MB or has more than 350 pages it will fail with \`file\_size\_limi…
* [Delete a fax](https://developers.telnyx.com/api-reference/programmable-fax-commands/delete-a-fax.md): Delete a fax resource from your account.
* [View a fax](https://developers.telnyx.com/api-reference/programmable-fax-commands/view-a-fax.md): Retrieve the details of a single fax.
* [Cancel a fax](https://developers.telnyx.com/api-reference/programmable-fax-commands/cancel-a-fax.md): Cancel the outbound fax that is in one of the following states: `queued`, `media.processed`, `originated` or `sending`
* [Refresh a fax](https://developers.telnyx.com/api-reference/programmable-fax-commands/refresh-a-fax.md): Refreshes the inbound fax's media\_url when it has expired

### Callbacks

* [Fax Delivered](https://developers.telnyx.com/api-reference/callbacks/fax-delivered.md)
* [Fax Failed](https://developers.telnyx.com/api-reference/callbacks/fax-failed.md)
* [Fax Media Processed](https://developers.telnyx.com/api-reference/callbacks/fax-media-processed.md)
* [Fax Queued](https://developers.telnyx.com/api-reference/callbacks/fax-queued.md)
* [Fax Sending Started](https://developers.telnyx.com/api-reference/callbacks/fax-sending-started.md)

### OAuth Discovery

* [Authorization server metadata](https://developers.telnyx.com/api-reference/oauth-discovery/authorization-server-metadata.md): OAuth 2.0 Authorization Server Metadata (RFC 8414)
* [Protected resource metadata](https://developers.telnyx.com/api-reference/oauth-discovery/protected-resource-metadata.md): OAuth 2.0 Protected Resource Metadata for resource discovery

### OAuth Protocol

* [OAuth authorization endpoint](https://developers.telnyx.com/api-reference/oauth-protocol/oauth-authorization-endpoint.md): OAuth 2.0 authorization endpoint for the authorization code flow
* [Get OAuth consent token](https://developers.telnyx.com/api-reference/oauth-protocol/get-oauth-consent-token.md): Retrieve details about an OAuth consent token
* [Token introspection](https://developers.telnyx.com/api-reference/oauth-protocol/token-introspection.md): Introspect an OAuth access token to check its validity and metadata
* [JSON Web Key Set](https://developers.telnyx.com/api-reference/oauth-protocol/json-web-key-set.md): Retrieve the JSON Web Key Set for token verification
* [Dynamic client registration](https://developers.telnyx.com/api-reference/oauth-protocol/dynamic-client-registration.md): Register a new OAuth client dynamically (RFC 7591)
* [OAuth token endpoint](https://developers.telnyx.com/api-reference/oauth-protocol/oauth-token-endpoint.md): Exchange authorization code, client credentials, or refresh token for access token

### OAuth Clients

* [List OAuth clients](https://developers.telnyx.com/api-reference/oauth-clients/list-oauth-clients.md): Retrieve a paginated list of OAuth clients for the authenticated user
* [Create OAuth client](https://developers.telnyx.com/api-reference/oauth-clients/create-oauth-client.md): Create a new OAuth client
* [Delete OAuth client](https://developers.telnyx.com/api-reference/oauth-clients/delete-oauth-client.md): Delete an OAuth client
* [Get OAuth client](https://developers.telnyx.com/api-reference/oauth-clients/get-oauth-client.md): Retrieve a single OAuth client by ID
* [Update OAuth client](https://developers.telnyx.com/api-reference/oauth-clients/update-oauth-client.md): Update an existing OAuth client

### OAuth Grants

* [List OAuth grants](https://developers.telnyx.com/api-reference/oauth-grants/list-oauth-grants.md): Retrieve a paginated list of OAuth grants for the authenticated user
* [Revoke OAuth grant](https://developers.telnyx.com/api-reference/oauth-grants/revoke-oauth-grant.md): Revoke an OAuth grant
* [Get OAuth grant](https://developers.telnyx.com/api-reference/oauth-grants/get-oauth-grant.md): Retrieve a single OAuth grant by ID
