Developers
API Reference
Manage browser profiles, folders, and folder permissions programmatically over a simple REST API.
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. |