GoUndetected Logo

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_xxxxxxxxxxxxxxxxxxxx

Create 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-core

You 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.

PlanCloud-hours / monthConcurrent sessions
Basic501
Team2003
Unlimited1000 (fair use)10
Free trial51

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/create Try it

Body

folderIdrequireduuidFolder (in your org) to place the profile in.
namestringOptional, ≤200 chars. Blank → auto-named "Profile N".
userAgentstringOptional User-Agent for the fingerprint.
viewportWidthintegerOptional, ≥320.
viewportHeightintegerOptional, ≥240.
proxyobjectOptional. 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.
Request
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"}}'
Response
{
  "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/profiles Try it

Query parameters

limitinteger1–200, default 50.
offsetintegerDefault 0.
Request
curl -X GET "https://api.goundetected.io/v1/profiles?limit=20&offset=0" \
  -H "Authorization: Bearer $GOU_API_KEY"
Response
{
  "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} Try it

Path parameters

idrequireduuidProfile id.
Request
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} Try it

Path parameters

idrequireduuidProfile id.

Body

namestringNew name.
userAgentstringNew User-Agent.
viewportWidthintegerNew width.
viewportHeightintegerNew height.
proxyobjectOptional. 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.
Request
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} Try it

Path parameters

idrequireduuidProfile id.
Request
curl -X DELETE "https://api.goundetected.io/v1/profiles/:id" \
  -H "Authorization: Bearer $GOU_API_KEY"

Cookies

Read and write a profile's cookies without launching the browser. Cookies use the common Cookie-Editor / EditThisCookie JSON shape, so exports from those extensions import directly.

The profile must not be running while you import or clear its cookies — mutating a live profile returns 409 Conflict.

List cookies

Returns every cookie stored in the profile.

GET/v1/profiles/{id}/cookies Try it

Path parameters

idrequireduuidProfile id.
Request
curl -X GET "https://api.goundetected.io/v1/profiles/:id/cookies" \
  -H "Authorization: Bearer $GOU_API_KEY"
Response
{
  "data": [
    {
      "domain": ".linkedin.com",
      "name": "li_at",
      "value": "AQEDAT...",
      "path": "/",
      "secure": true,
      "httpOnly": true,
      "hostOnly": false,
      "session": false,
      "expirationDate": 1789876543,
      "sameSite": "no_restriction"
    }
  ],
  "total": 42
}

Import cookies

Merges the supplied cookies into the profile (INSERT OR REPLACE, keyed by domain + name + path). Pass ?mode=replace to clear existing cookies first, so the profile ends up with exactly this set. The request body is a JSON array of cookies. Returns 409 if the profile is running.

POST/v1/profiles/{id}/cookies Try it

Path parameters

idrequireduuidProfile id.

Query parameters

modestringmerge (default) or replace.

Body

domainrequiredstringe.g. .linkedin.com (leading dot = include subdomains).
namerequiredstringCookie name.
valuerequiredstringCookie value (stored in plaintext).
pathstringDefault /.
securebooleanDefault false.
httpOnlybooleanDefault false.
hostOnlybooleanDefault inferred from domain (leading dot = not host-only).
sessionbooleanDefault true when expirationDate is omitted.
expirationDatenumberExpiry as unix seconds. Omit for a session cookie.
sameSitestringno_restriction | lax | strict | unspecified.
Request
curl -X POST "https://api.goundetected.io/v1/profiles/:id/cookies?limit=20&offset=0" \
  -H "Authorization: Bearer $GOU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '[{"domain":".linkedin.com","name":"li_at","value":"AQEDAT...","path":"/","secure":true,"httpOnly":true,"expirationDate":1789876543,"sameSite":"no_restriction"}]'
Response
{
  "imported": 1,
  "total": 42
}

Clear cookies

Deletes all cookies from the profile. Returns 204 No Content. Returns 409 if the profile is running.

DELETE/v1/profiles/{id}/cookies Try it

Path parameters

idrequireduuidProfile id.
Request
curl -X DELETE "https://api.goundetected.io/v1/profiles/:id/cookies" \
  -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/create Try it

Body

namerequiredstring1–255 chars.
Request
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"}'
Response
{
  "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/folders Try it
Request
curl -X GET "https://api.goundetected.io/v1/folders" \
  -H "Authorization: Bearer $GOU_API_KEY"
Response
{
  "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} Try it

Path parameters

idrequireduuidFolder id.
Request
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} Try it

Path parameters

idrequireduuidFolder id.

Body

namerequiredstringNew name.
Request
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} Try it

Path parameters

idrequireduuidFolder id.
Request
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}/permissions Try it

Path parameters

idrequireduuidFolder id.
Request
curl -X GET "https://api.goundetected.io/v1/folders/:id/permissions" \
  -H "Authorization: Bearer $GOU_API_KEY"
Response
{
  "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}/permissions Try it

Path parameters

idrequireduuidFolder id.

Body

emailstringMember's email (or use organizationMemberId).
organizationMemberIduuidMember id (or use email).
canViewbooleanDefault false.
canRunbooleanDefault false.
canEditbooleanDefault false.
fullAccessbooleanImplies all. Default false.
Request
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}'
Response
{
  "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} Try it

Path parameters

idrequireduuidFolder id.
memberIdrequireduuidorganization_members.id.
Request
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" } }
StatusMeaning
400Validation failed or bad reference (e.g. folder not in your org).
401Missing or invalid API key.
403Key is missing the required scope.
404Resource not found in your organization.
409Conflict — e.g. editing cookies while the profile is running.
429Rate limited.