---
title: Tunnel API | TestingBot API Documentation
description: List, inspect, start and stop TestingBot Tunnel instances so you can
  test websites behind your firewall.
source_url:
  html: https://testingbot.com/support/api/tunnel
  md: https://testingbot.com/support/api/tunnel.md
---

# Tunnel

Manage the secure tunnels that let the grid reach your staging and internal environments.

- **Endpoint:** api.testingbot.com
- **Version:** v1
- **Format:** JSON
- **Auth:** [HTTP Basic](https://testingbot.com/support/api#authentication)

GET `/v1/tunnel/list`

## List active tunnels
 Returns every TestingBot Tunnel currently running under the account. Use this to monitor running tunnels in CI dashboards or to find a tunnel ID before tearing it down. 
### Response fields

- **`id` integer:** Unique numeric tunnel ID.
- **`identifier` string:** The `--tunnel-identifier` the tunnel was started with, or null when it was started without one. Use this to tell concurrent tunnels on the same account apart.
- **`state` string:** Tunnel lifecycle state (READY, STOPPED, …).
- **`private_ip` string:** Internal IP of the tunnel VM.
- **`ip` string:** Public IP of the tunnel VM.
- **`requested_at` timestamp:** When the tunnel was requested.
- **`user_id` integer:** ID of the user the tunnel belongs to.

GET `/v1/tunnel/list`
[cURL](https://testingbot.com#) [.NET](https://testingbot.com#) [Ruby](https://testingbot.com#) [Python](https://testingbot.com#) [PHP](https://testingbot.com#) [Java](https://testingbot.com#) [NodeJS](https://testingbot.com#)
Request

```bash
$ curl "https://api.testingbot.com/v1/tunnel/list" \
-u key:secret
```

```csharp
var client = new TestingBotClient(key, secret);
var tunnels = await client.Tunnels.ListAsync();
```

```ruby
require 'testingbot'
api = TestingBot::Api.new(key, secret)
api.get_tunnels
```

```python
import testingbotclient
tb = testingbotclient.TestingBotClient(key, secret)
tb.tunnel.get_tunnels()
```

```php
$api = new TestingBot\TestingBotAPI($key, $secret);
$api->getTunnels();
```

```java
TestingbotREST restApi = new TestingbotREST(key, secret);
ArrayList<TestingbotTunnel> tunnels = restApi.getTunnels();
```

```javascript
const TestingBot = require('testingbot-api');

const api = new TestingBot({
  api_key: "your-tb-key",
  api_secret: "your-tb-secret"
});

const tunnels = await api.getTunnelList();
```

Response

```json
[
  {
    "id": 1,
    "identifier": "my-ci-tunnel",
    "state": "READY",
    "ip": "xx",
    "private_ip": "xx",
    "requested_at": "2026-05-13 01:34:23"
  }
]
```

DELETE `/v1/tunnel/{id}`

## Stop a tunnel
 Tears down a running tunnel. Use this in CI teardown steps to release the slot for the next run; idle tunnels also self-terminate after the configured timeout. 
### Arguments

- **`id` integer required:** Numeric tunnel ID to stop.

DELETE `/v1/tunnel/{id}`
[cURL](https://testingbot.com#) [.NET](https://testingbot.com#) [Ruby](https://testingbot.com#) [Python](https://testingbot.com#) [PHP](https://testingbot.com#) [Java](https://testingbot.com#) [NodeJS](https://testingbot.com#)
Request

```bash
$ curl "https://api.testingbot.com/v1/tunnel/{id}" \
-X DELETE \
-u key:secret
```

```csharp
var client = new TestingBotClient(key, secret);
await client.Tunnels.StopAsync(tunnelId);
```

```ruby
require 'testingbot'
api = TestingBot::Api.new(key, secret)
api.delete_tunnel(tunnel_id)
```

```python
import testingbotclient
tb = testingbotclient.TestingBotClient(key, secret)
tb.tunnel.delete_tunnel(tunnel_id)
```

```php
$api = new TestingBot\TestingBotAPI($key, $secret);
$api->deleteTunnel($tunnelID);
```

```java
TestingbotREST restApi = new TestingbotREST(key, secret);
boolean success = restApi.deleteTunnel(tunnelId);
```

```javascript
const TestingBot = require('testingbot-api');

const api = new TestingBot({
  api_key: "your-tb-key",
  api_secret: "your-tb-secret"
});

await api.deleteTunnel(tunnelId);
```

Response

```json
{
  "success": true
}
```

GET `/v1/tunnel`

## Get the user's active tunnel
 Returns the first active tunnel for the authenticated account (regardless of ID). Convenience endpoint when you only run one tunnel at a time. 
### Response fields

- **`id` integer:** Unique numeric tunnel ID.
- **`state` string:** Tunnel lifecycle state (READY, STOPPED, …).
- **`private_ip` string:** Internal IP of the tunnel VM, used to chain the tunnel with BrowserMob.
- **`ip` string:** Public IP of the tunnel VM. Only present once `state` is READY.
- **`version` string:** Latest tunnel client version TestingBot supports.

GET `/v1/tunnel`
[cURL](https://testingbot.com#) [.NET](https://testingbot.com#) [PHP](https://testingbot.com#) [Java](https://testingbot.com#) [NodeJS](https://testingbot.com#)
Request

```bash
$ curl "https://api.testingbot.com/v1/tunnel" \
-u key:secret
```

```csharp
var client = new TestingBotClient(key, secret);
var tunnel = await client.Tunnels.GetActiveAsync();
```

```php
$client = new TestingBot\Client($key, $secret);
$tunnel = $client->tunnels()->getActive();
```

```java
TestingbotREST restApi = new TestingbotREST(key, secret);
TestingbotTunnel tunnel = restApi.getTunnel();
```

```javascript
const TestingBot = require('testingbot-api');

const api = new TestingBot({
  api_key: "your-tb-key",
  api_secret: "your-tb-secret"
});

const tunnel = await api.getTunnel();
```

DELETE `/v1/tunnel`

## Stop the user's active tunnel
 Tears down whichever tunnel is currently active on the account, without needing to specify an ID. Useful when you only ever run one tunnel and want a simple cleanup call. 

DELETE `/v1/tunnel`
[cURL](https://testingbot.com#) [.NET](https://testingbot.com#) [PHP](https://testingbot.com#) [Java](https://testingbot.com#) [NodeJS](https://testingbot.com#)
Request

```bash
$ curl -X DELETE "https://api.testingbot.com/v1/tunnel" \
-u key:secret
```

```csharp
var client = new TestingBotClient(key, secret);
await client.Tunnels.StopActiveAsync();
```

```php
$client = new TestingBot\Client($key, $secret);
$client->tunnels()->deleteActive();
```

```java
TestingbotREST restApi = new TestingbotREST(key, secret);
boolean success = restApi.deleteTunnel();
```

```javascript
const TestingBot = require('testingbot-api');

const api = new TestingBot({
  api_key: "your-tb-key",
  api_secret: "your-tb-secret"
});

await api.deleteActiveTunnel();
```

POST `/v1/tunnel/create`

## Create a tunnel
 Launches a new TestingBot Tunnel VM and returns its connection metadata. Most users start tunnels via the tunnel client/CLI rather than this endpoint directly. 

POST `/v1/tunnel/create`
[cURL](https://testingbot.com#) [.NET](https://testingbot.com#) [PHP](https://testingbot.com#) [Java](https://testingbot.com#)
Request

```bash
$ curl -X POST "https://api.testingbot.com/v1/tunnel/create" \
-u key:secret
```

```csharp
var client = new TestingBotClient(key, secret);
var tunnel = await client.Tunnels.CreateAsync();
```

```php
$client = new TestingBot\Client($key, $secret);
$tunnel = $client->tunnels()->create();
```

```java
TestingbotREST restApi = new TestingbotREST(key, secret);
TestingbotTunnel tunnel = restApi.createTunnel();
```

GET `/v1/tunnel/isalive-check`

## Liveness check
 Unauthenticated ping endpoint used by the tunnel binary itself to confirm it can reach the TestingBot API. Always returns `{ success: true }`. 
### Response fields

- **`success` boolean:** Whether the operation succeeded.
- **`errors` object:** Validation errors keyed by field name. Only present when `success` is false.
- **`error` string:** Single human-readable reason, used by the older endpoints in place of `errors`. Only present when `success` is false.

GET `/v1/tunnel/isalive-check`
[cURL](https://testingbot.com#) [.NET](https://testingbot.com#) [Java](https://testingbot.com#)
Request

```bash
$ curl "https://api.testingbot.com/v1/tunnel/isalive-check" \
-u key:secret
```

```csharp
var client = new TestingBotClient(key, secret);
var alive = await client.Tunnels.IsAliveAsync();
```

```java
TestingbotREST restApi = new TestingbotREST(key, secret);
boolean alive = restApi.isTunnelAlive();
```

GET `/v1/tunnel/{id}`

## Get a specific tunnel
 Returns the current state of a tunnel by ID. Returns 500 with the underlying error if the tunnel is in a transition state or no longer exists. 
### Arguments

- **`id` integer required:** Numeric tunnel ID returned by /v1/tunnel/list or /v1/tunnel/create.

### Response fields

- **`id` integer:** Unique numeric tunnel ID.
- **`state` string:** Tunnel lifecycle state (READY, STOPPED, …).
- **`private_ip` string:** Internal IP of the tunnel VM, used to chain the tunnel with BrowserMob.
- **`ip` string:** Public IP of the tunnel VM. Only present once `state` is READY.
- **`version` string:** Latest tunnel client version TestingBot supports.

GET `/v1/tunnel/{id}`
[cURL](https://testingbot.com#) [.NET](https://testingbot.com#) [PHP](https://testingbot.com#) [Java](https://testingbot.com#) [NodeJS](https://testingbot.com#)
Request

```bash
$ curl "https://api.testingbot.com/v1/tunnel/{id}" \
-u key:secret
```

```csharp
var client = new TestingBotClient(key, secret);
var tunnel = await client.Tunnels.GetAsync(tunnelId);
```

```php
$client = new TestingBot\Client($key, $secret);
$tunnel = $client->tunnels()->get($tunnelId);
```

```java
TestingbotREST restApi = new TestingbotREST(key, secret);
TestingbotTunnel tunnel = restApi.getTunnel(tunnelId);
```

```javascript
const TestingBot = require('testingbot-api');

const api = new TestingBot({
  api_key: "your-tb-key",
  api_secret: "your-tb-secret"
});

const tunnel = await api.getTunnelById(tunnelId);
```

[Previous Screenshots](https://testingbot.com/support/api/screenshots) [Next Codeless Automation & TestLab](https://testingbot.com/support/api/codeless)
