> ## Documentation Index
> Fetch the complete documentation index at: https://docs.uplink.build/llms.txt
> Use this file to discover all available pages before exploring further.

# Client

> API reference for the Uplink Client class

The `Client` class is your main entry point for Uplink automation. It manages the WebSocket connection to the Uplink relay server and provides methods for worker and browser management.

<Info>
  **Key concepts:**

  * **Device**: A physical iOS or Android device
  * **Worker**: A worker created using the native Uplink SDK (a device can create multiple workers)
  * **Address**: A hex-encoded identifier for workers
</Info>

## Connection

### `uplink.session()`

Creates an Uplink session using your project credentials.

```typescript theme={null}
uplink.session(
  auth: Auth,
  options?: SessionOptions
): Promise<Session>
```

**Parameters:**

* `auth`: Your OAuth client credentials from Uplink Console — `{ clientId, clientSecret }`. The session is automatically scoped to the project the client belongs to. See [Authentication](/fundamentals/authentication), or the [Auth](#auth) type below for the accepted shapes.
* `options` (optional): Session configuration
  * `include`: Cryptographic key options
    * `ecdsa`: Boolean, include ECDSA keys (default: false)
    * `ecdh`: Boolean, include ECDH keys (default: false)
  * `restrict`: `{ verifier: string }` — a secret value that binds the session to your code, so that holding the session's URL is not enough to drive it. Sessions are restricted either way: if you omit this, a verifier is generated for you. Supply your own only when you need to reproduce it in another runtime. See [Session restriction](/sessions#session-restriction).

**Returns:** `Promise<Session>` - A [Session](#session), with the shape `{ sessionId, sessionUrl, qrUrl, credential, keys? }`. `credential` is the verifier — generated here if you did not supply one. Pass the object directly to [`uplink.client.fromSession()`](#uplinkclientfromsession) to connect.

**Example:**

```typescript theme={null}
const session = await uplink.session(
  {
    clientId: process.env.UPLINK_CLIENT_ID,
    clientSecret: process.env.UPLINK_CLIENT_SECRET
  },
  {
    include: { ecdsa: true, ecdh: true }
  }
)

const client = await uplink.client.fromSession(session)
```

**Example — choosing your own verifier:**

```typescript theme={null}
const session = await uplink.session(
  {
    clientId: process.env.UPLINK_CLIENT_ID,
    clientSecret: process.env.UPLINK_CLIENT_SECRET
  },
  {
    include: { ecdsa: true, ecdh: true },
    restrict: { verifier: process.env.UPLINK_SESSION_VERIFIER }
  }
)
```

<Note>
  Uplink stores only a challenge derived from the verifier, never the verifier itself. It cannot be recovered from the API — so if you plan to reconnect from a different runtime, either persist `session.credential` or supply a verifier you can read again.
</Note>

### `uplink.getSession()`

Looks up an existing session by ID and returns the same `Session` shape that `uplink.session()` returns — including a fresh `sessionUrl`. Useful when you already have a `sessionId` (for example, persisted across runs) and want to keep using the original session instead of creating a new one, or recover the keys that were generated when it was created.

```typescript theme={null}
uplink.getSession(
  auth: Auth,
  sessionId: string,
  options?: GetSessionOptions
): Promise<Session>
```

**Parameters:**

* `auth`: Your OAuth client credentials from Uplink Console — `{ clientId, clientSecret }`
* `sessionId`: The ID of an existing session belonging to the client's project
* `options` (optional):
  * `include`: Cryptographic key options
    * `ecdsa`: Boolean, include the project ECDSA key pair (default: false)
    * `ecdh`: Boolean, include the session ECDH key pair if one was created at session-create time (default: false)

**Returns:** `Promise<Session>` - Same shape as `uplink.session()`, but **never** with a `credential`. Uplink does not store the verifier, so it cannot return one. Merge yours back in before passing the session to `fromSession()`.

<Note>
  You only need to worry about merging `credential` when you're picking a session up later through `getSession()`. If you call `fromSession()` directly with the object returned by `uplink.session()`, the credential is already attached for you. See [Reconnecting from another runtime](/sessions#reconnecting-from-another-runtime).
</Note>

<Note>
  The `include.ecdh` / `include.ecdsa` flags only re-export keys that **Uplink generated** for the session. If you brought your own key pair instead, `getSession()` won't return anything in `keys` — you're expected to re-attach your own.
</Note>

**Example:**

```typescript theme={null}
import uplink from '@uplink-code/uplink'

// The verifier for this session: either the `credential` you persisted when you
// created it, or the one you passed as `restrict.verifier` at create time.
const credential = process.env.UPLINK_SESSION_VERIFIER

const session = await uplink.getSession(
  {
    clientId: process.env.UPLINK_CLIENT_ID,
    clientSecret: process.env.UPLINK_CLIENT_SECRET
  },
  '<existing-session-id>',
  { include: { ecdsa: true, ecdh: true } }
)

const client = await uplink.client.fromSession({ ...session, credential })
```

<Note>
  Returns `404` if the session does not exist or does not belong to the client's project.
</Note>

### `uplink.sessionDetails()`

Fetches a richer view of an existing session for display in dashboards or admin views — including paired devices, total bytes streamed, total connection duration, tags, and the parent project and organization. Use this when you need to inspect a session, not to reconnect to it.

```typescript theme={null}
uplink.sessionDetails(
  auth: Auth,
  sessionId: string,
  options?: { host?: string }
): Promise<SessionDetails>
```

**Parameters:**

* `auth`: Your OAuth client credentials from Uplink Console — `{ clientId, clientSecret }`
* `sessionId`: The ID of an existing session belonging to the client's project

**Returns:** `Promise<SessionDetails>` - Session metadata. See the [SessionDetails](#sessiondetails) type below.

**Example:**

```typescript theme={null}
import uplink from '@uplink-code/uplink'

const details = await uplink.sessionDetails(
  {
    clientId: process.env.UPLINK_CLIENT_ID,
    clientSecret: process.env.UPLINK_CLIENT_SECRET
  },
  '<existing-session-id>'
)
console.log('Devices paired:', details.devices.length)
console.log('Bytes streamed:', details.total_bytes)
console.log('Connection time (s):', details.total_connection_duration)
```

### `uplink.token()`

Exchanges your OAuth client credentials for an access token. You don't normally need this — passing credentials directly to the methods above lets the SDK obtain and renew tokens for you. Reach for it when you want to hold the token yourself, for example to share one across processes.

```typescript theme={null}
uplink.token(
  auth: Auth,
  options?: { host?: string; signal?: AbortSignal }
): Promise<Token>
```

**Parameters:**

* `auth`: Your OAuth client credentials from Uplink Console — `{ clientId, clientSecret }`
* `options` (optional):
  * `host`: Override the Uplink API host
  * `signal`: An `AbortSignal` to cancel the exchange

**Returns:** `Promise<Token>` - `{ token, expiresAt }`. Pass it anywhere an `Auth` is accepted.

**Example:**

```typescript theme={null}
import uplink from '@uplink-code/uplink'

const token = await uplink.token({
  clientId: process.env.UPLINK_CLIENT_ID,
  clientSecret: process.env.UPLINK_CLIENT_SECRET
})

const session = await uplink.session(token, {
  include: { ecdsa: true, ecdh: true }
})
const details = await uplink.sessionDetails(token, session.sessionId)
```

<Warning>
  A token you obtained yourself is **not** renewed for you — only credentials the SDK holds are. Once `expiresAt` passes, calls made with the token fail; call `uplink.token()` again with your client credentials to get a new one.
</Warning>

<Tip>
  **When to reach for which method:**

  * `uplink.session()` — create a brand-new session.
  * `uplink.getSession()` — refetch an existing session in the same shape as `session()` so you can pass it to `fromSession()`.
  * `uplink.sessionDetails()` — inspect a session's metadata (devices, usage, tags). Not for reconnecting.
  * `uplink.token()` — mint an access token to hold and reuse yourself, instead of letting the SDK manage one.
</Tip>

### `uplink.client.fromSession()`

Creates a client from an Uplink session.

```typescript theme={null}
uplink.client.fromSession(
  session: Session,
  options?: ClientOptions
): Promise<Client>
```

**Parameters:**

* `session`: A [Session](#session) from `uplink.session()`, or one from `uplink.getSession()` with its `credential` merged back in. `session.credential` is sent to the relay as the connection's proof that it owns the session; `session.keys` are re-imported for you.
* `options` (optional): Connection options
  * `agent`: Optional AI agent for natural language automation (requires `@uplink-code/ai`)

**Returns:** `Promise<Client>` - Connected client instance

**Complete example:**

```typescript theme={null}
import uplink from '@uplink-code/uplink'

const session = await uplink.session(
  {
    clientId: process.env.UPLINK_CLIENT_ID,
    clientSecret: process.env.UPLINK_CLIENT_SECRET
  },
  {
    include: { ecdsa: true, ecdh: true }
  }
)
const client = await uplink.client.fromSession(session)
console.log('Connected to Uplink')
```

**With AI agent:**

```typescript theme={null}
import uplink from '@uplink-code/uplink'
import ai from '@uplink-code/ai'

const agent = ai.createAgent({
  provider: 'anthropic',
  options: {
    apiKey: process.env.ANTHROPIC_API_KEY
  }
})

const session = await uplink.session(
  {
    clientId: process.env.UPLINK_CLIENT_ID,
    clientSecret: process.env.UPLINK_CLIENT_SECRET
  },
  {
    include: { ecdsa: true, ecdh: true }
  }
)
const client = await uplink.client.fromSession(session, { agent })

// All pages created from this client will have AI capabilities
const browser = await client.launch()
const page = await browser.newPage()

await page.goto('https://example.com')
await page.act('Click the sign in button') // AI-powered action
```

### `uplink.client.connect()` (Alternative)

Connects directly to an Uplink session via WebSocket URL. This is an alternative to using `uplink.session()` + `fromSession()`.

```typescript theme={null}
uplink.client.connect(
  url: string | URL,
  options?: ClientOptions
): Promise<Client>
```

**Parameters:**

* `url`: WebSocket URL in the format `wss://relay.uplink.build/session/<jwt>`
* `options` (optional): Connection options
  * `agent`: Optional AI agent for natural language automation (requires `@uplink-code/ai`)

**Returns:** `Promise<Client>` - Connected client instance

**Example:**

```typescript theme={null}
import uplink from '@uplink-code/uplink'

const client = await uplink.client.connect(
  'wss://relay.uplink.build/session/<jwt>'
)
console.log('Connected to Uplink')
```

<Note>
  **Recommended approach**: Use `uplink.session()` + `fromSession()` for better credential management and project organization. Use `connect()` when you need to work with pre-generated session URLs.
</Note>

## Browser operations

### `client.launch()`

Launches a new browser on a worker. If no worker address is provided, uses the first available worker.

```typescript theme={null}
client.launch(address?: Address): Promise<Browser>
```

**Parameters:**

* `address` (optional): Worker address to launch browser on

**Returns:** `Promise<Browser>` - New browser instance

**Example:**

```typescript theme={null}
// Launch on first available worker
const browser = await client.launch()

// Launch on specific worker using its address
const workers = await client.workers()
const browser = await client.launch(workers[0].address)
```

### `client.connect()`

Connects to an existing browser by its handle.

```typescript theme={null}
client.connect(handle: string, address?: Address): Promise<Browser>
```

**Parameters:**

* `handle`: Browser handle identifier
* `address` (optional): Worker address where browser is running

**Returns:** `Promise<Browser>` - Connected browser instance

**Example:**

```typescript theme={null}
// Connect to browser by handle (searches all workers)
const workers = await client.workers()
const browsers = await workers[0].browsers()

const existingBrowser = await client.connect(browsers[0].handle)
const page = await existingBrowser.newPage()
```

### `client.browsers()`

Lists all browsers on a worker.

```typescript theme={null}
client.browsers(address?: Address): Promise<Browser[]>
```

**Parameters:**

* `address` (optional): Worker address to query. If not provided, queries first available worker.

**Returns:** `Promise<Browser[]>` - Array of browser instances

**Example:**

```typescript theme={null}
// List browsers on first available worker
const browsers = await client.browsers()
console.log(`${browsers.length} browsers running`)

for (const browser of browsers) {
  console.log('Browser:', browser.handle)
}
```

## Worker operations

### `client.workers()`

Returns list of currently connected workers.

```typescript theme={null}
client.workers(): ClientWorker[]
```

**Returns:** `ClientWorker[]` - Array of connected workers

**Example:**

```typescript theme={null}
const workers = client.workers()

console.log(`${workers.length} workers connected`)
workers.forEach(worker => {
  console.log('Worker address:', worker.address)
})
```

### `client.terminate()`

Terminates a worker connection.

```typescript theme={null}
client.terminate(address?: Address): Promise<void>
```

**Parameters:**

* `address` (optional): Worker address to terminate. If not provided, terminates first available worker.

**Returns:** `Promise<void>`

**Example:**

```typescript theme={null}
// Terminate first available worker
await client.terminate()

// Terminate specific worker
await client.terminate(worker.address)
```

<Warning>
  Terminating a worker closes all browsers running on that worker and disconnects the device from the session.
</Warning>

## Connection management

### `client.close()`

Closes the client connection and cleans up resources.

```typescript theme={null}
client.close(): Promise<void>
```

**Returns:** `Promise<void>`

**Example:**

```typescript theme={null}
await client.close()
```

<Tip>
  Always call `close()` when done to properly clean up WebSocket connections and resources. Connection time is determined by how long clients and workers are connected to a session.
</Tip>

## Events

The client emits events for worker connection lifecycle.

### `worker-connected`

Emitted when a new worker connects to the session (i.e., when a device running the Uplink SDK joins).

```typescript theme={null}
client.on(event: 'worker-connected', handler: (worker: ClientWorker) => void)
```

**Example:**

```typescript theme={null}
client.on('worker-connected', (worker) => {
  console.log('Worker connected:', worker.address)
})
```

### `worker-disconnected`

Emitted when a worker disconnects from the session (i.e., when a device leaves or loses connection).

```typescript theme={null}
client.on(event: 'worker-disconnected', handler: (worker: ClientWorker) => void)
```

**Example:**

```typescript theme={null}
client.on('worker-disconnected', (worker) => {
  console.log('Worker disconnected:', worker.address)
})
```

## Types

### Session

Returned by `uplink.session()` and `uplink.getSession()`. Plain data — pass it to [`uplink.client.fromSession()`](#uplinkclientfromsession).

```typescript theme={null}
interface Session {
  sessionId: string
  sessionUrl: string   // Internal relay WebSocket URL the SDK connects to
  qrUrl: string        // The pairing link you show the user
  credential?: string  // The session's verifier. Always set by uplink.session(),
                       // never by uplink.getSession() — see Session restriction
  keys?: {
    ecdh?: { public: string; private: string }
    ecdsa?: { public: string; private: string }
  }
}
```

<Note>
  `credential` is optional on the type because `getSession()` returns a `Session` without one. It is always present on the result of `uplink.session()`.
</Note>

### Auth

Accepted by `uplink.session()`, `uplink.getSession()`, `uplink.sessionDetails()`, and `uplink.token()`. Either your client credentials, or an access token you already hold.

```typescript theme={null}
type Auth = ClientCredentials | Token
```

### ClientCredentials

```typescript theme={null}
interface ClientCredentials {
  clientId: string
  clientSecret: string
  scope?: string  // Request fewer permissions than the client was granted
}
```

### Token

Returned by `uplink.token()`.

```typescript theme={null}
interface Token {
  token: string
  expiresAt?: number  // Epoch ms; absent when the lifetime is unknown
}
```

### ClientOptions

```typescript theme={null}
interface ClientOptions {
  agent?: Agent  // Optional AI agent from @uplink-code/ai
}
```

### Address

Worker identifier - a hex-encoded address:

```typescript theme={null}
type Address = string  // Hex-encoded worker address
```

### SessionDetails

Returned by `uplink.sessionDetails()`. Aggregates session-level metadata across the API, devices, billing, and tags.

```typescript theme={null}
interface SessionDetails {
  id: string
  project: {
    id: string
    name: string
    description: string | null
  }
  organization: {
    id: string
    name: string
    avatar_url: string | null
  }
  created_at: Date
  updated_at: Date
  devices: Array<{
    id: string
    device_id: string
    device_type: string
    device_model: string | null
    platform: string | null
    platform_version: string | null
  }>
  total_bytes: number
  total_connection_duration: number  // seconds
  tags: Record<string, string>
  sessionUrl: string
}
```

## Complete example

```typescript theme={null}
import uplink from '@uplink-code/uplink'

async function main() {
  // Connect to session that will be used for multiple workers doing the same script
  const session = await uplink.session(
    {
      clientId: process.env.UPLINK_CLIENT_ID,
      clientSecret: process.env.UPLINK_CLIENT_SECRET
    },
    {
      include: { ecdsa: true, ecdh: true }
    }
  )
  const client = await uplink.client.fromSession(session)

  // Listen for worker events
  client.on('worker-connected', async (worker) => {
    console.log('Worker joined:', worker.address)

    const browser = await worker.launch()
    const page = await browser.newPage()
    await page.goto('https://example.com')

    await page.close()
    await browser.close()
    await client.close()
  })

  client.on('worker-disconnected', (worker) => {
    console.log('Worker left:', worker.address)
  })
}

main().catch(console.error)
```

## Related

<CardGroup cols={2}>
  <Card title="ClientWorker" icon="mobile" href="/api-reference/client-worker">
    Device-specific operations
  </Card>

  <Card title="Browser" icon="browser" href="/api-reference/browser">
    Browser management
  </Card>

  <Card title="Core concepts" icon="book" href="/fundamentals/core-concepts">
    Architecture overview
  </Card>

  <Card title="Sessions" icon="key" href="/sessions">
    Session management
  </Card>
</CardGroup>
