# Server integration

URL: https://developers.uidu.org/docs/paths/server-integration

> Read and write a workspace's private data from your own server — internal tools, sync jobs, provisioning.

Use this path when your code runs on a server you control and acts **as the workspace**: an
internal dashboard over contacts and deals, a nightly sync of employees into payroll, a script
that provisions a new site. It needs an account **Bearer** token (`apiKey`), never shipped to a
browser.

By the end you'll have a server client that reads private data, walks whole collections, writes
with proper error handling, and the CLI for the same work from a terminal.

### Create a server client with `apiKey`

One client, built with the account Bearer token and imported only from server code: a route
handler, a server action, a Server Component or a plain Node script.

```ts
// lib/uidu-admin.ts — never imported from a 'use client' file
import { createClient } from '@uidu/client';

export const uiduAdmin = createClient({
  workspace: process.env.UIDU_WORKSPACE!,
  apiKey: process.env.UIDU_API_KEY, // server-only — no NEXT_PUBLIC_ prefix
});
```

Where the token comes from, and why it never reaches the browser:
[Authentication](https://developers.uidu.org/docs/foundations/authentication.md).

### Read private data

Contacts and deals, people and goals are readable only with the Bearer. A route handler that
feeds an internal dashboard:

```ts
// app/api/dashboard/route.ts
import {
  listContacts,
  listDeals,
  listEmployees,
  listGoals,
  goalProgress,
  isGoalBehind,
} from '@uidu/client';
import { uiduAdmin } from '@/lib/uidu-admin';

export async function GET() {
  const [contacts, deals, employees, goals] = await Promise.all([
    listContacts(uiduAdmin), // every contact — not paginated
    listDeals(uiduAdmin, { first: 50 }),
    listEmployees(uiduAdmin, { first: 50 }),
    listGoals(uiduAdmin),
  ]);

  const behind = goals
    .filter((g) => !g.parentId) // objectives, not key results
    .filter((g) => isGoalBehind(g, { tolerance: 0.1 }))
    .map((g) => ({ name: g.name, done: goalProgress(g), status: g.status }));

  return Response.json({
    contacts: contacts.length,
    openDeals: deals.map((d) => ({ name: d.name, amount: d.amount, status: d.status?.name })),
    people: employees.map((e) => e.user?.name),
    behind,
  });
}
```

<Callout type="info" title="Empty is a symptom">
  A list that comes back empty (or a `get*` that returns `null`) usually means the client was built
  with a public token: private data isn't an error for it, just invisible. Check that the client
  has `apiKey` before debugging the query.
</Callout>

Fields, filters and helpers: [Contacts & deals](https://developers.uidu.org/docs/domains/contacts.md) (`amount` is in cents),
[People](https://developers.uidu.org/docs/domains/people.md), [Goals](https://developers.uidu.org/docs/domains/goals.md) (values stored ×100).

### Read everything

`list*` returns one page. For a sync job or an export, `paginate` walks every page of a list
with its generated `*Document`:

```ts
import { paginate, ListDealsDocument, ListEmployeesDocument } from '@uidu/client';
import { uiduAdmin } from '@/lib/uidu-admin';

const deals = await paginate(uiduAdmin, ListDealsDocument, {
  variables: { first: 100 },
  extract: (data) => data.currentWorkspace?.deals,
});

const employees = await paginate(uiduAdmin, ListEmployeesDocument, {
  variables: { first: 100 },
  extract: (data) => data.currentWorkspace?.employees,
});
```

`listContacts` already returns every contact: the field isn't a connection, so there is nothing
to paginate. More in [Pagination & field values](https://developers.uidu.org/docs/foundations/pagination-and-field-values.md).

### Write, and handle `errors`

Every write takes `{ input: { attributes } }` (plus `id` to update) and resolves to a payload
with an `errors` array. A &#x2A;*non-empty `errors`** is a validation failure — nothing is thrown, and
the record is unchanged. A transport or GraphQL failure, instead, **throws** a `UiduError`.

```ts
'use server';
import { updateDeal, UiduError } from '@uidu/client';
import { uiduAdmin } from '@/lib/uidu-admin';

export async function moveDeal(id: string, statusId: string) {
  try {
    const result = await updateDeal(uiduAdmin, {
      input: { id, attributes: { statusId } },
    });

    if (result?.errors?.length) {
      // validation: e.g. [{ key: 'status', name: '…', type: '…' }]
      return { ok: false, errors: result.errors.map((e) => e.name) };
    }
    return { ok: true, id: result?.deal?.id };
  } catch (err) {
    if (err instanceof UiduError && err.code === 'GRAPHQL_ERROR') {
      console.error(err.graphQLErrors);
      return { ok: false, errors: ['The server rejected the request.'] };
    }
    throw err; // NETWORK_ERROR and anything else: let it surface
  }
}
```

Where the record sits in the payload varies by mutation — `updateDeal` returns it at `deal`,
`createDeal` at `deal.node`, `createTask` at `task`. The domain page shows each one. The input
shape and the full capability matrix: [Writes](https://developers.uidu.org/docs/foundations/writes.md); error codes and what to
retry: [Errors](https://developers.uidu.org/docs/foundations/errors.md).

### Provision from the terminal

The `uidu` CLI runs the same functions with the same Bearer: sign in once, then
`<entity> list|get|create|update|delete`. With `--json`, stdout carries only JSON, so you can pipe
it.

```bash
uidu login --workspace acme          # browser sign-in, token stored in ~/.uidu/config.json
uidu whoami --json                   # check the workspace and that the token hasn't expired

# read
uidu deals list --first 50 --json
uidu employees list --json | jq -r '.[].user.email'

# write — exits non-zero and prints the errors when `errors` isn't empty
uidu tasks create --attributes '{"name":"Call back Acme","spaceId":"Z2lkOi8v…"}' --json
uidu deals update Z2lkOi8v… --attributes '{"statusId":"Z2lkOi8v…"}' --json
```

In CI, set `UIDU_WORKSPACE` and `UIDU_API_KEY` instead of running `login` (flags win over env,
env over `~/.uidu/config.json`). Every verb per entity: [CLI commands](https://developers.uidu.org/docs/tools/cli/commands.md);
sign-in flows and token expiry: [Login](https://developers.uidu.org/docs/tools/cli/login.md).

### Run actions the SDK doesn't cover yet

The platform exposes more actions than the SDK has typed functions for. Any of them runs today,
by key, through the generic dispatcher — `executeAction` in code, `uidu tools call` in the
terminal. `attributes` uses the entity's own camelCase fields, and associations are GlobalIDs (the
`id` a previous `list` or `get` returned).

```ts
import { executeAction } from '@uidu/client';
import { uiduAdmin } from '@/lib/uidu-admin';

const result = await executeAction(uiduAdmin, {
  input: {
    key: 'Budgets::Budget::Create',
    attributes: { fiscalYear: 2027, currency: 'EUR' },
  },
});

if (result?.errors?.length) {
  // same rule as every write: validation errors come back, they aren't thrown
} else {
  result?.result; // what the action returned, as JSON
}

// The same from the terminal:
//   uidu tools call Budgets::Budget::Create --attributes '{"fiscalYear":2027,"currency":"EUR"}' --json
```

For an action on an existing record, pass its `id` in `input` as well — `executeAction` only;
`uidu tools call` takes `--attributes` alone. Every available key, and which ones the SDK covers:
[Roadmap](https://developers.uidu.org/docs/reference/roadmap.md). Field names per entity: [Schema](https://developers.uidu.org/docs/reference/schema.md).

## Where to go next

* [Writes](https://developers.uidu.org/docs/foundations/writes.md) — the input shape, `errors`, and which verbs each entity supports.
* [Paginate every page](https://developers.uidu.org/docs/recipes/paginate.md) — worked examples of `paginate`.
* [Bookings](https://developers.uidu.org/docs/domains/bookings.md) and [Campaigns](https://developers.uidu.org/docs/domains/campaigns.md) — the other private-data domains.
* [Spaces, tasks & notes](https://developers.uidu.org/docs/domains/workspace.md) — `createTask`, `createNote`, `createSpace`.
* [The `uidu` CLI](https://developers.uidu.org/docs/tools/cli.md) — everything the terminal can do, including scaffolding.
* [Client reference](https://developers.uidu.org/docs/reference/client.md) — every function, with its auth.
