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

# Servers and CI

> Drive a workspace from your own server, a job or a CI step with an API key: files with conditional writes, Actions, chats, Site builds, and the legacy client underneath.

`createServerClient` from `@arg/sdk/server` binds the whole SDK to one workspace with a credential you hold. It refuses to run in a browser: credentials stay on your side, and the backend checks every call against the principal's own permissions.

```ts theme={null}
import { createServerClient } from "@arg/sdk/server";

const arg = createServerClient({
  workspaceId: process.env.ARG_WORKSPACE_ID!,
  auth: { kind: "api-key", key: process.env.ARG_API_KEY! },
});
try {
  const state = await arg.fs.readFile("/state.json");
  await arg.fs.writeJSON(
    "/state.json",
    { ...JSON.parse(state.content), ran: Date.now() },
    {
      baseRevision: state.revision!,
    },
  );
  const sites = await arg.sites.list();
} finally {
  arg.dispose();
}
```

## Options

| Option        | Meaning                                                                                                                                         |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `workspaceId` | The workspace every call is bound to                                                                                                            |
| `auth`        | `{ kind: "api-key", key }` for a [user-owned or service-account key](/guides/api-keys), or `{ kind: "access-token", token }` for a bearer token |
| `baseUrl`     | Defaults to `https://api.arg.ai`; must be HTTPS except on loopback                                                                              |
| `basePath`    | The folder relative `fs` paths resolve against. A convenience, **not** an authorization boundary                                                |
| `fetch`       | A custom `fetch`, for tests or an instrumented client                                                                                           |
| `timeoutMs`   | Per-call deadline; each call also accepts `{ signal, timeoutMs }` as its last argument                                                          |

A user-owned key acts as that user; a service-account key acts as the account, and user-only endpoints refuse it. The SDK never widens what the key can do.

## What the client serves

Everything in the [reference](/guides/sdk/reference) except the embedded-only surfaces: `fs` with [conditional writes](/guides/sdk/files#conditional-writes), `actions` and `agents`, `chat` and `workspace`, `auth.principal()`, `users`, `permissions`, and the resource namespaces (`comments`, `notifications`, `integrations`, `automations`, `code`, `sites`, `servers`). `db`, `host`, `ui` and `fs.open` need an Arg application host and report `unsupported_capability` here; `chat.start` with a desktop harness is refused too, since [those run inside the desktop app](/guides/sdk/chat#claude-code-and-codex-on-the-desktop).

## Requests, deadlines, cancellation

Every request carries a deadline, refuses redirects, sends no cookies and reads at most 32 MiB. Mutating requests are never retried automatically. Cancelling a call through its `signal` stops waiting; it cannot undo a mutation the server already accepted, so reconcile with a read before retrying. `dispose()` aborts every in-flight call and stops every `fs.watch` the client created; a call after that rejects with `client_closed`.

## The legacy client underneath

The server client is built on [`@arg-ai/sdk`](/guides/typescript-sdk) and exposes it, so a script written against that package keeps its resources and gains the new namespaces:

```ts theme={null}
const run = await arg.run("Chart revenue by region from data.csv"); // the Run lifecycle
const result = await run.result();
await arg.files.upload({ data: bigBlob, name: "video.mp4" }, { onProgress });
const ws = await arg.workspaces.create({ name: "quickstart" });
const keys = await arg.keys.list();
```

`files`, `workspaces`, `chats`, `keys` and `agentLifecycle` are those legacy resources unchanged: their DTOs, errors and retry behaviour are the legacy contracts, documented on the [TypeScript SDK](/guides/typescript-sdk) page.

### Advanced file transfers

`arg.fs` reads and writes whole files as JSON. For anything large or streamed - multipart uploads with per-part retries, streamed downloads, batch operations - use `arg.files`, the legacy files resource, which switches to a resilient multipart upload above 16 MiB.

## Sites and Servers with their own tokens

A published Site or a running Server receives an Action capability token at deploy time. `createCapabilityClient` from `@arg/sdk/server` uses that token on the app server without turning it into an account credential:

```ts theme={null}
import { createCapabilityClient } from "@arg/sdk/server";

const arg = createCapabilityClient({
  token: process.env.ARG_ACTION_TOKEN!,
  integrationToken: process.env.ARG_INTEGRATION_TOKEN, // optional
  workspaceId: process.env.ARG_WORKSPACE_ID!,
});
const catalog = await arg.actions.list();
const receipt = await arg.actions.run("file_read", { path: "notes.md" });
const contacts = await arg.integrations.proxy("crm", "/objects/contacts", { method: "GET" });
```

The capability client covers Action discovery, schema, execution and run lookup, plus `integrations.proxy` when a separate integration token is supplied. It cannot reach chats, workspace discovery or the other resources: those need an account, and a capability token is not one. Its principal, read scope, expiry and revocation are exactly what the token carries. Never return either token to a browser; the [hosted frontends guide](/guides/sdk/hosted-sites#hosted-frontends-outside-arg) shows how a Site's own frontend reaches the SDK through a gateway instead.

## A CI example

```yaml theme={null}
- name: Publish the nightly report
  env:
    ARG_API_KEY: ${{ secrets.ARG_API_KEY }}
    ARG_WORKSPACE_ID: ${{ vars.ARG_WORKSPACE_ID }}
  run: node scripts/publish-report.mjs
```

```js theme={null}
import { createServerClient } from "@arg/sdk/server";

const arg = createServerClient({
  workspaceId: process.env.ARG_WORKSPACE_ID,
  auth: { kind: "api-key", key: process.env.ARG_API_KEY },
});
try {
  await arg.fs.write("/reports/nightly.md", report);
  await arg.notifications.sendToMe({
    title: "Nightly report ready",
    body: "See /reports/nightly.md",
    channels: ["in-app"],
  });
  const turn = await arg.chat.start("Summarize /reports/nightly.md in three bullets");
  console.log("started chat", turn.chat_id);
} finally {
  arg.dispose();
}
```
