Developers
API Reference
Manage browser profiles, folders, and folder permissions programmatically over a simple REST API.
Base URLhttps://api.goundetected.io
Introduction
The GoUndetected API lets you manage browser profiles, folders, and folder permissions programmatically. Everything you can do with profiles and folders in the app is available over a simple REST interface.
All requests go to https://api.goundetected.io and return JSON. Every endpoint is scoped to a single organization — an API key only ever sees or changes data in the organization it belongs to.
Authentication
Authenticate every request with an API key in the Authorization header as a Bearer token:
Authorization: Bearer gou_live_xxxxxxxxxxxxxxxxxxxxCreate and manage keys in the desktop app under Settings → API Keys (owner only). The full key is shown once at creation — store it securely; it can't be retrieved later. Each key is granted scopes that gate which endpoints it can call.
Scopes: profiles:read, profiles:write, folders:read, folders:write, proxies:read, sessions:read, sessions:write.
SDK (@goundetected/sdk)
The official `@goundetected/sdk` lets you drive GoUndetected from code — manage profiles, folders and cookies, and, the headline feature, launch a profile as a live cloud browser you control with Puppeteer or Playwright. Same account, same profiles, same proxies as the desktop app — now scriptable. npm: npmjs.com/package/@goundetected/sdk
Install
npm install @goundetected/sdk puppeteer-coreYou control the cloud browser over CDP, so you need a CDP client — puppeteer-core (no bundled Chromium, it just connects) or Playwright (chromium.connectOverCDP). Works in Node 18+, ships ESM + CommonJS, with TypeScript types included.
Get an API key
In the desktop app go to Settings → API Keys and create a key with the scopes you need. For automation you almost always want profiles:read + sessions:write (add profiles:write to also create profiles from code). Treat the key like a password — put it in an env var, never in git.
Quick start
import { Goundetected } from "@goundetected/sdk";
import puppeteer from "puppeteer-core";
const gg = new Goundetected({ apiKey: process.env.GOU_API_KEY });
// 1. launch a profile as a cloud browser
const session = await gg.launch("<profileId>");
// 2. connect Puppeteer over CDP
const browser = await puppeteer.connect({ browserWSEndpoint: session.browserWSEndpoint });
const page = (await browser.pages())[0] ?? (await browser.newPage());
// 3. drive it — you're on the profile's fingerprint + proxy
await page.goto("https://ipinfo.io/json", { waitUntil: "domcontentloaded" });
console.log(await page.evaluate(() => document.body.innerText));
// 4. always stop when done (frees the browser + syncs the profile)
await browser.disconnect();
await session.stop();That's the whole loop: launch → connect → drive → stop.
The client
const gg = new Goundetected({
apiKey: "gou_live_…", // required
baseUrl: "https://api.goundetected.io", // optional (this is the default)
});It exposes three resource groups plus launch(): gg.profiles (list / get / create / update / delete + cookies), gg.folders (list / get / create / update / delete), gg.sessions (list / get / heartbeat / stop), and gg.launch(profileId, opts?) which starts a cloud browser and returns a Session.
Launching a cloud browser
const session = await gg.launch(profileId, {
region: "us-east-1", // optional — omit to let the server pick
ttlSeconds: 900, // optional — auto-stop horizon (see below)
readyTimeoutMs: 120000, // optional — wait for the browser (default 120s)
});launch() resolves once the browser is connectable and returns a Session: pass session.browserWSEndpoint to puppeteer.connect({ browserWSEndpoint }) or chromium.connectOverCDP(url). It also carries session.sessionId, session.region, session.heartbeat() and session.stop(). Cold start is ~30–60s — a fresh browser is provisioned on demand.
TTL, heartbeat & auto-stop
Every cloud session has an auto-stop horizon. A background reaper stops any session whose horizon lapses, so a crashed or forgotten script can't leave a browser — and your bill — running forever. Default TTL is 5 minutes: untouched for ~5 min → stopped. Each session.heartbeat() pushes the horizon 5 minutes into the future. Prefer a fixed window instead? Pass ttlSeconds at launch (60–43200, up to 12h).
For anything longer than a few minutes, heartbeat on a timer:
const hb = setInterval(() => session.heartbeat().catch(() => {}), 2 * 60 * 1000);
try {
// … your long automation …
} finally {
clearInterval(hb);
await browser.disconnect();
await session.stop();
}Rule of thumb: heartbeat every 2 minutes while working, and stop the moment you finish — prompt stops are what keep your cloud-hours low.
Concurrency & cloud-hours limits
Cloud sessions use real compute, so every plan includes a monthly cloud-hours pool and a cap on simultaneous sessions. launch() throws before starting anything if you're over a limit — 403 for cloud-hours reached / not on your plan, 409 for concurrency. Local desktop launches are free and never counted. Watch usage in the app under Billing → Cloud browser usage.
| Plan | Cloud-hours / month | Concurrent sessions |
|---|---|---|
| Basic | 50 | 1 |
| Team | 200 | 3 |
| Unlimited | 1000 (fair use) | 10 |
| Free trial | 5 | 1 |
Requirements & gotchas
Logged-in profiles — for sites behind a login (LinkedIn, etc.) the profile must already be signed in: do it once in the desktop app (cookies persist) or import them with setCookies. Heavy SPAs load async — after page.goto, await page.waitForSelector(...) for the element you need, don't rely on domcontentloaded alone. Cold starts (~30–60s) are normal (on-demand provisioning). Acceptable use — proxies + cloud are for managing your own or clients' social-media accounts; no scraping, spam, or abuse.
Profiles
A profile is an isolated browser identity (fingerprint, viewport, and optional proxy). Profiles live inside folders — a new profile is placed in a folder at creation via folderId.
Create a profile
Creates a profile and assigns it to a folder in your organization. If name is blank or omitted, a default like "Profile 5" is generated.
POST/v1/profiles/createprofiles:write Try it
Body
| folderIdrequired | uuid | Folder (in your org) to place the profile in. |
| name | string | Optional, ≤200 chars. Blank → auto-named "Profile N". |
| userAgent | string | Optional User-Agent for the fingerprint. |
| viewportWidth | integer | Optional, ≥320. |
| viewportHeight | integer | Optional, ≥240. |
| proxy | object | Optional. Your own proxy: { type: http|https|socks5, ip, port, login, password, countryCode?, timezone? }. countryCode/timezone are auto-detected through the proxy when omitted. Send null, {} or omit for no proxy. |
curl -X POST "https://api.goundetected.io/v1/profiles/create" \
-H "Authorization: Bearer $GOU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"folderId":"74c8298c-9276-4187-a8c6-bc72c84e2662","name":"LinkedIn — US persona","proxy":{"type":"http","ip":"1.2.3.4","port":"8080","login":"user","password":"pass"}}'{
"id": "8bd9a9fe-dfcd-4abc-9719-652967b60f28",
"name": "LinkedIn — US persona",
"status": "idle",
"user_agent": null,
"viewport_width": null,
"viewport_height": null,
"is_in_use": false,
"last_used": null,
"created_at": "2026-08-12T14:45:04.138Z",
"updated_at": "2026-08-12T14:45:04.138Z",
"proxy": {
"type": "http",
"ip": "1.2.3.4",
"port": "8080",
"login": "user",
"password": "pass",
"countryCode": "US",
"timezone": "America/New_York"
}
}List profiles
Returns your organization's profiles, paginated.
GET/v1/profilesprofiles:read Try it
Query parameters
| limit | integer | 1–200, default 50. |
| offset | integer | Default 0. |
curl -X GET "https://api.goundetected.io/v1/profiles?limit=20&offset=0" \
-H "Authorization: Bearer $GOU_API_KEY"{
"data": [
{
"id": "0003b217-d1a0-42a7-9cad-53967ccef518",
"name": "Sam Roberts",
"status": "idle"
}
],
"total": 3751,
"limit": 50,
"offset": 0
}Get a profile
Returns a single profile, including its nested proxy. 404 if the profile isn't in your organization.
GET/v1/profiles/{id}profiles:read Try it
Path parameters
| idrequired | uuid | Profile id. |
curl -X GET "https://api.goundetected.io/v1/profiles/:id" \
-H "Authorization: Bearer $GOU_API_KEY"Update a profile
Updates any subset of a profile's fields.
PATCH/v1/profiles/{id}profiles:write Try it
Path parameters
| idrequired | uuid | Profile id. |
Body
| name | string | New name. |
| userAgent | string | New User-Agent. |
| viewportWidth | integer | New width. |
| viewportHeight | integer | New height. |
| proxy | object | Optional. Your own proxy: { type: http|https|socks5, ip, port, login, password, countryCode?, timezone? }. countryCode/timezone are auto-detected through the proxy when omitted. Send null, {} or omit for no proxy. |
curl -X PATCH "https://api.goundetected.io/v1/profiles/:id" \
-H "Authorization: Bearer $GOU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Renamed profile"}'Delete a profile
Deletes the profile. Returns 204 No Content.
DELETE/v1/profiles/{id}profiles:write Try it
Path parameters
| idrequired | uuid | Profile id. |
curl -X DELETE "https://api.goundetected.io/v1/profiles/:id" \
-H "Authorization: Bearer $GOU_API_KEY"Folders
Folders group profiles and are the unit of access control. Deleting a folder removes its profile assignments and permissions; the profiles themselves are kept.
Create a folder
Creates a folder in your organization.
POST/v1/folders/createfolders:write Try it
Body
| namerequired | string | 1–255 chars. |
curl -X POST "https://api.goundetected.io/v1/folders/create" \
-H "Authorization: Bearer $GOU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Client A — LinkedIn"}'{
"id": "d97d8b61-6a47-4405-a962-8206e8bd7418",
"name": "Client A — LinkedIn",
"position": 0,
"sort_order": "a1",
"created_at": "2026-08-12T17:18:28.078Z",
"updated_at": "2026-08-12T17:18:28.078Z"
}List folders
Returns your organization's folders.
GET/v1/foldersfolders:read Try it
curl -X GET "https://api.goundetected.io/v1/folders" \
-H "Authorization: Bearer $GOU_API_KEY"{
"data": [
{
"id": "74c8298c-9276-4187-a8c6-bc72c84e2662",
"name": "Client A"
}
]
}Get a folder
Returns a single folder. 404 if not in your organization.
GET/v1/folders/{id}folders:read Try it
Path parameters
| idrequired | uuid | Folder id. |
curl -X GET "https://api.goundetected.io/v1/folders/:id" \
-H "Authorization: Bearer $GOU_API_KEY"Rename a folder
Renames a folder.
PATCH/v1/folders/{id}folders:write Try it
Path parameters
| idrequired | uuid | Folder id. |
Body
| namerequired | string | New name. |
curl -X PATCH "https://api.goundetected.io/v1/folders/:id" \
-H "Authorization: Bearer $GOU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Renamed folder"}'Delete a folder
Deletes the folder (cascades assignments + permissions). Returns 204.
DELETE/v1/folders/{id}folders:write Try it
Path parameters
| idrequired | uuid | Folder id. |
curl -X DELETE "https://api.goundetected.io/v1/folders/:id" \
-H "Authorization: Bearer $GOU_API_KEY"Folder permissions
Control which members of your organization can view, run, or edit a folder. Permissions are per-member.
List folder permissions
Lists each member's access to the folder.
GET/v1/folders/{id}/permissionsfolders:read Try it
Path parameters
| idrequired | uuid | Folder id. |
curl -X GET "https://api.goundetected.io/v1/folders/:id/permissions" \
-H "Authorization: Bearer $GOU_API_KEY"{
"data": [
{
"organizationMemberId": "b5c2cf83-3a29-4fe6-add2-968aaf8c4ccd",
"email": "teammate@example.com",
"canView": true,
"canRun": false,
"canEdit": false,
"fullAccess": false
}
]
}Set a folder permission
Grants or replaces one member's access. Identify the member by email or organizationMemberId. Flags default to false; fullAccess implies all.
PUT/v1/folders/{id}/permissionsfolders:write Try it
Path parameters
| idrequired | uuid | Folder id. |
Body
| string | Member's email (or use organizationMemberId). | |
| organizationMemberId | uuid | Member id (or use email). |
| canView | boolean | Default false. |
| canRun | boolean | Default false. |
| canEdit | boolean | Default false. |
| fullAccess | boolean | Implies all. Default false. |
curl -X PUT "https://api.goundetected.io/v1/folders/:id/permissions" \
-H "Authorization: Bearer $GOU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"teammate@example.com","canView":true}'{
"organizationMemberId": "b5c2cf83-3a29-4fe6-add2-968aaf8c4ccd",
"folderId": "d97d8b61-6a47-4405-a962-8206e8bd7418",
"updated": true
}Remove a folder permission
Revokes a member's access to the folder. Returns 204.
DELETE/v1/folders/{id}/permissions/{memberId}folders:write Try it
Path parameters
| idrequired | uuid | Folder id. |
| memberIdrequired | uuid | organization_members.id. |
curl -X DELETE "https://api.goundetected.io/v1/folders/:id/permissions/:memberId" \
-H "Authorization: Bearer $GOU_API_KEY"Errors
Errors return a consistent shape with the right HTTP status:
{ "error": { "code": "unauthorized", "message": "Invalid API key" } }| Status | Meaning |
|---|---|
| 400 | Validation failed or bad reference (e.g. folder not in your org). |
| 401 | Missing or invalid API key. |
| 403 | Key is missing the required scope. |
| 404 | Resource not found in your organization. |
| 409 | Conflict — e.g. editing cookies while the profile is running. |
| 429 | Rate limited. |