28 endpoints, six tags.
All responses are JSON. Every request must carry an
Authorization: Bearer $DONUT_API_KEY header — see
Authentication. Set the env var in your shell once and
every example below will pick it up.
profiles
Manage isolated browser profiles — create, list, update, delete, launch, kill, drive.
/v1/profiles Returns every profile in the local install.
Request
curl -sS -X GET http://127.0.0.1:10108/v1/profiles \
-H "Authorization: Bearer $DONUT_API_KEY"Response
{
"profiles": [
{
"id": "ce490dd0-02fe-4be5-b91d-fedec7550b28",
"name": "Work · Chromium",
"browser": "wayfern",
"version": "147.0.7727.138",
"release_type": "stable",
"group_id": "75f2b42f-8c94-4291-8af7-7a3d79122507",
"proxy_id": null,
"vpn_id": null,
"tags": [],
"is_running": true,
"process_id": 29300,
"last_launch": 1779106515,
"camoufox_config": null,
"launch_hook": null,
"proxy_bypass_rules": []
},
{
"id": "c20221fa-be36-485d-a4e1-5537254fa863",
"name": "Personal · Firefox",
"browser": "camoufox",
"version": "v150.0.2-beta.25",
"release_type": "stable",
"group_id": null,
"proxy_id": null,
"vpn_id": null,
"tags": [
"personal"
],
"is_running": false,
"process_id": null,
"last_launch": 1779226206,
"camoufox_config": {},
"launch_hook": null,
"proxy_bypass_rules": []
}
]
} /v1/profiles Both browser engines need their own config object. Pass `wayfern_config: {}` and `camoufox_config: {}` for sensible defaults.
Request
curl -sS -X POST http://127.0.0.1:10108/v1/profiles \
-H "Authorization: Bearer $DONUT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "client-1",
"browser": "chromium",
"version": "latest",
"proxy_id": "c5617755-0709-4820-832a-6b97a0a24f3c",
"tags": [
"client-1",
"prod"
],
"wayfern_config": {},
"camoufox_config": {}
}'Response
{
"profile": {
"id": "prf_01J8...",
"name": "client-1",
"browser": "chromium",
"version": "latest",
"release_type": "stable",
"group_id": null,
"proxy_id": "c5617755-0709-4820-832a-6b97a0a24f3c",
"vpn_id": null,
"tags": [
"client-1",
"prod"
],
"is_running": false,
"process_id": null,
"last_launch": null,
"camoufox_config": {},
"launch_hook": null,
"proxy_bypass_rules": []
}
} /v1/profiles/{id} Fetches the full profile object by ID.
Request
curl -sS -X GET http://127.0.0.1:10108/v1/profiles/{id} \
-H "Authorization: Bearer $DONUT_API_KEY"Response
{
"profile": {
"id": "ce490dd0-02fe-4be5-b91d-fedec7550b28",
"name": "Work · Chromium",
"browser": "wayfern",
"version": "147.0.7727.138",
"release_type": "stable",
"group_id": "75f2b42f-8c94-4291-8af7-7a3d79122507",
"proxy_id": null,
"vpn_id": null,
"tags": [],
"is_running": true,
"process_id": 29300,
"last_launch": 1779106515,
"camoufox_config": null,
"launch_hook": null,
"proxy_bypass_rules": []
}
} /v1/profiles/{id} Any subset of fields can be sent. `camoufox_config` is required by the spec; pass the current value if you don't want to change it.
Request
curl -sS -X PUT http://127.0.0.1:10108/v1/profiles/{id} \
-H "Authorization: Bearer $DONUT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Work · Chromium — renamed",
"proxy_id": "c5617755-0709-4820-832a-6b97a0a24f3c",
"tags": [
"work",
"client-1"
],
"camoufox_config": {}
}'Response
{
"profile": {
"id": "...",
"name": "Work · Chromium — renamed",
"...": "..."
}
} /v1/profiles/{id} Hard delete. The profile data on disk is removed.
Request
curl -sS -X DELETE http://127.0.0.1:10108/v1/profiles/{id} \
-H "Authorization: Bearer $DONUT_API_KEY"Response
204 No Content /v1/profiles/{id}/run Spawns the browser process for the profile and returns the CDP port. Optionally headless and/or opening at a specific URL.
Request
curl -sS -X POST http://127.0.0.1:10108/v1/profiles/{id}/run \
-H "Authorization: Bearer $DONUT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://whoer.net",
"headless": false
}'Response
{
"cdp_port": 9222,
"process_id": 41218
} Without an active Pro subscription this endpoint returns HTTP 402 Payment Required.
/v1/profiles/{id}/kill Terminates the browser process for the profile.
Request
curl -sS -X POST http://127.0.0.1:10108/v1/profiles/{id}/kill \
-H "Authorization: Bearer $DONUT_API_KEY"Response
204 No Content /v1/profiles/{id}/open-url Opens the given URL as a new tab in a profile that's already running.
Request
curl -sS -X POST http://127.0.0.1:10108/v1/profiles/{id}/open-url \
-H "Authorization: Bearer $DONUT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://news.ycombinator.com"
}'Response
200 OK Without an active Pro subscription this endpoint returns HTTP 402 Payment Required.
groups
Organize profiles into named groups for bulk actions.
/v1/groups Every group, with its current profile count.
Request
curl -sS -X GET http://127.0.0.1:10108/v1/groups \
-H "Authorization: Bearer $DONUT_API_KEY"Response
[
{
"id": "75f2b42f-8c94-4291-8af7-7a3d79122507",
"name": "Work",
"profile_count": 12
},
{
"id": "bfda155d-0c04-4fc2-ab62-30651557a510",
"name": "Personal",
"profile_count": 3
}
] /v1/groups Create a named group profiles can be assigned to.
Request
curl -sS -X POST http://127.0.0.1:10108/v1/groups \
-H "Authorization: Bearer $DONUT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Client – Acme"
}'Response
{
"id": "grp_01J8...",
"name": "Client – Acme",
"profile_count": 0
} /v1/groups/{id} Fetches a single group by ID.
Request
curl -sS -X GET http://127.0.0.1:10108/v1/groups/{id} \
-H "Authorization: Bearer $DONUT_API_KEY"Response
{
"id": "bfda155d-0c04-4fc2-ab62-30651557a510",
"name": "Personal",
"profile_count": 3
} /v1/groups/{id} Updates the group name.
Request
curl -sS -X PUT http://127.0.0.1:10108/v1/groups/{id} \
-H "Authorization: Bearer $DONUT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Client – Acme (2026)"
}'Response
{
"id": "grp_01J8...",
"name": "Client – Acme (2026)",
"profile_count": 8
} /v1/groups/{id} Profiles assigned to the group are not deleted — their `group_id` is set to null.
Request
curl -sS -X DELETE http://127.0.0.1:10108/v1/groups/{id} \
-H "Authorization: Bearer $DONUT_API_KEY"Response
204 No Content proxies
HTTP, HTTPS, SOCKS4, SOCKS5 proxy definitions. Attach to a profile via `proxy_id`.
/v1/proxies Every proxy definition stored locally.
Request
curl -sS -X GET http://127.0.0.1:10108/v1/proxies \
-H "Authorization: Bearer $DONUT_API_KEY"Response
[
{
"id": "c5617755-0709-4820-832a-6b97a0a24f3c",
"name": "Albania",
"proxy_settings": {
"proxy_type": "https",
"host": "proxy.example.com",
"port": 8080,
"username": "user",
"password": "secret"
}
}
] /v1/proxies `proxy_type` ∈ `http` | `https` | `socks4` | `socks5`. `username`/`password` are optional for unauthenticated proxies.
Request
curl -sS -X POST http://127.0.0.1:10108/v1/proxies \
-H "Authorization: Bearer $DONUT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "us-residential",
"proxy_settings": {
"proxy_type": "socks5",
"host": "proxy.example.com",
"port": 1080,
"username": "user",
"password": "secret"
}
}'Response
{
"id": "pxy_01J8...",
"name": "us-residential",
"proxy_settings": {
"proxy_type": "socks5",
"host": "proxy.example.com",
"port": 1080,
"username": "user",
"password": "secret"
}
} /v1/proxies/{id} Returns the single proxy definition matching the ID.
Request
curl -sS -X GET http://127.0.0.1:10108/v1/proxies/{id} \
-H "Authorization: Bearer $DONUT_API_KEY"Response
{
"id": "c5617755-0709-4820-832a-6b97a0a24f3c",
"name": "Albania",
"proxy_settings": {
"proxy_type": "https",
"host": "proxy.example.com",
"port": 8080,
"username": "user",
"password": "secret"
}
} /v1/proxies/{id} Send the full updated `proxy_settings` and optionally a new `name`.
Request
curl -sS -X PUT http://127.0.0.1:10108/v1/proxies/{id} \
-H "Authorization: Bearer $DONUT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "us-residential-rotating",
"proxy_settings": {
"proxy_type": "socks5",
"host": "rotate.example.com",
"port": 1080
}
}'Response
{
"id": "pxy_01J8...",
"name": "us-residential-rotating",
"proxy_settings": {
"proxy_type": "socks5",
"host": "rotate.example.com",
"port": 1080,
"username": null,
"password": null
}
} /v1/proxies/{id} Profiles that referenced the proxy will have their `proxy_id` set to null.
Request
curl -sS -X DELETE http://127.0.0.1:10108/v1/proxies/{id} \
-H "Authorization: Bearer $DONUT_API_KEY"Response
204 No Content vpns
WireGuard VPN configs. Attach to a profile via `vpn_id`.
/v1/vpns Every VPN config stored locally.
Request
curl -sS -X GET http://127.0.0.1:10108/v1/vpns \
-H "Authorization: Bearer $DONUT_API_KEY"Response
[
{
"id": "22034ceb-7b76-456e-9cfe-a4b207751f8d",
"name": "Test WG",
"vpn_type": "WireGuard",
"created_at": 1777203528,
"last_used": null
}
] /v1/vpns `config_data` is the raw WireGuard `.conf` content. Newlines must be preserved.
Request
curl -sS -X POST http://127.0.0.1:10108/v1/vpns \
-H "Authorization: Bearer $DONUT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "DE — Frankfurt",
"vpn_type": "WireGuard",
"config_data": "[Interface]\nPrivateKey = <base64>\nAddress = 10.0.0.2/32\n\n[Peer]\nPublicKey = <base64>\nEndpoint = de.example.com:51820\nAllowedIPs = 0.0.0.0/0"
}'Response
{
"id": "vpn_01J8...",
"name": "DE — Frankfurt",
"vpn_type": "WireGuard",
"created_at": 1779226206,
"last_used": null
} /v1/vpns/import Same as `POST /v1/vpns`, but you pass the file `content` plus `filename` (used to derive `name` if omitted).
Request
curl -sS -X POST http://127.0.0.1:10108/v1/vpns/import \
-H "Authorization: Bearer $DONUT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filename": "de-frankfurt.conf",
"name": "DE — Frankfurt",
"content": "[Interface]\nPrivateKey = <base64>\n…\n[Peer]\nPublicKey = <base64>\nEndpoint = de.example.com:51820"
}'Response
{
"id": "vpn_01J8...",
"name": "DE — Frankfurt",
"vpn_type": "WireGuard",
"created_at": 1779226206,
"last_used": null
} /v1/vpns/{id} Returns metadata for a single VPN config. The raw key material is not exposed.
Request
curl -sS -X GET http://127.0.0.1:10108/v1/vpns/{id} \
-H "Authorization: Bearer $DONUT_API_KEY"Response
{
"id": "22034ceb-7b76-456e-9cfe-a4b207751f8d",
"name": "Test WG",
"vpn_type": "WireGuard",
"created_at": 1777203528,
"last_used": null
} /v1/vpns/{id} Currently only the `name` field is updatable on existing VPNs.
Request
curl -sS -X PUT http://127.0.0.1:10108/v1/vpns/{id} \
-H "Authorization: Bearer $DONUT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "DE — Frankfurt (primary)"
}'Response
{
"id": "vpn_01J8...",
"name": "DE — Frankfurt (primary)",
"vpn_type": "WireGuard",
"created_at": 1779226206,
"last_used": null
} /v1/vpns/{id} Profiles that referenced the VPN will have their `vpn_id` set to null.
Request
curl -sS -X DELETE http://127.0.0.1:10108/v1/vpns/{id} \
-H "Authorization: Bearer $DONUT_API_KEY"Response
204 No Content browsers
Trigger browser-binary downloads and check installed versions.
/v1/browsers/download Triggers a download of the named browser at the named version (Chromium via Wayfern, Firefox via Camoufox).
Request
curl -sS -X POST http://127.0.0.1:10108/v1/browsers/download \
-H "Authorization: Bearer $DONUT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"browser": "wayfern",
"version": "147.0.7727.138"
}'Response
{
"started": true,
"browser": "wayfern",
"version": "147.0.7727.138"
} /v1/browsers/{browser}/versions Versions are returned newest first.
Request
curl -sS -X GET http://127.0.0.1:10108/v1/browsers/{browser}/versions \
-H "Authorization: Bearer $DONUT_API_KEY"Response
[
"147.0.7727.138",
"147.0.7727.137"
] /v1/browsers/{browser}/versions/{version}/downloaded Returns `true` when the browser+version pair is on disk.
Request
curl -sS -X GET http://127.0.0.1:10108/v1/browsers/{browser}/versions/{version}/downloaded \
-H "Authorization: Bearer $DONUT_API_KEY"Response
true