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

# Workflows

> createEndpoint from zitejs/backend: typed input/output, authentication, errors, streaming, and calling workflows from the frontend.

A [workflow](/concepts/workflows) is one file in an app's `src/api/` directory that
default-exports `createEndpoint` from `zitejs/backend` — server-side logic with typed inputs and outputs.

```typescript theme={null}
// apps/helpdesk/src/api/createTicket.ts
import { z } from 'zod';
import { createEndpoint, ZiteError } from 'zitejs/backend';
import { zite } from 'zitejs/db';

export default createEndpoint({
  description: 'Create a support ticket',
  authenticated: true,
  inputSchema: z.object({ subject: z.string().min(1) }),
  outputSchema: z.object({ id: z.string() }),
  execute: async ({ input, context }) => {
    if (!input.subject) throw new ZiteError('Subject is required', { statusCode: 400 });
    const ticket = await zite.Tickets.create({
      record: { subject: input.subject, status: 'open', assigneeEmail: context.user.email },
    });
    return { id: ticket.id };
  },
});
```

<Note>
  Each workflow must be a **default export**, and the filename is the workflow id — a single segment
  matching `^[a-zA-Z0-9_-]+$` (no subfolders under `src/api/`).
</Note>

## Config

| Field           | Type            | Notes                                                          |
| --------------- | --------------- | -------------------------------------------------------------- |
| `description`   | `string`        | Human/agent-readable summary                                   |
| `inputSchema`   | Zod schema      | Validates and types `input`                                    |
| `outputSchema`  | Zod schema      | Validates and types the return value                           |
| `authenticated` | `boolean`       | When `true`, `context.user` is populated                       |
| `stream`        | `boolean`       | Enables streaming responses (see below)                        |
| `schedule`      | schedule config | Run on a cadence — see [Schedules](/framework/schedules)       |
| `webhook`       | webhook config  | Receive inbound webhooks — see [Webhooks](/framework/webhooks) |
| `execute`       | function        | `async ({ input, context, stream? }) => output`                |

## Context

`execute` receives a `context`:

| Property         | Type               | When populated                                       |
| ---------------- | ------------------ | ---------------------------------------------------- |
| `user`           | `{ id, email, … }` | When `authenticated: true`. `null` on scheduled runs |
| `userId`         | `string`           | Identifier for the current request                   |
| `organizationId` | `string`           | Identifier for the current request                   |
| `scheduledAt`    | `string`           | Scheduled runs only — the ISO time the run was due   |

`process.env.ZITE_APP_URL` is set to your app's public URL — use it to build links instead of hardcoding.

## Errors

Throw `ZiteError` to return a controlled error with an HTTP status:

```typescript theme={null}
throw new ZiteError('Not found', { statusCode: 404 }); // statusCode defaults to 500
```

## Calling workflows from the frontend

Workflows are called through a generated, typed client (like tRPC). Each workflow exports a camel-cased
caller plus its input/output types:

```tsx theme={null}
import { createTicket, CreateTicketInputType, CreateTicketOutputType } from 'zitejs/api';

const { id } = await createTicket({ subject: 'Printer is down' });
```

After adding, renaming, or deleting a workflow file, regenerate the client with `npx zitejs generate` from
the workspace root (the [build tools](/mcp/build-tools) do this for you).

Workflows are also reachable publicly at `POST /public/{flowId}/api/{name}`.

## Streaming

Set `stream: true` and write chunks as you go:

```typescript theme={null}
export default createEndpoint({
  stream: true,
  inputSchema: z.object({ prompt: z.string() }),
  outputSchema: z.object({ text: z.string() }),
  execute: async ({ input, stream }) => {
    await stream.write({ token: 'thinking…' });
    // stream.forward(asyncIterable) pipes an async iterable (e.g. an LLM stream) through
    const text = await stream.forward(model.stream(input.prompt));
    return { text };
  },
});
```

On the frontend, streaming workflows are called with the generated streaming caller, which yields chunks
and resolves a final `result`.
