# Backend functions reference

> Write, call and secure server functions with the server SDK.


A function is a Next.js route handler at `app/api/functions/<name>/route.ts` exporting `POST`.

```ts
import { createServiceClient, getUser, json } from "@/lib/draftship-server";

export async function POST(req: Request) {
  const user = await getUser(req);
  if (!user) return json({ error: "Sign in first" }, 401);

  const { orderId } = await req.json();
  const ds = createServiceClient();                 // admin access, ignores security rules
  const order = await ds.entities.Order.get(orderId);
  if (order.created_by !== user.email) return json({ error: "Not your order" }, 403);

  await ds.entities.Order.update(orderId, { status: "cancelled" });
  ds.log(`Order ${orderId} cancelled`, "info", { by: user.email });
  return json({ ok: true });
}
```

Call it from a client component:

```ts
const result = await ds.functions.invoke("cancelOrder", { orderId });
```

## Server SDK

| Export | Purpose |
|---|---|
| `createServiceClient()` | `entities` (all methods incl. `updateMany`, `deleteMany`), `integrations.Core`, `log()`. |
| `getUser(req)` | The calling app user, or `null`. |
| `isAutomation(req)` | `true` when an [automation](/docs/logic/automations) is calling. |
| `json(data, status?)` | JSON response helper. |

## Environment

Functions receive `DRAFTSHIP_APP_ID`, `DRAFTSHIP_API_URL`, `DRAFTSHIP_SERVICE_KEY` and your [secrets](/docs/logic/secrets). Never send the service key to the browser.

## Automation payload

Automations `POST` `{ "event": "schedule" | "manual" | "create" | "update" | "delete", "entity"?, "record"? }`. Responses are logged; non-2xx responses mark the run as failed. Functions have 60 seconds per automation run.

## Connectors

Accounts the owner linked in **Dashboard → Connectors** are available to backend functions through the server SDK:

```ts
import { createServiceClient, getUser, json } from "@/lib/draftship-server";

export async function POST(req: Request) {
  const user = await getUser(req);
  if (!user) return json({ error: "Sign in first" }, 401);
  const { title, start, end } = await req.json();
  const ds = createServiceClient();
  const res = await ds.connectors.fetch("google_calendar", "/calendars/primary/events", {
    method: "POST",
    body: JSON.stringify({ summary: title, start: { dateTime: start }, end: { dateTime: end } }),
  });
  if (!res.ok) return json({ error: "Couldn't add the event" }, 502);
  return json(await res.json());
}
```

`ds.connectors.fetch(id, path, init)` adds the owner's token and resolves relative paths against the service's API (`https://www.googleapis.com/calendar/v3` for `google_calendar`). Full URLs are used as they are. For SDKs that need the raw token, use `ds.connectors.getAccessToken(id)`, which returns `{ access_token, expires_at, account, api_base }`.

Connector ids: `google_calendar`, `gmail`, `google_sheets`, `google_drive`, `slack`, `notion`, `github`, `hubspot`, `linear`. A connector that isn't linked returns `404`, and one whose access was revoked returns `401`.

The token endpoint (`GET /connectors/<id>/token` on the apps API) only accepts the app's service key, so tokens can't be fetched from the browser.
