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.
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_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.
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):
| Group | Scopes |
|---|---|
| Folders | folders:read, folders:create, folders:update, folders:delete |
| Profiles | profiles:read, profiles:create, profiles:update, profiles:move, profiles:delete |
| Proxies | proxies:read, proxies:use |
| Profile notes | profile-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).
| Area | Status (Preview) |
|---|---|
| Folders: list, create, rename | Available |
| Folders: delete | Request only — owner/admin confirms in Activity |
| Profiles: list, create (multi-group), update, move | Available |
| Profiles: delete | Request only — owner/admin confirms in Activity |
Assign proxies (folder_profiles, inline / raw) | Available |
| Profile notes: read, create, update | Available |
| Profile notes: clear | Request only — owner/admin confirms in Activity |
| Agent connections: create, list, Edit permissions (PATCH scopes), revoke | Available in Settings → Integrations (Connect) (owner/admin) |
| Folders: share, unshare, transfer | Not enabled — hard denied (see Share & transfer) |
| Cookies: list (JSON), import, clear | Not 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.
/v2/tools/capabilitiescurl -X GET "$GOU_TOOLS_BASE/v2/tools/capabilities" \
-H "Authorization: Bearer $GOU_CONNECT_TOKEN"{
"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.
/v2/tools/validateBody
- commandstringrequired
- e.g. profiles.create, profiles.assign-proxies, folders.delete-request.
- inputobjectrequired
- The body you would send to that command.
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.
/v2/tools/profiles/createprofiles:createBody
- 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.
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"]}}]}'{
"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).
/v2/tools/profilesprofiles:readQuery 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.
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}profiles:read Try it
Sends a live request to https://api.goundetected.io. Changes apply to your organization.
Path parameters
- iduuidrequired
- Profile id.
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).
/v2/tools/profiles/updateprofiles:updateBody
- 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.
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.
/v2/tools/profiles/moveprofiles:moveBody
- 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.
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.
/v2/tools/profiles/assign-proxiesprofiles:update + proxies:useBody
- 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.
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"}]}}'{
"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).
/v2/tools/profiles/delete-requestsprofiles:deleteBody
- 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.
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"]}'{
"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.
/v2/tools/profiles/{id}/notesprofile-notes:readPath parameters
- iduuidrequired
- Profile id.
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 }] }).
/v2/tools/profile-notes/createprofile-notes:writeBody
- 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.
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.
/v2/tools/profile-notes/updateprofile-notes:writeBody
- 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.
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.
/v2/tools/profile-notes/clear-requestsprofile-notes:deleteBody
- 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.
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"]}'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).
/v2/tools/foldersfolders:readQuery parameters
- scopestring
- all (default) | owned | shared_in.
- limitinteger
- Default 100, max 200.
- cursorstring
- nextCursor from the previous page.
curl -X GET "$GOU_TOOLS_BASE/v2/tools/folders?limit=20" \
-H "Authorization: Bearer $GOU_CONNECT_TOKEN"{
"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 }.
/v2/tools/folders/createfolders:createBody
- 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.
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}folders:read Try it
Sends a live request to https://api.goundetected.io. Changes apply to your organization.
Path parameters
- iduuidrequired
- Folder id.
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 }.
/v2/tools/folders/renamefolders:updateBody
- 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.
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).
/v2/tools/folders/delete-requestsfolders:deleteBody
- 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.
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}/permissionsfolders:read Try it
Sends a live request to https://api.goundetected.io. Changes apply to your organization.
Path parameters
- iduuidrequired
- 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
Sends a live request to https://api.goundetected.io. Changes apply to your organization.
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.
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
Sends a live request to https://api.goundetected.io. Changes apply to your organization.
Path parameters
- iduuidrequired
- Folder id.
- memberIduuidrequired
- organization_members.id.
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.
/v2/tools/operations/{id}Path parameters
- iduuidrequired
- operationId from a command response.
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.
/v1/workspace/organizations/{orgId}/agent-connectionsPath parameters
- orgIduuidrequired
- Current organization.
curl -X GET "$GOU_TOOLS_BASE/v1/workspace/organizations/:orgId/agent-connections" \
-H "Authorization: Bearer $OWNER_DESKTOP_SESSION"{
"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).
/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).
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"]}'{
"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 }.
/v1/workspace/organizations/{orgId}/agent-connections/{id}Path parameters
- orgIduuidrequired
- Current organization.
- iduuidrequired
- Connection id.
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.
| Request | Result |
|---|---|
targetOrganizationId without a Secure Form confirmation | 400 invalid_input, details.reason: "org_id_requires_secure_form" |
| Target is your own organization | 400 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-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.
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" } } }| Status | Meaning | Tools v2 codes (Preview) |
|---|---|---|
| 200 | Success, or any Tools dryRun preview. | — |
| 202 | Proposal created, awaiting owner/admin confirmation in Activity. | awaiting_confirmation |
| 400 | Validation failed or bad reference (e.g. folder not in your org). | invalid_input (+details.path), idempotency_key_required, org_id_requires_secure_form |
| 401 | Missing, invalid, expired or revoked API key / Connect token. | — |
| 403 | Missing scope or no access. | missing_scope, permission_denied (shared_in_read_only, hard-denied share/transfer) |
| 404 | Not found in your organization (Tools: or Tools v2 not enabled on this server). | not_found |
| 409 | Conflict — e.g. editing cookies or proxies while the profile is running. | idempotency_conflict, insufficient_reusable_proxies, source_changed, profiles_running, invalid_state |
| 410 | Proposal expired. | confirmation_expired |
| 429 | Rate limited (Tools: 60 req/min per credential). | — |
| 503 | Feature not enabled on this server. | unavailable |
