---
notionId: "670ef73b7a304f908bdcd9cc4127b8d8"
product: "developer"
cluster: "start"
intent: "developer.api-basics"
docSlug: "api-basics"
task: "API Basics in the API"
title: "API Basics in the API | VirtualPBX"
meta_description: "The Dash API is a REST interface that external applications call with ordinary HTTP requests. A request names a resource and an action, and the URI splits into"
answer: "The Dash API is a REST interface that external applications call with ordinary HTTP requests. A request names a resource and an action, and the URI splits into the endpoint plus the resource id so Dash can route it. The same API covers the verbs those requests use, including PATCH and the tunneled form of a verb when a client cannot send it directly. Start with this page before calling a specific resource."
audience: "user"
updated: "2026-09-29"
legacy: ["api-basics"]
headings: [{"depth":2,"text":"Introduction","id":"introduction"},{"depth":3,"text":"Basic URI Structure","id":"basic-uri-structure"},{"depth":3,"text":"Resources","id":"resources"},{"depth":3,"text":"HTTP Verbs","id":"http-verbs"},{"depth":3,"text":"PATCH","id":"patch"},{"depth":3,"text":"Tunneling the HTTP Verb","id":"tunneling-the-http-verb"},{"depth":3,"text":"Tunneling the Accept Header","id":"tunneling-the-accept-header"},{"depth":3,"text":"Authentication Tokens","id":"authentication-tokens"},{"depth":3,"text":"Request Envelope","id":"request-envelope"},{"depth":3,"text":"Response Envelope","id":"response-envelope"},{"depth":3,"text":"Pagination","id":"pagination"},{"depth":3,"text":"API Pagination","id":"api-pagination"},{"depth":3,"text":"Requesting a page","id":"requesting-a-page"},{"depth":3,"text":"Disabling Pagination","id":"disabling-pagination"},{"depth":3,"text":"Requesting a range of binary data","id":"requesting-a-range-of-binary-data"},{"depth":2,"text":"Query String Filters","id":"query-string-filters"},{"depth":3,"text":"How filters limit results","id":"how-filters-limit-results"},{"depth":3,"text":"Available Filters","id":"available-filters"},{"depth":3,"text":"Keys","id":"keys"},{"depth":3,"text":"Multiple Filters","id":"multiple-filters"}]
lint: []
---

## Introduction

VirtualPBX API gives you the tools to develop high-quality unified telecom applications. Our REST API interface provides a simple way for external applications to talk to VirtualPBX by making HTTP requests.

### Basic URI Structure

Plain Text

<pre tabindex="0"><code>/{VERSION}/accounts/{ACCOUNT_ID}/resources/{RESOURCE_ID}</code></pre>

<ul>
<li><p><code>{VERSION}</code> - The version of the API you are calling.</p>
</li>
<li><p><code>{ACCOUNT_ID}</code> - Most requests operate against a specific account and thus require the account_id to route the resquest properly</p>
</li>
<li><p><code>{RESOURCE_ID}</code> - When accessing a specific resource, like a device, user, or callflow, this is the <code>{RESOURCE_ID}</code>points to the specific instance you're accessing.</p>
</li>
</ul>

### Resources

There are two parts to how a request is routed: the REST endpoint and the resource ID. Let's break down a common URI and see how Dash figures out what is an endpoint and what is a resource ID.

Given a uri of `/v2/accounts/{ACCOUNT_ID}/devices/{DEVICE_ID}`:

<ol>
<li><p>First, strip the version off the URI</p>
</li>
<li><p>See if the next token is a REST endpoint module. It is, so track the module for later routing:</p>
</li>
<li><p>See if the next token is a REST endpoint module. It is not, so add the token to the last module's data:</p>
</li>
<li><p>Repeat parsing. devices is a REST endpoint:</p>
</li>
<li><p>Repeat parsing. {DEVICE_ID} is an argument:</p>
</li>
</ol>

So we have a request to account \{account\_id\} to do something with a device \{device\_id\}.

### HTTP Verbs

The HTTP verb will determine the class of actions to take against the resource. Generically speaking, the verbs map thusly:

<ul>
<li><p><code>/v2/accounts/{ACCOUNT_ID}/resources</code></p>
</li>
<li><p><code>/v2/accounts/{ACCOUNT_ID}/resources/{RESOURCE_ID}</code></p>
</li>
</ul>

### PATCH

Some resources support the PATCH verb, allowing partial updates instead of requiring the request to include the full version of the document. `/users/{USER_ID}`, for instance, supports PATCH:

Plain Text

<pre tabindex="0"><code>curl -v -X PATCH -H &quot;Content-Type: application/json&quot; -H &quot;X-Auth-Token: {AUTH_TOKEN}&quot; 'https://api.virtualpbx.net/v2/accounts/{ACCOUNT_ID}/webhooks/{WEBHOOKS_ID}' -d '{&quot;data&quot;:{&quot;enabled&quot;:true}}'</code></pre>

This cURL request will patch the user's doc and set `vm_to_email_enabled` to `true`. All normal validation will occur after patching the document.

If a resource does not support PATCH, clients can expect to receive a `405 Method Not Allowed` error.

### Tunneling the HTTP Verb

Some clients do not support the full range of HTTP verbs, and are typically limited to GET and POST. To access the functionalities of PUT and DELETE, you can tunnel the verb in a POST in a couple of ways:

<ol>
<li><p>As part of the request envelope: <code>{&quot;data&quot;:{...}, &quot;verb&quot;:&quot;PUT&quot;}</code></p>
</li>
<li><p>As a query string parameter: <code>/v2/accounts/{ACCOUNT_ID}/resources?verb=PUT</code></p>
</li>
</ol>

### Tunneling the Accept Header

Some clients do not support the ability to set the [Accept header](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html) in the request, meaning they will not necessarily receive the response in the format they wish. Clients can append `accept=text/html` to the request body or query string to indicate they'd like the response processed as if the Accept header was `text/html`.

<aside class="note"><p><code>accept=csv</code> is retained for backwards-compatibility but it is encouraged to use a proper media type going forward.</p></aside>

### Authentication Tokens

Most APIs require the client to have authenticated and received a token usable on subsequent requests.

<ol>
<li><p>User Authentication: This is the preferred way of authenticating a user and useful for making an UI</p>
</li>
</ol>

### Request Envelope

When issuing a PUT or POST, a request body is needed. When submitting a JSON (the most common body), Dash expects a request envelope with a few bits of metadata:

<ul>
<li><p>data: this top-level key will contain the object you wish to create/update</p>
</li>
<li><p>auth_token: optionally put your auth token in the envelope</p>
</li>
<li><p>verb: optionally tunnel a PUT or DELETE in a POST request</p>
</li>
</ul>

Sample Request Envelope:

JSON

<pre tabindex="0"><code>{
    &quot;data&quot;: {
        &quot;foo&quot;: &quot;bar&quot;
    },
    &quot;auth_token&quot;: &quot;{AUTH_TOKEN}&quot;,
    &quot;verb&quot;: &quot;delete&quot;
}</code></pre>

### Response Envelope

When receiving JSON responses, clients will receive the response in an envelope. The response includes some duplicated data from the HTTP Response headers, since some clients do not have access to those headers.

<ul>
<li><p><code>data</code>: contains the results of the request, if any</p>
</li>
<li><p><code>auth_token</code>: contains the auth_token used on the request</p>
</li>
<li><p><code>status</code>: One of 'success', 'error', or 'fatal'</p>
</li>
<li><p><code>message</code>: Optional message that should clarify what happened on the request</p>
</li>
<li><p><code>error</code>: Error code, if any</p>
</li>
<li><p><code>request_id</code>: ID of the request; usuable for debugging the server-side processing of the request</p>
</li>
</ul>

Sample Response Envelope:

JSON

<pre tabindex="0"><code>{
    &quot;data&quot;: {
        &quot;the&quot;: &quot;response&quot;,
        &quot;data&quot;: &quot;is here&quot;
    },
    &quot;auth_token&quot;: &quot;{AUTH_TOKEN}&quot;,
    &quot;status&quot;: &quot;success&quot;,
    &quot;request_id&quot;: &quot;{REQUEST_ID}&quot;
}</code></pre>

### Pagination

All listing APIs in v2 will be paginated by default.

Let's take a look at the recordings API to see how to interpret pagination.

### API Pagination

We start with the typical recordings request for a listing of recordings:

Shell

<pre tabindex="0"><code>curl -v \
    -H &quot;X-Auth-Token: {AUTH_TOKEN}&quot; \
    -H &quot;Content-Type: application/json&quot; \
    https://api.virtualpbx.net/v2/accounts/{ACCOUNT_ID}/recordings</code></pre>

JSON

<pre tabindex="0"><code>{
    &quot;auth_token&quot;: &quot;{AUTH_TOKEN}&quot;,
    &quot;data&quot;: [
        {RECORDING_OBJECT},
        {RECORDING_OBJECT},
        ...
    ],
    &quot;next_start_key&quot;: 63566193143,
    &quot;page_size&quot;: 25,
    &quot;request_id&quot;: &quot;{REQUEST_ID}&quot;,
    &quot;revision&quot;: &quot;{REVISION}&quot;,
    &quot;start_key&quot;: 63565345339,
    &quot;status&quot;: &quot;success&quot;
}</code></pre>

The pagination response keys are `next_start_key`, `page_size`, and `start_key`.

<ul>
<li><p><code>next_start_key</code>: used to get the next page of results from this API. Will not exist if this is the last page.</p>
</li>
<li><p><code>start_key</code>: used to get back to this page of results (or start pagination from this point)</p>
</li>
<li><p><code>page_size</code>: the number of results returned in this page</p>
</li>
</ul>

Assuming no changes are made to the underlying documents, `start_key` will get you this page of results, and `next_start_key` will give you a pointer to the next page (imagine a linked-list).

### Requesting a page

Using the `next_start_key` value, let's request the next page of recordings:

Shell

<pre tabindex="0"><code>curl -v \
    -H &quot;X-Auth-Token: {AUTH_TOKEN}&quot; \
    -H &quot;Content-Type: application/json&quot; \
    https://api.virtualpbx.net/v2/accounts/{ACCOUNT_ID}/recordings?start_key=63566193143</code></pre>

JSON

<pre tabindex="0"><code>{
    &quot;auth_token&quot;: &quot;{AUTH_TOKEN}&quot;
    &quot;data&quot;: [
        {RECORDING_OBJECT},
        {RECORDING_OBJECT},
        ...
    ],
    &quot;next_start_key&quot;: 63566542092,
    &quot;page_size&quot;: 25,
    &quot;request_id&quot;: &quot;{REQUEST_ID}&quot;,
    &quot;revision&quot;: &quot;{REVISION}&quot;,
    &quot;start_key&quot;: 63566193143,
    &quot;status&quot;: &quot;success&quot;
}</code></pre>

Observe now that `start_key` is the requested `start_key` and `next_start_key` points to the start of the next page of results.

If `next_start_key` is missing from the response envelope, the response represents the last page of results.

You can also choose to receive pages in bigger or smaller increments by specifying `page_size` on the request. Do take care, as the `next_start_key` will probably vary if you use the same `start_key` but differing `page_size` values.

### Disabling Pagination

If you want to disable pagination for a request, simply include `paginate=false` on the query string.

### Requesting a range of binary data

It is useful to be able to get just a section of a file when streaming or resuming a download. This can be accomplished with the range header, eg:

Shell

<pre tabindex="0"><code>curl -v \
    -H &quot;X-Auth-Token: {AUTH_TOKEN}&quot; \
    -H &quot;Content-Type: application/json&quot; \
    -H &quot;Accept: audio/mpeg&quot; \
    -H &quot;Range: bytes={START_BYTE}-{END_BYTE}&quot; \
    https://api.virtualpbx.net/v2/accounts/{ACCOUNT_ID}/vmboxes/{VMBOX_ID}/messages/{MESSAGE_ID}/raw</code></pre>

## Query String Filters

### How filters limit results

Query string filters allow the API results to be filtered by additional criteria to limit the result set. This is especially useful when querying a collection that could be massive (like recordings) but you're only interested in results that match certain criteria.

### Available Filters

<div class="table-scroll" tabindex="0"><table><tr><th>Filter</th><th>Operates On</th><th>Description</th></tr><tr><td><code>filter_not_{KEY}</code></td><td><code>{VALUE}</code></td><td>Doc include if <code>{KEY}</code> is not <code>{VALUE}</code></td></tr><tr><td><code>filter_{KEY}</code></td><td><code>{VALUE}</code></td><td>Doc included if <code>{KEY}</code> is <code>{VALUE}</code></td></tr><tr><td><code>has_key</code></td><td><code>{KEY}</code></td><td>Doc included if <code>{KEY}</code> is present on the doc</td></tr><tr><td><code>key_missing</code></td><td><code>{KEY}</code></td><td>Doc included if <code>{KEY}</code> is not present on the doc</td></tr><tr><td><code>has_value</code></td><td><code>{KEY}</code></td><td>Doc included if <code>{KEY}</code> exists and the <code>{VALUE}</code> is non-empty</td></tr><tr><td><code>missing_value</code></td><td><code>{KEY}</code></td><td>Doc included if <code>{KEY}</code> is not present or the <code>{VALUE}</code> is empty</td></tr><tr><td><code>created_from</code></td><td><code>{VALUE}</code></td><td>Doc included if the created time is greater than or equal to <code>{VALUE}</code> (in Gregorian seconds)</td></tr><tr><td><code>created_to</code></td><td><code>{VALUE}</code></td><td>Doc included if the created time is less than or equal to <code>{VALUE}</code> (in Gregorian seconds)</td></tr><tr><td><code>modified_from</code></td><td><code>{VALUE}</code></td><td>Doc included if the last-modified time is greater than or equal to <code>{VALUE}</code> (in Gregorian seconds)</td></tr><tr><td><code>modified_to</code></td><td><code>{VALUE}</code></td><td>Doc included if the last-modified time is less than or equal to <code>{VALUE}</code> (in Gregorian seconds)</td></tr></table></div>

### Keys

Filters can be used on validated keys (those appearing in the schema) and on custom keys (those included by the caller).

`{KEY}` can be a dot-delimited string representing a JSON key path. So `filter_foo.bar.baz=1` would match a doc that had `{"foo":{"bar":{"baz":1}}}` in it.

### Multiple Filters

Filters can be chained together on a query string and will be applied as a boolean `AND` operation. For example, `?filter_foo=1&has_key=bar` will look for docs where `foo=1` and the key `bar` exists on the doc.
