---
title: "Document Authoring AI workflows"
canonical_url: "https://www.nutrient.io/guides/document-authoring/ai/workflows/"
md_url: "https://www.nutrient.io/guides/document-authoring/ai/workflows.md"
last_updated: "2026-06-12T18:57:15.093Z"
description: "Run bounded AI tasks such as proofreading and translation with structured output and deterministic document apply."
---

# Workflows

Workflows are the Document Authoring AI path for a well-defined task. Your app picks a task such as proofreading, translation, or a house style check; sends the document to the model; and gets back a structured set of edits to apply. There’s no conversation and no tool loop — just one roundtrip per run.

Use workflows for tasks your app picks in advance. Typical jobs include proofreading the document, translating it into another language, replacing contract placeholders with field references, or flagging anything that breaks your writing rules.

If the task isn’t known up front, or it might take several back-and-forth turns, use [agentic tools](https://www.nutrient.io/guides/document-authoring/ai/agentic-tools.md) instead. The two paths share the same execution boundary, so it’s fine to ship both side by side.

## How a workflow runs

The flow has three steps — two in the browser and one on your server. The browser reads the document and ships the snapshot to your server, the server asks the model for structured output that matches the workflow’s schema, and the browser then validates that output and applies it as one batch of edits.

The browser owns the editor, so it has to be the one reading and writing. The server owns the model, so that’s where prompts and API keys live. The workflow’s output schema is the contract between them.

## Running a workflow in the browser

```ts

const workflow = getBuiltInWorkflow("proofreading");
const editorMode = editor.getEditorMode();

if (editorMode === "view") {
  throw new Error("Switch to Edit or Review mode before running this workflow.");
}

const workflowInput = await toolkit.readWorkflowInput(workflow);

const response = await fetch("/api/ai/workflow", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    workflow: workflow.name,
    task: workflow.defaultTask,
    workflowInput,
  }),
});

const { output } = await response.json();

await toolkit.applyWorkflowOutput(workflow, output, {
  writeMode: editorMode === "review"? "track_changes"
    : "apply",
  scope: workflowInput.scope,
});

```

`readWorkflowInput` returns a `WorkflowInput` with the plain text content, a DocJSON fragment, and the scope (`"selection"` or `"document"`). Pass the entire thing to your server as the prompt input. `applyWorkflowOutput` accepts the raw model response, validates the `replacementFragment` against the workflow’s output schema, and replaces either the selection or the whole document in one operation.

View mode is read-only, so guard against it before calling `applyWorkflowOutput`. The full mode policy lives in [review and approval](https://www.nutrient.io/guides/document-authoring/ai/review-and-approval.md).

Passing `scope: workflowInput.scope` is important: It tells `applyWorkflowOutput` whether to replace the selection or the whole document. When scope is `"document"`, the document is replaced directly regardless of `writeMode`. Tracked changes aren’t used for whole-document replacement.

`applyWorkflowOutput` doesn’t consume `reviewComment`. Review comments are currently an agentic-only feature: Each agentic write tool call can carry its own reviewer-facing motivation, but workflow output cannot.

## Built-in workflows

The toolkit ships two ready-to-use workflows.

### Proofreading

```ts

const workflow = getBuiltInWorkflow("proofreading");

```

Proofreading fixes spelling, grammar, punctuation, capitalization, duplicated words, and obvious typos. It deliberately won’t rewrite for style, shorten the document, add or delete content, or touch formatting. Keeping the diff predictable is what makes it safe to apply as tracked changes.

### Translation

```ts

const workflow = getBuiltInWorkflow("translation", {
  targetLanguage: "spanish",
});

```

Translation runs into `english`, `german`, `french`, or `spanish`, and defaults to `english` if you omit `targetLanguage`. Document structure, paragraph order, tables, names, numbers, dates, and formatting intent are preserved. Only the text changes.

## Calling the model on the server

The server’s job is small. Take the workflow input, call the model with structured output, and return what the model produced. The example below uses Vercel AI SDK [`generateText`](https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-text) with [`Output.object`](https://ai-sdk.dev/docs/reference/ai-sdk-core/output), but the pattern works with any library that supports JSON Schema or Zod-style output. For framework-specific adapter details, see [Vercel AI SDK integration](https://www.nutrient.io/guides/document-authoring/ai/integrations/vercel-ai-sdk.md) or [LangChain integration](https://www.nutrient.io/guides/document-authoring/ai/integrations/langchain.md).

```ts

import { openai } from "@ai-sdk/openai";
import { generateText, Output } from "ai";
import { getBuiltInWorkflow } from "@nutrient-sdk/document-authoring-ai";
import { toVercelAiWorkflowOutputSchema } from "@nutrient-sdk/document-authoring-ai/vercel";

export async function POST(req: Request) {
  const body = await req.json();
  const workflow = getBuiltInWorkflow("proofreading");

  const result = await generateText({
    model: openai("gpt-4o-mini"),
    system: workflow.systemPrompt,
    prompt: JSON.stringify({
      task: body.task?? workflow.defaultTask,
      input: body.workflowInput,
    }),
    output: Output.object(toVercelAiWorkflowOutputSchema(workflow)),
    temperature: 0.1,
  });

  return Response.json({ output: result.output });
}

```

The output schema is what makes this safe. Without it, the model could return anything. With it, you either get a valid `WorkflowOutput` or a parse error your route can surface.

## Output shape

Every workflow returns this type:

```ts

type WorkflowOutput = {
  replacementFragment: WorkflowFragment;
};

```

`replacementFragment` is a DocJSON fragment that replaces the input scope in full: either the selected content or the whole document body. The model receives the input fragment and returns a corrected or translated version of it. The toolkit validates the fragment against the workflow’s output schema before applying it.

## Custom workflows

If proofreading and translation don’t fit your task, write your own:

```ts

const workflow = createWorkflow({
  name: "house_style_review",
  systemPrompt: `You are running the House Style Review workflow.

Return structured workflow output with a replacementFragment DocJSON object.

Review scope:

- Make wording concise, direct, and consistent.

- Preserve facts, document structure, formatting intent, and tone.

- Return a complete DocJSON fragment that replaces the input scope.

- Preserve the existing fragment type tag, version, resources, styles, and list formats unless the edit requires a change.

- Do not add new claims or delete required content.

- If the document has no issues that match this scope, return replacementFragment equal to the input fragment.`,
  defaultTask:
    "Review the document against our writing rules without changing the facts.",
});

```

Custom workflows reuse the same output schema and the same apply path. If you find yourself wanting branching, planning, or arbitrary tool choice, you’ve outgrown workflows. Switch to [agentic tools](https://www.nutrient.io/guides/document-authoring/ai/agentic-tools.md).
---

## Related pages

- [Agentic tools](/guides/document-authoring/ai/agentic-tools.md)
- [Review and approval](/guides/document-authoring/ai/review-and-approval.md)
- [Document Authoring AI](/guides/document-authoring/ai/overview.md)
- [Quick start](/guides/document-authoring/ai/quick-start.md)

