Inkbox

> # Documentation index
> Fetch the complete documentation index at: https://inkbox.ai/sitemap.xml
> Use this file to discover all available pages before exploring further.

---

# Manage Tunnels
description: Create, list, get, update, delete, and restore tunnels

---


# Manage Tunnels

CRUD plus a 24-hour soft-delete grace window for tunnel resources. See the [Tunnels API overview](/docs/api/tunnels) for naming rules, lifecycle states, and TLS modes.

---

## Create tunnel `POST`


Create a new tunnel. The response includes a `connect_secret` shown **once** — store it securely.

- `tls_mode: "edge"` (default): the tunnel is `active` immediately and routable at `{tunnel_name}.inkboxwire.com`.
- `tls_mode: "passthrough"`: the tunnel starts in `awaiting_cert`. You must then submit a CSR via [`/sign-csr`](/docs/api/tunnels/passthrough) to transition to `active`.

Rate-limited to 10 successful creates per organization per day.

### Request body

| Field | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| `tunnel_name` | string | Yes | Subdomain label. 3–63 chars, lowercase a–z, 0–9, hyphens; must start and end with alphanumeric; no consecutive hyphens. Globally unique within the environment. |
| `description` | string \| null | No | Free-form description (max 1000 chars). For your own reference. |
| `tls_mode` | string | No | `"edge"` (default) or `"passthrough"`. Immutable after creation. |

### Request example

```json
{
    "tunnel_name": "my-agent",
    "description": "Production webhook receiver",
    "tls_mode": "edge"
}
```

### Response (201)

```json
{
    "tunnel": {
      "id": "t1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "organization_id": "org_123",
      "tunnel_name": "my-agent",
      "description": "Production webhook receiver",
      "tls_mode": "edge",
      "cert_pem": null,
      "cert_fingerprint_sha256": null,
      "cert_expires_at": null,
      "status": "active",
      "last_connected_at": null,
      "last_connected_ip_addr": null,
      "restore_deadline_at": null,
      "currently_connected": false,
      "created_at": "2026-04-21T12:30:00Z",
      "updated_at": "2026-04-21T12:30:00Z"
    },
    "connect_secret": "tnl_8f3a92b1c4d5e6f7a8b9c0d1e2f3a4b5"
}
```

### Error responses

| Status | Description |
| :--- | :--- |
| 409 | `tunnel_name` is already taken or reserved |
| 422 | `tunnel_name` violates naming rules, or `description` exceeds 1000 chars |
| 429 | Daily create rate limit exceeded |

### Code examples

**cURL**

```bash
curl -X POST "https://inkbox.ai/api/v1/tunnels" \\
    -H "X-API-Key: YOUR_API_KEY" \\
    -H "Content-Type: application/json" \\
    -d '{"tunnel_name": "my-agent", "description": "Production webhook receiver"}'
```

**JavaScript**

```javascript
const response = await fetch("https://inkbox.ai/api/v1/tunnels", {
    method: "POST",
    headers: {
        "X-API-Key": "YOUR_API_KEY",
        "Content-Type": "application/json",
    },
    body: JSON.stringify({
        tunnel_name: "my-agent",
        description: "Production webhook receiver",
    }),
});
const { tunnel, connect_secret } = await response.json();
```

**Python**

```python
import requests

response = requests.post(
    "https://inkbox.ai/api/v1/tunnels",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "tunnel_name": "my-agent",
        "description": "Production webhook receiver",
    },
)
data = response.json()
tunnel, connect_secret = data["tunnel"], data["connect_secret"]
```

---

## List tunnels `GET`


List all tunnels in the caller's organization (excludes `deleted`). Each entry includes a live `currently_connected` flag.

### Response (200)

```json
{
    "tunnels": [
      {
        "id": "t1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "tunnel_name": "my-agent",
        "tls_mode": "edge",
        "status": "active",
        "currently_connected": true,
        "last_connected_at": "2026-04-21T12:35:00Z",
        "...": "additional fields per Tunnel object"
      }
    ]
}
```

### Code examples

**cURL**

```bash
curl -X GET "https://inkbox.ai/api/v1/tunnels" \\
    -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const response = await fetch("https://inkbox.ai/api/v1/tunnels", {
    headers: { "X-API-Key": "YOUR_API_KEY" },
});
const { tunnels } = await response.json();
```

**Python**

```python
import requests

response = requests.get(
    "https://inkbox.ai/api/v1/tunnels",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
tunnels = response.json()["tunnels"]
```

---

## Get tunnel `GET`


Fetch a single tunnel by ID. Includes a live `currently_connected` flag.

### Path parameters

| Parameter | Type | Description |
| :--- | :--- | :--- |
| `tunnel_id` | UUID | Tunnel ID |

### Code examples

**cURL**

```bash
curl -X GET "https://inkbox.ai/api/v1/tunnels/TUNNEL_ID" \\
    -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const response = await fetch(
    `https://inkbox.ai/api/v1/tunnels/${tunnelId}`,
    { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const tunnel = await response.json();
```

**Python**

```python
import requests

response = requests.get(
    f"https://inkbox.ai/api/v1/tunnels/{tunnel_id}",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
tunnel = response.json()
```

---

## Update tunnel `PATCH`


Update the tunnel's `description`. `tunnel_name` and `tls_mode` are immutable — create a new tunnel if you need a different name or mode.

### Path parameters

| Parameter | Type | Description |
| :--- | :--- | :--- |
| `tunnel_id` | UUID | Tunnel ID |

### Request body

| Field | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| `description` | string | No | New description (max 1000 chars). Omit to leave unchanged. |

### Code examples

**cURL**

```bash
curl -X PATCH "https://inkbox.ai/api/v1/tunnels/TUNNEL_ID" \\
    -H "X-API-Key: YOUR_API_KEY" \\
    -H "Content-Type: application/json" \\
    -d '{"description": "Updated description"}'
```

**JavaScript**

```javascript
const response = await fetch(
    `https://inkbox.ai/api/v1/tunnels/${tunnelId}`,
    {
        method: "PATCH",
        headers: {
            "X-API-Key": "YOUR_API_KEY",
            "Content-Type": "application/json",
        },
        body: JSON.stringify({ description: "Updated description" }),
    }
);
const tunnel = await response.json();
```

**Python**

```python
import requests

response = requests.patch(
    f"https://inkbox.ai/api/v1/tunnels/{tunnel_id}",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"description": "Updated description"},
)
tunnel = response.json()
```

---

## Delete tunnel `DELETE`


Move a tunnel into a 24-hour delete grace window. Inbound traffic stops immediately and `status` flips to `delete_pending`. The tunnel name stays reserved for your organization for 24 hours; after that, the tunnel is deleted and the name is released.

To undo within the grace window, call [`POST /tunnels/{id}/restore`](#restore-tunnel). Idempotent — calling delete on a `delete_pending` tunnel is a no-op.

### Path parameters

| Parameter | Type | Description |
| :--- | :--- | :--- |
| `tunnel_id` | UUID | Tunnel ID |

### Response (200)

Returns the updated [Tunnel object](#tunnel-object). `status` is `delete_pending` and `restore_deadline_at` is set to 24 hours from now.

### Code examples

**cURL**

```bash
curl -X DELETE "https://inkbox.ai/api/v1/tunnels/TUNNEL_ID" \\
    -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const response = await fetch(
    `https://inkbox.ai/api/v1/tunnels/${tunnelId}`,
    {
        method: "DELETE",
        headers: { "X-API-Key": "YOUR_API_KEY" },
    }
);
const tunnel = await response.json();
```

**Python**

```python
import requests

response = requests.delete(
    f"https://inkbox.ai/api/v1/tunnels/{tunnel_id}",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
tunnel = response.json()
```

---

## Restore tunnel `POST`


Undo a `delete_pending` deletion during the 24-hour grace window.

- `edge` tunnels return to `active`.
- `passthrough` tunnels return to `active` if a still-valid signed cert is on file; otherwise `awaiting_cert`.

### Path parameters

| Parameter | Type | Description |
| :--- | :--- | :--- |
| `tunnel_id` | UUID | Tunnel ID |

### Error responses

| Status | Description |
| :--- | :--- |
| 409 | Tunnel is not in `delete_pending` state (e.g. already finalized as `deleted`, or grace window expired) |

### Code examples

**cURL**

```bash
curl -X POST "https://inkbox.ai/api/v1/tunnels/TUNNEL_ID/restore" \\
    -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const response = await fetch(
    `https://inkbox.ai/api/v1/tunnels/${tunnelId}/restore`,
    {
        method: "POST",
        headers: { "X-API-Key": "YOUR_API_KEY" },
    }
);
const tunnel = await response.json();
```

**Python**

```python
import requests

response = requests.post(
    f"https://inkbox.ai/api/v1/tunnels/{tunnel_id}/restore",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
tunnel = response.json()
```

---

## Force-delete tunnel `DELETE`


Skip the 24-hour grace window and finalize a `delete_pending` tunnel immediately. The name is released for re-use right away.

> **Auth:** API key or Clerk JWT only. The grace window exists to undo accidental deletes; force-finalize bypasses that net.

### Path parameters

| Parameter | Type | Description |
| :--- | :--- | :--- |
| `tunnel_id` | UUID | Tunnel ID |

### Error responses

| Status | Description |
| :--- | :--- |
| 403 | Caller is not an admin (scoped agent keys cannot force-delete) |
| 409 | Tunnel is not in `delete_pending` state — call regular `DELETE` first |

### Code examples

**cURL**

```bash
curl -X DELETE "https://inkbox.ai/api/v1/tunnels/TUNNEL_ID/force" \\
    -H "X-API-Key: YOUR_ADMIN_API_KEY"
```

**JavaScript**

```javascript
const response = await fetch(
    `https://inkbox.ai/api/v1/tunnels/${tunnelId}/force`,
    {
        method: "DELETE",
        headers: { "X-API-Key": "YOUR_ADMIN_API_KEY" },
    }
);
const tunnel = await response.json();
```

**Python**

```python
import requests

response = requests.delete(
    f"https://inkbox.ai/api/v1/tunnels/{tunnel_id}/force",
    headers={"X-API-Key": "YOUR_ADMIN_API_KEY"},
)
tunnel = response.json()
```

---

## Tunnel object

| Field | Type | Description |
| :--- | :--- | :--- |
| `id` | UUID | Unique tunnel identifier |
| `organization_id` | string | Owning organization |
| `tunnel_name` | string | Subdomain label. Hostname is `{tunnel_name}.inkboxwire.com`. Immutable. |
| `description` | string \| null | Optional free-form description |
| `tls_mode` | string | `"edge"` or `"passthrough"`. Immutable. |
| `cert_pem` | string \| null | Signed cert PEM (passthrough only, after first `/sign-csr`) |
| `cert_fingerprint_sha256` | string \| null | SHA-256 fingerprint of `cert_pem`, useful for monitoring rotations |
| `cert_expires_at` | string \| null | ISO 8601 expiry of `cert_pem`. Renew before this date. |
| `status` | string | `awaiting_cert`, `active`, `delete_pending`, or `deleted` |
| `last_connected_at` | string \| null | ISO 8601 timestamp of the most recent agent connection |
| `last_connected_ip_addr` | string \| null | Client IP recorded at the most recent agent connection |
| `restore_deadline_at` | string \| null | Set only while `status="delete_pending"`. After this UTC timestamp, restore returns 409 and the tunnel finalizes to `deleted`. |
| `currently_connected` | boolean | `true` if at least one agent connection is currently registered |
| `created_at` | string | ISO 8601 |
| `updated_at` | string | ISO 8601 |
