---
title: "VPN · intentic sandbox API"
description: "Corporate tunnels the sandbox can hold open. Every route in the vpn group of the intentic sandbox API, with its input, its answer and a playground."
url: "https://intentic.dev/api/vpn/"
---

Connected systems

# VPN

Corporate tunnels the sandbox can hold open

**On this page (4 sections)**

- [Configured tunnels and which are up](#vpn-list)
- [Dial a VPN](#vpn-connect)
- [Drop a tunnel](#vpn-disconnect)
- [Read connections out of an exported config](#vpn-importForticlient)

What is configured, dialling and dropping one, and reading connections out of an exported client configuration. Link state is read back from the operating system, not from memory.

**GET`/vpn` Configured tunnels and which are up**

Every stored VPN with its live link state, read back from the operating system rather than from memory, so a tunnel dropped from a shell and one dropped from a screen look the same here.

### What you send

Nothing. Call it as it is.

### What comes back

| Field | Type |
| --- | --- |
| `links` Every configured tunnel with its live… | object[] |
| `id` Which tunnel | string |
| `provider` What kind of tunnel it is | "wireguard" | "fortinet" | "ipsec" |
| `state` Whether it is up, dialling, resting,… | "connected" | "connecting" | "disconnected" | "unavailable" … (5) |
| `gateway` What it dials | string |
| `interface` The network interface carrying it, once… | string |
| `address` The address the far end gave… | string |
| `routes` What goes through it | string[] |
| `dns` Name servers it pushed, when it… | string[] |
| `since` When it came up, in milliseconds | number |
| `autoConnect` Whether it dials itself when the… | boolean |
| `detail` Why it failed, or a note… | string |

Try it answered in this tab

curl

```bash
curl "$SANDBOX/vpn" \
 -H "x-intentic-control: $INTENTIC_TOKEN"
```

TypeScript

```typescript
import { sandbox } from "@intentic/sandbox-client";

const result = await sandbox.vpn.list();
```

**POST`/vpn/{id}/connect` Dial a VPN stream**

Brings a stored tunnel up, streaming the client's progress as it authenticates and then sets up routing. Streamed because a dial takes seconds and can fail with something you have to read: a wrong password, a gateway certificate nobody trusts, a code it wants. Connecting one that is already up simply says so.

### What you send

| Field | Type | Where |
| --- | --- | --- |
| `id` required Which tunnel to dial | string | address |
| `otp` A one-time code, where the gateway… | string | body |

### What comes back

| Field | Type |
| --- | --- |
| `when event is "message"` | shape |
| `data` | object |
| `kind` | string |
| `id` | string |
| `retry` | number |
| `when event is "done"` | shape |
| `data` | unknown |
| `id` | string |
| `retry` | number |
| `when event is "error"` | shape |
| `data` | unknown |
| `id` | string |
| `retry` | number |

Try it answered in this tab

curl

```bash
curl -N -X POST "$SANDBOX/vpn/a1b2c3d4/connect" \
 -H "x-intentic-control: $INTENTIC_TOKEN" \
 -H "content-type: application/json" \
 -d '{"otp":"…"}'
```

TypeScript

```typescript
import { sandbox } from "@intentic/sandbox-client";

const result = await sandbox.vpn.connect({
 "id": "a1b2c3d4",
 "otp": "…"
});
```

**POST`/vpn/{id}/disconnect` Drop a tunnel**

Takes the tunnel down. One that was already down is fine: the promise is that it is not up afterwards.

### What you send

| Field | Type | Where |
| --- | --- | --- |
| `id` required Which tunnel | string | address |

### What comes back

| Field | Type |
| --- | --- |
| `ok` Always true | true |

Try it answered in this tab

curl

```bash
curl -X POST "$SANDBOX/vpn/a1b2c3d4/disconnect" \
 -H "x-intentic-control: $INTENTIC_TOKEN"
```

TypeScript

```typescript
import { sandbox } from "@intentic/sandbox-client";

const result = await sandbox.vpn.disconnect({
 "id": "a1b2c3d4"
});
```

**POST`/vpn/import-forticlient` Read connections out of an exported config**

Turns an exported FortiClient configuration into a list of connections you can add, so somebody holding that file picks from a list instead of retyping a host and port for every tunnel.

### What you send

| Field | Type | Where |
| --- | --- | --- |
| `xml` required The exported configuration file, whole | string | body |

### What comes back

| Field | Type |
| --- | --- |
| `connections` The connections found in the file,… | object[] |
| `id` The id it would be added… | string |
| `label` Its name as the file has… | string |
| `provider` What kind of tunnel it is | "wireguard" | "fortinet" | "ipsec" |
| `server` Where it dials | string |
| `port` On which port | number |
| `username` The username, but only when the… | string |
| `description` Whatever the file said about it | string |
| `localId` An identity some tunnel types need,… | string |
| `aggressive` Which negotiation mode it used | boolean |
| `pfs` Whether it asked for forward secrecy | boolean |
| `dhGroup` Which key-exchange group it used | string |
| `needs` What you still have to type… | string[] |

Try it answered in this tab

curl

```bash
curl -X POST "$SANDBOX/vpn/import-forticlient" \
 -H "x-intentic-control: $INTENTIC_TOKEN" \
 -H "content-type: application/json" \
 -d '{"xml":"…"}'
```

TypeScript

```typescript
import { sandbox } from "@intentic/sandbox-client";

const result = await sandbox.vpn.importForticlient({
 "xml": "…"
});
```

More in Connected systems

[Previous ← Secrets](https://intentic.dev/api/secrets/)[Next Exit locations →](https://intentic.dev/api/exit/)
