GoUndetected / Developers

API Reference

Profiles, proxies, notes and folders. Build your workflow with REST, the SDK, or a connected agent.

BASE URLhttps://api.goundetected.io

PreviewTools v2 & Connect require a supported desktop build. Classic /v1 is unchanged.

Plans & API limits

Introduction

The GoUndetected API lets you manage browser profiles, folders, proxies, notes and folder permissions programmatically — for teams and agencies running their own or their clients' social-media accounts. It is one API with two credentials: classic API keys on /v1, and Connect tokens (Preview) on Tools v2 (/v2/tools/*) for agents and batch work — see Tools & Connect. Each job (create profiles, folders, cookies, …) is documented once; where both surfaces cover it, the endpoint names the other in one line.

Classic requests go to https://api.goundetected.io; Tools Preview uses the base in your Connect setup. Classic API keys only see or change data in their organization. Tools Preview can read shared-in folders within their grants. Successful classic deletes return 204 with no body; other responses return JSON.

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.

Connect tokens (groot_pat_…, Preview) are the credential for Tools v2 (/v2/tools/* only — they are refused on /v1, and gou_live_… keys are refused on /v2/tools). An organization owner or admin creates one in the desktop app under Settings → Integrations (Connect), picks its permissions and expiry, and copies the token once — it is never shown again (lifecycle: Agent connections). Send it as Authorization: Bearer <token> to the API base shown in the Connect setup block.

Connect scopes (granular — the classic profiles:write / folders:write are not accepted as aliases):

GroupScopes
Foldersfolders:read, folders:create, folders:update, folders:delete
Profilesprofiles:read, profiles:create, profiles:update, profiles:move, profiles:delete
Proxiesproxies:read, proxies:use
Profile notesprofile-notes:read, profile-notes:write, profile-notes:delete

*:delete scopes never delete directly: they only let the token request a deletion that an owner or admin confirms in Activity (see Confirmations). Plans and limits: Pricing.

Tools & ConnectPreview

Preview. Tools v2 is available through Connect on supported desktop builds (Settings → Integrations (Connect)). The production public base for classic automation remains https://api.goundetected.io (/v1). Treat Tools paths as Preview until general availability is announced — shapes may still change. After release Tools v2 is intended to be served from the same host (/v2/tools/*, not under /v1); until then use the API base shown in your Connect setup block.

GoUndetected exposes two complementary surfaces. The classic REST API (/v1, gou_live_… keys) manages profiles, folders, cookies and cloud browser sessions. Tools v2 via Connect is the agent-friendly batch surface: create folders and profiles in groups, assign proxies, add notes and discover capabilities — always scoped to one organization, with dry-run previews and human confirmation for destructive work.

Credentials and scopes for both surfaces are in Authentication — one section, two credential types (gou_live_… API keys for /v1, groot_pat_… Connect tokens for /v2/tools/*).

Mutations. Send an Idempotency-Key header (8–200 printable characters, e.g. a UUID) on every real mutation. Reuse the same key only to retry the same body; a different body with a used key returns 409 idempotency_conflict. With "dryRun": true nothing is written and no key is needed. For cURL examples, set GOU_IDEMPOTENCY_KEY once for the operation (for example with uuidgen) and keep that value for retries.

Limits (Preview). Lists return 100 items by default (max 200). A single mutation touches at most 250 profiles and is atomic — oversized batches are rejected, never truncated. 60 requests per minute per credential. Idempotent replays are kept for at least 7 days.

Where the endpoints live. Tools v2 is not a separate product surface: its commands are documented inline with each resource — Profiles (one Create profiles — with or without a proxy — and Assign proxies), Profile notes and Folders. Each operation appears once; where classic /v1 covers the same job, the endpoint names its /v1 equivalent in one line.

What Tools v2 is not. It does not launch cloud browsers over CDP (use @goundetected/sdk and the /v1 sessions API), and it has no cookie endpoints — cookie list (JSON) / import / clear are classic /v1 only, see Cookies. Folder permissions are also /v1 only (Folder permissions).

CapabilitiesPreview

Call GET /v2/tools/capabilities first. It tells an agent exactly what this token may do: its organization, scopes, every read and command with allowed / missingScopes, which commands need human confirmation, supported proxy sources, limits, and the organization-isolation policy. Calling it with a new Connect token also marks the connection Verified in Settings → Integrations (Connect).

AreaStatus (Preview)
Folders: list, create, renameAvailable
Folders: deleteRequest only — owner/admin confirms in Activity
Profiles: list, create (multi-group), update, moveAvailable
Profiles: deleteRequest only — owner/admin confirms in Activity
Assign proxies (folder_profiles, inline / raw)Available
Profile notes: read, create, updateAvailable
Profile notes: clearRequest only — owner/admin confirms in Activity
Agent connections: create, list, Edit permissions (PATCH scopes), revokeAvailable in Settings → Integrations (Connect) (owner/admin)
Folders: share, unshare, transferNot enabled — hard denied (see Share & transfer)
Cookies: list (JSON), import, clearNot in Tools v2 — classic /v1 with an API key only (see Cookies)

Get capabilitiesPreview

Returns the token's organization, scopes, reads and commands (each with allowed, requiredScopes, requiresConfirmation), policy (autoExecute, requiresHumanConfirmation, hardDenied), proxySources, limits, sharedFolders, orgIsolation, policies and shareTransfer. Fields are additive; check revision.

GET/v2/tools/capabilities
Request
curl -X GET "$GOU_TOOLS_BASE/v2/tools/capabilities" \
  -H "Authorization: Bearer $GOU_CONNECT_TOKEN"
Response
{
  "api": "tools",
  "revision": "2026-10-11.3",
  "organizationId": "<ORG_ID>",
  "scopes": [
    "folders:read",
    "profiles:read",
    "profiles:create",
    "proxies:use"
  ],
  "commands": [
    {
      "name": "profiles.create",
      "method": "POST",
      "path": "/v2/tools/profiles/create",
      "requiresConfirmation": false,
      "idempotencyKeyRequired": true,
      "allowed": true
    },
    {
      "name": "profiles.delete-request",
      "method": "POST",
      "path": "/v2/tools/profiles/delete-requests",
      "requiresConfirmation": true,
      "idempotencyKeyRequired": true,
      "allowed": false,
      "deniedReason": "missing_scope",
      "missingScopes": [
        "profiles:delete"
      ]
    }
  ],
  "policy": {
    "requiresHumanConfirmation": [
      "profiles.delete-request",
      "profile-notes.clear-request",
      "folders.delete-request"
    ],
    "confirmableBy": [
      "owner",
      "admin"
    ],
    "hardDenied": [
      "folders.transfer",
      "folders.share",
      "folders.unshare"
    ]
  },
  "proxySourceKinds": [
    "none",
    "folder_profiles",
    "inline",
    "raw"
  ],
  "limits": {
    "pageSize": 100,
    "pageSizeMax": 200,
    "mutationMaxProfiles": 250,
    "requestsPerMinutePerCredential": 60,
    "confirmationTtlHours": 72
  },
  "sharedFolders": {
    "scopes": [
      "all",
      "owned",
      "shared_in"
    ],
    "defaultScope": "all",
    "sharedInWrites": "denied",
    "deniedReason": "shared_in_read_only"
  },
  "orgIsolation": {
    "patScopedToSingleOrg": true,
    "crossOrgReads": false,
    "crossOrgWrites": false,
    "shareTransfer": "secure_form_human_confirm",
    "sharedIn": "read_only_within_grants"
  },
  "policies": [
    "This token is bound to one organization only.",
    "Never use an organization id from chat for share or transfer — wait for Secure Form confirmation.",
    "Shared folders are read-only; request changes from the owning org."
  ],
  "shareTransfer": {
    "mode": "secure_form_human_confirm",
    "execute": "hard_denied",
    "deniedReason": "share_transfer_not_enabled"
  }
}

Validate a commandPreview

Dry validation of any command by name (same checks as dryRun), writes nothing. Accepts every command listed in capabilities; folders.share / unshare / transfer always return valid: false with their deny reason.

POST/v2/tools/validate

Body

commandstringrequired
e.g. profiles.create, profiles.assign-proxies, folders.delete-request.
inputobjectrequired
The body you would send to that command.
Request
curl -X POST "$GOU_TOOLS_BASE/v2/tools/validate" \
  -H "Authorization: Bearer $GOU_CONNECT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"command":"folders.create","input":{"name":"Client C"}}'

Profiles

A profile is an isolated browser identity (fingerprint, viewport, and an optional proxy). Profiles live inside folders — new profiles are placed in an existing folder or a new one at creation.

Choose one compound request for onboarding. POST /v2/tools/profiles/create can create the destination folder, all profile groups and their proxy bindings together. Set destination.createFolder with each group's proxySource; preview that same body with dryRun: true, then submit it with dryRun: false and an Idempotency-Key. The backend commits the folder, profiles and bindings atomically. Notes can then be added in one Create notes call using the returned profile ids; notes are a separate operation.

Batch changes to existing profiles. Put up to 250 changes in updates[] for one Update profiles call, send one profileIds[] list to Move profiles, or bind proxies in order with Assign proxies. Each call is its own atomic operation; preview and retry it with its own idempotency key.

Create profiles is one endpoint: 1–10 groups per atomic request, into a new or existing folder. Every group must carry proxySource: { "kind": "none" } for profiles without a proxy, inline (alias raw, your own proxies) or folder_profiles (reuse proxies already bound in another folder). There is no implicit default — a missing or non-object proxySource returns 400 invalid_input (proxySource is required). Groups can differ by OS (macos / windows) and by proxy source. Profiles are auto-named "Profile N" (naming continues across groups) unless you set names.

Always dry-run first. If a folder_profiles group asks for more proxies than are available, the request fails with 409 insufficient_reusable_proxies and nothing is created — no partial batches and no silent proxy cycling. That refusal is recorded and consumes the Idempotency-Key, so retry with a new key after adding proxies.

Inline / raw proxies. One proxy per profile in items (alias proxies): host:port, host:port:user:pass, user:pass@host:port, scheme://user:pass@host:port (http, https, socks5), or { host, port, username?, password?, type?, country?, timezone? }. Bound 1:1 in order — the group count (optional for inline) must equal the number of items. An unreadable item returns 400 invalid_input with its path (e.g. $.groups[0].proxySource.items[1]); error messages never echo the item. Passwords never appear in responses, Activity or stored results — inline sources are echoed as { kind: "inline", count }.

Classic API keys. The endpoints below use a Connect token (Preview). With a classic gou_live_… key, use the /v1 equivalent noted on each endpoint — e.g. POST /v1/profiles/create creates a single profile with { folderId, name?, userAgent?, viewportWidth?, viewportHeight?, proxy? } (the proxy is optional there too).

Assign proxies (existing profiles). Assign or replace proxies on profiles that already exist — either copied from profiles in another folder (folder_profiles) or supplied inline. Targets must not be running (409 profiles_running). Responses return safe references only (proxyRef, protocol, host, port, country, hasAuth) — never logins or passwords.

Scopes: profiles:update + proxies:use for inline; folder_profiles additionally needs proxies:read. A folder_profiles shortage returns 409 insufficient_reusable_proxies with nothing changed.

Create profilesPreview

Creates 1..N groups of profiles atomically; each group sets proxySource explicitly: none | inline (alias raw) | folder_profiles — missing → 400 invalid_input. Extra scopes by option: destination.createFolder → folders:create; proxySource inline → proxies:use; proxySource folder_profiles → proxies:read + proxies:use. Example: a new folder with 2 macOS profiles without a proxy and 2 Windows profiles, each on its own inline proxy. API key (/v1): POST /v1/profiles/create — one profile per call, optional proxy.

POST/v2/tools/profiles/create

Body

destinationobjectrequired
{ folderId } (existing folder) or { createFolder: { name } }.
groups[]arrayrequired
1–10 groups.
groups[].countinteger
Profiles in the group. Required for none / folder_profiles; optional for inline (defaults to items.length; must match if given).
groups[].osstring
macos | windows. Overrides request-level os; omit for random per profile.
groups[].proxySourceobjectrequired
Required on every group. { kind: "none" } — profiles without a proxy (send it explicitly; a missing or non-object proxySource → 400 invalid_input "proxySource is required"). { kind: "inline" } (alias "raw") with items (alias proxies) — one proxy per profile, in order. { kind: "folder_profiles", folderId, country? } — copy bindings from profiles already in a folder you can view (each binding used at most once per request).
proxyAssignmentobject
folder_profiles only: { mode: "copy_binding", reuseWithExistingProfiles?, reuseWithinBatch: false }.
dryRunboolean
Preview only: same scope, access and validation checks, nothing written, no Activity entry, no Idempotency-Key needed. Returns 200 with operationId: null.
Request
curl -X POST "$GOU_TOOLS_BASE/v2/tools/profiles/create" \
  -H "Authorization: Bearer $GOU_CONNECT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"dryRun":true,"destination":{"createFolder":{"name":"Client A — onboarding"}},"groups":[{"os":"macos","count":2,"proxySource":{"kind":"none"}},{"os":"windows","proxySource":{"kind":"inline","items":["203.0.113.10:8000:user1:pass1","user2:pass2@203.0.113.11:8000"]}}]}'
Response
{
  "dryRun": true,
  "operationId": null,
  "folder": {
    "id": null,
    "name": "Client A — onboarding"
  },
  "groups": [
    {
      "os": "macos",
      "proxySource": {
        "kind": "none"
      },
      "names": [
        "Profile 1",
        "Profile 2"
      ]
    },
    {
      "os": "windows",
      "proxySource": {
        "kind": "inline",
        "count": 2
      },
      "names": [
        "Profile 3",
        "Profile 4"
      ]
    }
  ]
}

List profilesPreview

Lists profiles the token can see, including read-only profiles in folders shared into your organization. Add include=proxyBinding for safe proxy refs (never credentials). API key (/v1): GET /v1/profiles?limit=&offset= (limit 1–200, default 50).

GET/v2/tools/profiles

Query parameters

folderIduuid
Only profiles in this folder.
scopestring
all (default) | owned | shared_in.
includestring
proxyBinding — add a masked proxy reference (never credentials); also needs proxies:read.
cursorstring
nextCursor from the previous page.
limitinteger
Default 100, max 200.
Request
curl -X GET "$GOU_TOOLS_BASE/v2/tools/profiles?limit=20" \
  -H "Authorization: Bearer $GOU_CONNECT_TOKEN"

Get a profile

Returns a single profile, including its nested proxy. 404 if the profile isn't in your organization. API key only — with a Connect token, use List profiles (filter by folderId).

GET/v1/profiles/{id} Try it

Sends a live request to https://api.goundetected.io. Changes apply to your organization.

Shared between panels until refresh. Never saved to browser storage.

Path parameters

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

Update profilesPreview

Renames profiles or changes their User-Agent (only name and userAgent are accepted — no implicit move, delete or proxy change). Up to 250 per call, atomic. Changing userAgent on a running profile → 409 profiles_running. API key (/v1): PATCH /v1/profiles/{id} (name, userAgent, viewport, proxy).

POST/v2/tools/profiles/update

Body

updates[]arrayrequired
{ profileId, name?, userAgent? }.
dryRunboolean
Preview only: same scope, access and validation checks, nothing written, no Activity entry, no Idempotency-Key needed. Returns 200 with operationId: null.
Request
curl -X POST "$GOU_TOOLS_BASE/v2/tools/profiles/update" \
  -H "Authorization: Bearer $GOU_CONNECT_TOKEN" \
  -H "Idempotency-Key: ${GOU_IDEMPOTENCY_KEY:?Set one UUID per operation; reuse it for retries}" \
  -H "Content-Type: application/json" \
  -d '{"updates":[{"profileId":"8bd9a9fe-dfcd-4abc-9719-652967b60f28","name":"Client A — 1"},{"profileId":"0003b217-d1a0-42a7-9cad-53967ccef518","name":"Client A — 2"}]}'

Move profilesPreview

Atomically moves profiles into a target folder (you need edit access on every source folder and the target). Returns movedProfileIds, unchangedProfileIds and counts.

POST/v2/tools/profiles/move

Body

profileIdsuuid[]required
Profiles to move.
targetFolderIduuidrequired
Destination folder.
dryRunboolean
Preview only: same scope, access and validation checks, nothing written, no Activity entry, no Idempotency-Key needed. Returns 200 with operationId: null.
Request
curl -X POST "$GOU_TOOLS_BASE/v2/tools/profiles/move" \
  -H "Authorization: Bearer $GOU_CONNECT_TOKEN" \
  -H "Idempotency-Key: ${GOU_IDEMPOTENCY_KEY:?Set one UUID per operation; reuse it for retries}" \
  -H "Content-Type: application/json" \
  -d '{"profileIds":["8bd9a9fe-dfcd-4abc-9719-652967b60f28"],"targetFolderId":"d97d8b61-6a47-4405-a962-8206e8bd7418"}'

Assign proxiesPreview

Assigns or replaces proxies on existing profiles, one per profile, 1:1 in order. For inline, profileIds.length must equal the number of items. To add a proxy at creation, set proxySource on Create profiles instead. API key (/v1): PATCH /v1/profiles/{id} { proxy } — one profile per call.

POST/v2/tools/profiles/assign-proxies

Body

profileIdsuuid[]required
Target profiles (edit access, not running).
proxySourceobjectrequired
{ kind: "inline" | "raw", items } or { kind: "folder_profiles", folderId, country? }.
dryRunboolean
Preview only: same scope, access and validation checks, nothing written, no Activity entry, no Idempotency-Key needed. Returns 200 with operationId: null.
Request
curl -X POST "$GOU_TOOLS_BASE/v2/tools/profiles/assign-proxies" \
  -H "Authorization: Bearer $GOU_CONNECT_TOKEN" \
  -H "Idempotency-Key: ${GOU_IDEMPOTENCY_KEY:?Set one UUID per operation; reuse it for retries}" \
  -H "Content-Type: application/json" \
  -d '{"profileIds":["8bd9a9fe-dfcd-4abc-9719-652967b60f28","0003b217-d1a0-42a7-9cad-53967ccef518"],"proxySource":{"kind":"inline","items":["user:pass@proxy.example.com:3128",{"host":"203.0.113.20","port":1080,"type":"socks5","username":"u","password":"p","country":"DE"}]}}'
Response
{
  "assignments": [
    {
      "profileId": "8bd9a9fe-dfcd-4abc-9719-652967b60f28",
      "sourceProfileId": null,
      "proxy": {
        "protocol": "http",
        "host": "proxy.example.com",
        "port": "3128",
        "country": null,
        "hasAuth": true
      }
    },
    {
      "profileId": "0003b217-d1a0-42a7-9cad-53967ccef518",
      "sourceProfileId": null,
      "proxy": {
        "protocol": "socks5",
        "host": "203.0.113.20",
        "port": "1080",
        "country": "DE",
        "hasAuth": true
      }
    }
  ],
  "counts": {
    "assigned": 2
  }
}

Delete profilesPreview

Proposal only — nothing is deleted by the token. Returns 202 with state awaiting_confirmation and a proposal (digest, targets, summary, expiresAt +72 h, reviewPath). An owner or admin confirms or declines it in Activity. API key (/v1): DELETE /v1/profiles/{id} deletes one profile immediately (204).

POST/v2/tools/profiles/delete-requests

Body

profileIdsuuid[]required
Profiles to delete.
dryRunboolean
Preview only: same scope, access and validation checks, nothing written, no Activity entry, no Idempotency-Key needed. Returns 200 with operationId: null.
Request
curl -X POST "$GOU_TOOLS_BASE/v2/tools/profiles/delete-requests" \
  -H "Authorization: Bearer $GOU_CONNECT_TOKEN" \
  -H "Idempotency-Key: ${GOU_IDEMPOTENCY_KEY:?Set one UUID per operation; reuse it for retries}" \
  -H "Content-Type: application/json" \
  -d '{"profileIds":["8bd9a9fe-dfcd-4abc-9719-652967b60f28"]}'
Response
{
  "state": "awaiting_confirmation",
  "operationId": "3f2a9c1e-7b4d-4e8a-9c21-5d6e7f8a9b0c",
  "proposal": {
    "digest": "sha256:…",
    "expiresAt": "2026-10-14T16:02:00.000Z",
    "reviewPath": "/settings?tab=activity&operation=3f2a9c1e-7b4d-4e8a-9c21-5d6e7f8a9b0c"
  }
}

Profile notesPreview

Read, create and update notes on profiles — up to 250 per call, atomic, max 10,000 characters per note. An empty note is refused (clearing is a separate, confirmed action). Note bodies are never written to Activity — only note ids and lengths.

List a profile's notesPreview

Returns the notes of one profile.

GET/v2/tools/profiles/{id}/notes

Path parameters

iduuidrequired
Profile id.
Request
curl -X GET "$GOU_TOOLS_BASE/v2/tools/profiles/:id/notes" \
  -H "Authorization: Bearer $GOU_CONNECT_TOKEN"

Create notesPreview

Adds a note to one profile ({ profileId, note }) or to many ({ items: [{ profileId, note }] }).

POST/v2/tools/profile-notes/create

Body

items[]array
{ profileId, note } — or send profileId + note at the top level for a single note.
dryRunboolean
Preview only: same scope, access and validation checks, nothing written, no Activity entry, no Idempotency-Key needed. Returns 200 with operationId: null.
Request
curl -X POST "$GOU_TOOLS_BASE/v2/tools/profile-notes/create" \
  -H "Authorization: Bearer $GOU_CONNECT_TOKEN" \
  -H "Idempotency-Key: ${GOU_IDEMPOTENCY_KEY:?Set one UUID per operation; reuse it for retries}" \
  -H "Content-Type: application/json" \
  -d '{"items":[{"profileId":"8bd9a9fe-dfcd-4abc-9719-652967b60f28","note":"Client A — warm-up week 1"}]}'

Update notesPreview

Replaces the text of existing notes (single or items[]); each note must belong to the given profile.

POST/v2/tools/profile-notes/update

Body

items[]array
{ profileId, noteId, note } — or send them at the top level for a single note.
dryRunboolean
Preview only: same scope, access and validation checks, nothing written, no Activity entry, no Idempotency-Key needed. Returns 200 with operationId: null.
Request
curl -X POST "$GOU_TOOLS_BASE/v2/tools/profile-notes/update" \
  -H "Authorization: Bearer $GOU_CONNECT_TOKEN" \
  -H "Idempotency-Key: ${GOU_IDEMPOTENCY_KEY:?Set one UUID per operation; reuse it for retries}" \
  -H "Content-Type: application/json" \
  -d '{"items":[{"profileId":"8bd9a9fe-dfcd-4abc-9719-652967b60f28","noteId":1042,"note":"Client A — warm-up week 2"}]}'

Request notes clearingPreview

Proposal only (202 awaiting_confirmation). An owner or admin confirms in Activity; only the exact note ids in the proposal are cleared.

POST/v2/tools/profile-notes/clear-requests

Body

profileIdsuuid[]required
Profiles whose notes to clear.
dryRunboolean
Preview only: same scope, access and validation checks, nothing written, no Activity entry, no Idempotency-Key needed. Returns 200 with operationId: null.
Request
curl -X POST "$GOU_TOOLS_BASE/v2/tools/profile-notes/clear-requests" \
  -H "Authorization: Bearer $GOU_CONNECT_TOKEN" \
  -H "Idempotency-Key: ${GOU_IDEMPOTENCY_KEY:?Set one UUID per operation; reuse it for retries}" \
  -H "Content-Type: application/json" \
  -d '{"profileIds":["8bd9a9fe-dfcd-4abc-9719-652967b60f28"]}'

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.

Export: GET /v1/profiles/{id}/cookies returns the profile's cookies as JSON (data[]) in the same shape POST accepts — save that array to move cookies to another profile or tool.

Classic API key (`/v1`) only. Tools v2 / Connect has no cookie endpoints (no list, import, export or clear), and Connect tokens are not accepted here — use an API key with profiles:read / profiles:write. The SDK wraps these routes (gg.profiles cookies helpers).

List / export cookies (JSON)

Returns every cookie stored in the profile as JSON — the same shape Import cookies accepts, so this is also the export. API key (/v1) only; not available in Tools v2.

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

Sends a live request to https://api.goundetected.io. Changes apply to your organization.

Shared between panels until refresh. Never saved to browser storage.

Path parameters

iduuidrequired
Profile 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

Sends a live request to https://api.goundetected.io. Changes apply to your organization.

Shared between panels until refresh. Never saved to browser storage.

Path parameters

iduuidrequired
Profile id.

Query parameters

modestring
merge (default) or replace.

Body

domainstringrequired
e.g. .linkedin.com (leading dot = include subdomains).
namestringrequired
Cookie name.
valuestringrequired
Cookie value (stored in plaintext).
pathstring
Default /.
secureboolean
Default false.
httpOnlyboolean
Default false.
hostOnlyboolean
Default inferred from domain (leading dot = not host-only).
sessionboolean
Default true when expirationDate is omitted.
expirationDatenumber
Expiry as unix seconds. Omit for a session cookie.
sameSitestring
no_restriction | lax | strict | unspecified.
Request
curl -X POST "https://api.goundetected.io/v1/profiles/:id/cookies?mode=merge" \
  -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

Sends a live request to https://api.goundetected.io. Changes apply to your organization.

Shared between panels until refresh. Never saved to browser storage.

Path parameters

iduuidrequired
Profile 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 unless you choose otherwise.

With a Connect token, folder lists include folders shared into your organization by default: every item carries folderType (owned | shared_in), sharedFrom for shared-in folders, and your access (view / run / edit / full). Filter with scope=all (default) | owned | shared_in.

Shared-in folders are read-only. Any write that names a shared-in folder or a profile inside one — create into, use as a proxy source, move in or out, assign proxies, notes, rename, delete requests (dry-run included) — returns 403 permission_denied with details.reason: "shared_in_read_only". Request changes from the owning organization.

Member tokens act with the issuing member's folder permissions (see Folder permissions); owners and admins see every folder in the organization.

List foldersPreview

Lists owned folders and folders shared into your organization (read-only). Paginated with nextCursor. API key (/v1): GET /v1/folders (your organization's folders only).

GET/v2/tools/folders

Query parameters

scopestring
all (default) | owned | shared_in.
limitinteger
Default 100, max 200.
cursorstring
nextCursor from the previous page.
Request
curl -X GET "$GOU_TOOLS_BASE/v2/tools/folders?limit=20" \
  -H "Authorization: Bearer $GOU_CONNECT_TOKEN"
Response
{
  "items": [
    {
      "id": "d97d8b61-6a47-4405-a962-8206e8bd7418",
      "name": "Client A — LinkedIn",
      "folderType": "owned",
      "isSharedOut": false,
      "access": {
        "view": true,
        "run": true,
        "edit": true,
        "full": true
      }
    },
    {
      "id": "74c8298c-9276-4187-a8c6-bc72c84e2662",
      "name": "Partner pool",
      "folderType": "shared_in",
      "sharedFrom": {
        "organizationId": "…",
        "organizationName": "Partner Agency"
      },
      "access": {
        "view": true,
        "run": true,
        "edit": false,
        "full": false
      }
    }
  ]
}

Create a folderPreview

Creates a folder at the top of the list. Owners/admins, or members allowed to create folders. To create a folder together with profiles, use destination.createFolder on Create profiles. API key (/v1): POST /v1/folders/create { name }.

POST/v2/tools/folders/create

Body

namestringrequired
Folder name.
dryRunboolean
Preview only: same scope, access and validation checks, nothing written, no Activity entry, no Idempotency-Key needed. Returns 200 with operationId: null.
Request
curl -X POST "$GOU_TOOLS_BASE/v2/tools/folders/create" \
  -H "Authorization: Bearer $GOU_CONNECT_TOKEN" \
  -H "Idempotency-Key: ${GOU_IDEMPOTENCY_KEY:?Set one UUID per operation; reuse it for retries}" \
  -H "Content-Type: application/json" \
  -d '{"name":"Client B — onboarding"}'

Get a folder

Returns a single folder. 404 if not in your organization. API key only — with a Connect token, use List folders.

GET/v1/folders/{id} Try it

Sends a live request to https://api.goundetected.io. Changes apply to your organization.

Shared between panels until refresh. Never saved to browser storage.

Path parameters

iduuidrequired
Folder id.
Request
curl -X GET "https://api.goundetected.io/v1/folders/:id" \
  -H "Authorization: Bearer $GOU_API_KEY"

Rename a folderPreview

Renames a folder you can edit. Same name → no change. API key (/v1): PATCH /v1/folders/{id} { name }.

POST/v2/tools/folders/rename

Body

folderIduuidrequired
Folder id.
namestringrequired
New name.
dryRunboolean
Preview only: same scope, access and validation checks, nothing written, no Activity entry, no Idempotency-Key needed. Returns 200 with operationId: null.
Request
curl -X POST "$GOU_TOOLS_BASE/v2/tools/folders/rename" \
  -H "Authorization: Bearer $GOU_CONNECT_TOKEN" \
  -H "Idempotency-Key: ${GOU_IDEMPOTENCY_KEY:?Set one UUID per operation; reuse it for retries}" \
  -H "Content-Type: application/json" \
  -d '{"folderId":"d97d8b61-6a47-4405-a962-8206e8bd7418","name":"Client A — archived"}'

Delete a folderPreview

Proposal only — returns 202 awaiting_confirmation; the folder is deleted only after an owner or admin confirms in Activity. contents is required: delete_profiles (also needs profiles:delete) or move_profiles to targetFolderId (also needs profiles:move). Requires full access to the folder. Running profiles are listed as warnings. API key (/v1): DELETE /v1/folders/{id} deletes immediately (204; cascades assignments + permissions, profiles are kept).

POST/v2/tools/folders/delete-requests

Body

folderIduuidrequired
Folder to delete.
contentsobjectrequired
{ mode: "delete_profiles" } or { mode: "move_profiles", targetFolderId }.
dryRunboolean
Preview only: same scope, access and validation checks, nothing written, no Activity entry, no Idempotency-Key needed. Returns 200 with operationId: null.
Request
curl -X POST "$GOU_TOOLS_BASE/v2/tools/folders/delete-requests" \
  -H "Authorization: Bearer $GOU_CONNECT_TOKEN" \
  -H "Idempotency-Key: ${GOU_IDEMPOTENCY_KEY:?Set one UUID per operation; reuse it for retries}" \
  -H "Content-Type: application/json" \
  -d '{"folderId":"d97d8b61-6a47-4405-a962-8206e8bd7418","contents":{"mode":"move_profiles","targetFolderId":"74c8298c-9276-4187-a8c6-bc72c84e2662"}}'

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

Sends a live request to https://api.goundetected.io. Changes apply to your organization.

Shared between panels until refresh. Never saved to browser storage.

Path parameters

iduuidrequired
Folder 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

Sends a live request to https://api.goundetected.io. Changes apply to your organization.

Shared between panels until refresh. Never saved to browser storage.

Path parameters

iduuidrequired
Folder id.

Body

emailstring
Member's email (or use organizationMemberId).
organizationMemberIduuid
Member id (or use email).
canViewboolean
Default false.
canRunboolean
Default false.
canEditboolean
Default false.
fullAccessboolean
Implies 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

Sends a live request to https://api.goundetected.io. Changes apply to your organization.

Shared between panels until refresh. Never saved to browser storage.

Path parameters

iduuidrequired
Folder id.
memberIduuidrequired
organization_members.id.
Request
curl -X DELETE "https://api.goundetected.io/v1/folders/:id/permissions/:memberId" \
  -H "Authorization: Bearer $GOU_API_KEY"

Confirmations (Activity)Preview

Destructive Tools commands — delete profiles, clear notes, delete folders — never execute on the token alone. They return 202 with state: "awaiting_confirmation", a proposal.digest and a reviewPath. An organization owner or admin reviews it in the desktop app under Settings → Activity and clicks Confirm or Decline. A Connect token cannot confirm its own proposals.

Confirmation re-checks the targets: if they changed since the proposal (different digest), the operation fails with 409 source_changed and nothing is executed; running profiles return 409 profiles_running and the proposal stays pending. Proposals expire after 72 hours (410 confirmation_expired).

Preview note: approval execution ships with the Tools v2 release. Until then a confirm may answer 503 unavailable and the proposal stays pending — nothing is deleted.

Track any operation with GET /v2/tools/operations/{id} — state is one of awaiting_confirmation, queued, running, succeeded, failed, declined, expired; decided operations include decidedAt and decidedByUserId.

Get an operationPreview

Returns kind, state, result, error and confirmation details for an operation of your organization.

GET/v2/tools/operations/{id}

Path parameters

iduuidrequired
operationId from a command response.
Request
curl -X GET "$GOU_TOOLS_BASE/v2/tools/operations/:id" \
  -H "Authorization: Bearer $GOU_CONNECT_TOKEN"

Agent connectionsPreview

Agent connections are the Connect tokens listed under Settings → Integrations (Connect). Only organization owners and admins can create, edit or revoke them, and only from a signed-in desktop session — a Connect token or API key can never issue, edit or revoke tokens (including itself). The routes below are what the desktop app calls; they are documented for transparency.

Create: name, permissions (all selected by default) and expiry (30 / 90 default / 180 / 365 days, or Never). The token is returned once. Up to 25 active connections per organization. Revoke takes effect on the next request; the row stays for audit.

Edit permissions replaces the connection's scopes. The token string does not change and is never returned again; new scopes apply on the token's next request.

List agent connectionsPreview

Newest first, including revoked and expired. Metadata only — tokenHint is a non-secret prefix; the token itself is never listed.

GET/v1/workspace/organizations/{orgId}/agent-connections

Path parameters

orgIduuidrequired
Current organization.
Request
curl -X GET "$GOU_TOOLS_BASE/v1/workspace/organizations/:orgId/agent-connections" \
  -H "Authorization: Bearer $OWNER_DESKTOP_SESSION"
Response
{
  "items": [
    {
      "id": "5b0c…",
      "name": "Grok Bot",
      "status": "verified",
      "scopes": [
        "folders:read",
        "profiles:read",
        "profiles:create",
        "proxies:use"
      ],
      "audience": "tools_v2",
      "tokenHint": "groot_pat_aB3dE9…",
      "createdAt": "2026-10-11T15:00:00.000Z",
      "expiresAt": null,
      "lastUsedAt": "2026-10-11T17:20:00.000Z",
      "verifiedAt": "2026-10-11T15:01:12.000Z",
      "revokedAt": null
    }
  ]
}

Edit permissions (PATCH scopes)Preview

Full replace of the connection's scopes. scopes is the only accepted key and must be non-empty. Same set → 200 with no change. Errors: 400 invalid_input (empty, unknown scope → details.path scopes[i]), 403 permission_denied (not owner/admin — checked before the body), 404 not_found, 409 invalid_state (connection_revoked | connection_expired → connect a new bot instead).

PATCH/v1/workspace/organizations/{orgId}/agent-connections/{id}

Path parameters

orgIduuidrequired
Current organization.
iduuidrequired
Connection id.

Body

scopesstring[]required
Complete new scope list (see Connect scopes).
Request
curl -X PATCH "$GOU_TOOLS_BASE/v1/workspace/organizations/:orgId/agent-connections/:id" \
  -H "Authorization: Bearer $OWNER_DESKTOP_SESSION" \
  -H "Content-Type: application/json" \
  -d '{"scopes":["folders:read","profiles:read","profiles:create","proxies:use"]}'
Response
{
  "connection": {
    "id": "5b0c…",
    "name": "Grok Bot",
    "status": "verified",
    "scopes": [
      "folders:read",
      "profiles:read",
      "profiles:create",
      "proxies:use"
    ],
    "tokenHint": "groot_pat_aB3dE9…"
  }
}

Revoke a connectionPreview

Soft revoke; idempotent. Returns 200 { connection, alreadyRevoked }.

DELETE/v1/workspace/organizations/{orgId}/agent-connections/{id}

Path parameters

orgIduuidrequired
Current organization.
iduuidrequired
Connection id.
Request
curl -X DELETE "$GOU_TOOLS_BASE/v1/workspace/organizations/:orgId/agent-connections/:id" \
  -H "Authorization: Bearer $OWNER_DESKTOP_SESSION"

Organization isolation · Share & transferPreview

One token = one organization. Every API key and Connect token is issued in exactly one organization. Classic API keys read or change only that organization's data. Connect tokens can also read folders shared into it within their grants; shared-in writes are denied. There is no "switch organization" with the same token: to work in another organization, create a connection there.

GET /v2/tools/capabilities reports this as orgIsolation (patScopedToSingleOrg: true, crossOrgReads: false, crossOrgWrites: false, shareTransfer: "secure_form_human_confirm", sharedIn: "read_only_within_grants") plus plain-language policies an agent should follow.

Share & transfer — not enabled. Sharing, unsharing and transferring folders between organizations are hard denied for Connect tokens today. The endpoints exist only so agents get a clear, safe answer; they never write anything (no operation, no Activity entry).

Never from chat. A target organization id is accepted only together with a confirmation from the desktop Secure Form, where an owner or admin picks the target organization and confirms. An organization id typed into a chat or prompt is never enough.

RequestResult
targetOrganizationId without a Secure Form confirmation400 invalid_input, details.reason: "org_id_requires_secure_form"
Target is your own organization400 invalid_input, details.reason: "same_organization"
Anything else (incl. dryRun or a Secure Form token)403 permission_denied, details.reason: "share_transfer_not_enabled", hardDenied: true

Paths (stub): POST /v2/tools/folders/share-requests, /unshare-requests, /transfer-requests. GET /v2/tools/capabilities reports shareTransfer.execute: "hard_denied" and lists these commands under policy.hardDenied.

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.

Errors

Both surfaces return one error envelope with the right HTTP status. Tools v2 adds a retryable flag and optional details (e.g. path, reason, operationId):

{ "error": { "code": "invalid_input", "message": "…", "retryable": false, "details": { "path": "$.groups[0].proxySource" } } }
StatusMeaningTools v2 codes (Preview)
200Success, or any Tools dryRun preview.—
202Proposal created, awaiting owner/admin confirmation in Activity.awaiting_confirmation
400Validation failed or bad reference (e.g. folder not in your org).invalid_input (+details.path), idempotency_key_required, org_id_requires_secure_form
401Missing, invalid, expired or revoked API key / Connect token.—
403Missing scope or no access.missing_scope, permission_denied (shared_in_read_only, hard-denied share/transfer)
404Not found in your organization (Tools: or Tools v2 not enabled on this server).not_found
409Conflict — e.g. editing cookies or proxies while the profile is running.idempotency_conflict, insufficient_reusable_proxies, source_changed, profiles_running, invalid_state
410Proposal expired.confirmation_expired
429Rate limited (Tools: 60 req/min per credential).—
503Feature not enabled on this server.unavailable