> For the complete documentation index, see [llms.txt](https://docs.appmixer.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.appmixer.com/getting-started/ai-copilot.md).

# AI Copilot

<figure><img src="https://4113719406-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FzU8KYStPfR8N1iZXsgad%2Fuploads%2FxKoULY4CZjYgY21tkOT0%2Fbuild%20with%20ai%20copilot.gif?alt=media&amp;token=0f8c64dc-b2cd-4b5d-bbd1-9d8b78eb541f" alt=""><figcaption></figcaption></figure>

AI Copilot is an assistant built into the Appmixer Designer that builds, edits and explains flows from natural language. Ask it to "Create a flow that posts new Typeform responses to Slack" and it designs the flow directly on the canvas — selecting connectors, wiring components, and filling in parameters.

Copilot can:

* **Build new flows** from a natural-language description.
* **Edit existing flows** — add, replace or remap components in the open flow. For running flows, Copilot works on a draft so the live flow keeps running unchanged.
* **Explain flows** — describe what an existing flow does, step by step.

## Enabling Copilot

Copilot ships as a system plugin of the Appmixer engine. It is loaded when it is listed in the `SYSTEM_PLUGINS` environment variable and an LLM API key is configured:

```
SYSTEM_PLUGINS=copilot
COPILOT_LLM_API_KEY=sk-ant-...
```

Additional configuration:

| Environment Variable                       | Default                                  | Description                                                                                                                                                                                                                                                                                                                  |
| ------------------------------------------ | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `COPILOT_LLM_PROVIDER`                     | `anthropic`                              | LLM provider.                                                                                                                                                                                                                                                                                                                |
| `COPILOT_LLM_API_KEY`                      | *(unset)*                                | LLM provider API key. The plugin is skipped when no API key is set.                                                                                                                                                                                                                                                          |
| `COPILOT_LLM_MODEL`                        | `claude-sonnet-5`                        | LLM model used by the assistant.                                                                                                                                                                                                                                                                                             |
| `COPILOT_LLM_EFFORT`                       | `medium`                                 | Reasoning effort requested from the model on the direct provider path — `low`, `medium`, `high`, `xhigh` or `max`. Requested only on models known to accept it; any other model is sent neither field and runs with its own defaults. Thinking tokens are billed as output tokens. Ignored on the Appmixer Cloud proxy path. |
| `COPILOT_MAX_TOKENS`                       | *(unset)*                                | Per-step output token ceiling. Unset leaves it to the provider — the model's own maximum for a model id the SDK recognizes, but a conservative 4096 fallback for one it does not, so set it explicitly on a deployment pinned to an aliased or unlisted model.                                                               |
| `COPILOT_MAX_TURNS`                        | `25`                                     | Maximum agent turns per chat request.                                                                                                                                                                                                                                                                                        |
| `COPILOT_CONVERSATION_RETENTION_DAYS`      | `0`                                      | How many days a conversation is retained after its last turn. `0` keeps it forever. Expiry is swept periodically by a garbage-collector job, so deletion is not instant. Replaces `COPILOT_CONVERSATION_TTL`.                                                                                                                |
| `COPILOT_CONVERSATION_MAX_STORED_MESSAGES` | `500`                                    | Cap on the messages kept per conversation. The oldest fall off first.                                                                                                                                                                                                                                                        |
| `COPILOT_CONVERSATION_PROMPT_CHAR_BUDGET`  | `400000`                                 | Cap, in characters, on how much of a conversation is replayed into the prompt on each turn. Storage is unaffected and the history endpoint always returns the full transcript.                                                                                                                                               |
| `COPILOT_MAX_RUN_TIME`                     | `600000`                                 | Wall-clock cap (ms) on a single chat turn. A turn runs on the server and is no longer aborted by the client disconnecting, so this is what ends one whose provider connection has stalled.                                                                                                                                   |
| `COPILOT_PRICING_PATH`                     | *(unset)*                                | Optional path to a custom model pricing JSON file used for cost computation in the usage API.                                                                                                                                                                                                                                |
| `COPILOT_LLM_URL`                          | *(unset)*                                | Base URL for direct LLM provider access, e.g. an Anthropic-compatible gateway. Unset uses the provider SDK default. No effect when the Appmixer Cloud proxy is used.                                                                                                                                                         |
| `COPILOT_APPMIXER_CLOUD_API`               | `https://cloud.appmixer.com/api/copilot` | Appmixer Cloud proxy endpoint (OpenAI-compatible).                                                                                                                                                                                                                                                                           |
| `COPILOT_APPMIXER_CLOUD_API_KEY`           | *(unset)*                                | When set, LLM requests are routed through the Appmixer Cloud proxy instead of calling the provider directly.                                                                                                                                                                                                                 |
| `COPILOT_FORWARDED_HEADERS`                | *(unset)*                                | Comma-separated allow-list of request header names forwarded from the incoming chat request to the LLM request. A trailing `*` matches a prefix, e.g. `X-Acme-*,X-Request-Id`. See [LLM gateway headers](#llm-gateway-headers).                                                                                              |
| `COPILOT_LLM_HEADERS`                      | *(unset)*                                | JSON object of static headers added to every LLM request, e.g. `{"X-Acme-Installation": "acme"}`. See [LLM gateway headers](#llm-gateway-headers).                                                                                                                                                                           |
| `COPILOT_LLM_REFUSED_HEADERS_EXCEPTIONS`   | *(unset)*                                | Comma-separated header names lifted from the built-in list of headers that are refused on an LLM request whatever the two settings above say.                                                                                                                                                                                |
| `COPILOT_CUSTOM_CONTEXT`                   | *(unset)*                                | Additional context about your use cases, connectors and data, injected into the system prompt. Up to 16000 characters. See [Writing Custom Context for Copilot](/getting-started/ai-copilot/custom-context.md).                                                                                                              |
| `COPILOT_TONE_OF_RESPONSE`                 | *(unset)*                                | Tone, vocabulary and register of Copilot's replies. Up to 4000 characters.                                                                                                                                                                                                                                                   |
| `USER_AI_CONTEXT_MAX_LENGTH`               | `8000`                                   | Maximum length of a single user's `aiContext` (see [Per-user context](#per-user-context)). An engine-level setting rather than a Copilot one: the user API enforces it when the field is written, and Copilot trims to it again when reading.                                                                                |
| `COPILOT_VARIABLES_MAX_VARIABLES`          | `100`                                    | Cap on output variables listed per component when Copilot reads live flow variables.                                                                                                                                                                                                                                         |
| `COPILOT_VARIABLES_MAX_OPTION_FIELDS`      | `25`                                     | Cap on inspector fields whose options are read.                                                                                                                                                                                                                                                                              |
| `COPILOT_VARIABLES_MAX_OPTIONS_PER_FIELD`  | `50`                                     | Cap on options read per field.                                                                                                                                                                                                                                                                                               |
| `COPILOT_VARIABLES_MAX_RESPONSE_CHARS`     | `24000`                                  | Cap on the whole variables tool result. Raising these shows the model more data at a higher token cost per request.                                                                                                                                                                                                          |
| `INTERNAL_API_URL`                         | *(falls back to `APPMIXER_API_URL`)*     | In-cluster engine URL. Copilot calls the platform API to persist flows while building them; set this so those calls do not route through the external load balancer.                                                                                                                                                         |

Copilot's configuration lives in the cluster configuration and can be edited from the Backoffice. Changes apply without restarting a pod.

{% hint style="info" %}
`COPILOT_CUSTOM_CONTEXT` and `COPILOT_TONE_OF_RESPONSE` are injected as a delimited, lower-authority section of the system prompt. They can refine Copilot's context and phrasing, but cannot override its core role, its safety rules or its flow-building rules, and cannot make it reveal the system prompt.
{% endhint %}

When the plugin is not enabled, the Copilot button is automatically hidden in the Designer.

## LLM gateway headers

Some LLM gateways need a header on every request to identify the installation, the tenant or the end user behind it. Copilot can add such headers in two ways. Both are unset by default — nothing changes for a deployment that does not configure them — and both live in the cluster configuration, so they can be edited from the Backoffice without restarting a pod.

* **`COPILOT_FORWARDED_HEADERS`** forwards headers from the incoming chat request, for a value that differs per user or per request.
* **`COPILOT_LLM_HEADERS`** adds static headers to every LLM request, for a value that is the same for the whole deployment.

Both apply to whichever endpoint Copilot talks to: the gateway named by `COPILOT_LLM_URL`, the Appmixer Cloud proxy, or the provider's own API when neither is set. The static headers are also sent with the model availability check Copilot runs at startup.

### Forwarding request headers

`COPILOT_FORWARDED_HEADERS` is a comma-separated allow-list of header names, matched case-insensitively. A trailing `*` matches every header with that prefix:

```
COPILOT_FORWARDED_HEADERS=X-Acme-*,X-Request-Id
```

Only headers named on the list are forwarded from `POST /system-plugins/copilot/chat` to the LLM request. A bare `*` is not a wildcard — the Backoffice rejects it, as it does any entry that is not a valid header name. An entry that can match no header is reported in a warning on every chat request.

{% hint style="warning" %}
The headers have to be added **server-side**, by a proxy in front of Appmixer. A browser cannot send them: the CORS preflight allows a fixed list of headers.
{% endhint %}

### Static headers

`COPILOT_LLM_HEADERS` is a JSON object of headers added to every LLM request:

```
COPILOT_LLM_HEADERS={"X-Acme-Installation": "acme"}
```

Being administrator data, it may carry an `Authorization` header for a gateway that authenticates with a bearer token. On the Appmixer Cloud proxy path that would replace the proxy's own token, so set it only for a direct gateway.

When both settings carry the same header, the static value wins, so a client can never override an administrator-pinned header. A malformed value in the environment is reported and ignored rather than stopping the node; the Backoffice rejects one up front.

### What is never sent

Any holder of a valid access token can call the chat endpoint directly with headers of their choosing, so the forwarded values are treated as untrusted input. Whatever the allow-list matches, these headers are never placed on an LLM request:

* **Credential headers** — `Authorization`, `Proxy-Authorization`, `api-key`, `x-api-key`, `Cookie`, `Set-Cookie`. `Authorization` is the one exception for static headers, as described above; a forwarded header can never carry it.
* **Transport, framing and encoding headers** — `Host`, `Connection`, `Content-Length`, `Content-Type`, `Content-Encoding`, `Accept-Encoding`, `Transfer-Encoding`, `TE`, `Trailer`, `Upgrade`, `Keep-Alive`, `Expect`.
* **Headers the provider SDK owns** — `anthropic-version`, `anthropic-beta`, `User-Agent`.

Further limits apply to every header from either source:

* Values must be printable ASCII. A value carrying a line break is dropped rather than failing the whole chat request.
* A value is capped at 4096 characters.
* At most 32 headers are forwarded per request.
* Only header names are logged, never values, and the values are kept out of the Langfuse traces.

`COPILOT_LLM_REFUSED_HEADERS_EXCEPTIONS` lifts individual names from that list, for a gateway that needs a header the list cannot anticipate:

```
COPILOT_LLM_REFUSED_HEADERS_EXCEPTIONS=api-key
```

{% hint style="warning" %}
An exception weakens the guard for **both** the static and the forwarded headers. A name lifted here can be set by whoever sends the chat request whenever `COPILOT_FORWARDED_HEADERS` also matches it — lifting `x-api-key` on the direct path, for example, would let a caller replace the server's own API key.
{% endhint %}

## Per-user context

`COPILOT_CUSTOM_CONTEXT` applies to everyone on the instance. When different users need different context, each user can also carry their own in the optional `aiContext` field on the user object. Copilot reads it on every chat request and injects it into the system prompt, so its answers and the flows it builds can take account of who it is helping — their timezone or locale, or the group, property or account that a user stands for.

It is aimed at embedded deployments that provision [virtual users](/tutorials/appmixer-virtual-users.md) through the admin API, where a single Appmixer user often represents a whole customer, site or tenant.

### Setting the field

`aiContext` is written through the [User API](/api/user.md), either when the user is created:

```bash
curl -XPOST "[TENANT_API_URL]/user" \
  -H "Authorization: Bearer [ADMIN_TOKEN]" \
  -H "Content-type: application/json" \
  -d '{
        "username": "tower@acme.example",
        "email": "tower@acme.example",
        "password": "[PASSWORD]",
        "aiContext": "Timezone: Europe/Prague. This user represents Acme Tower, a 40-unit residential building. Flows built here usually notify the building manager."
      }'
```

or later, on an existing user:

```bash
curl -XPUT "[TENANT_API_URL]/users/[USER_ID]" \
  -H "Authorization: Bearer [ADMIN_TOKEN]" \
  -H "Content-type: application/json" \
  -d '{ "aiContext": "Timezone: America/New_York. Represents the Brooklyn site." }'
```

Send `null` or an empty string to clear it:

```bash
curl -XPUT "[TENANT_API_URL]/users/[USER_ID]" \
  -H "Authorization: Bearer [ADMIN_TOKEN]" \
  -H "Content-type: application/json" \
  -d '{ "aiContext": null }'
```

The field is returned by `GET /user`, `GET /users/:userId` and `GET /users`, so a user can read the context that describes them.

{% hint style="warning" %}
Writing `aiContext` always requires an **admin token** — including on `POST /user`, which is open to unauthenticated sign-ups by default. Copilot splices the value into its system prompt, so letting end users set their own would be a prompt-injection vector. A request without the `admin` scope is rejected with `403 Setting aiContext requires the admin scope.`
{% endhint %}

### Limits and precedence

* The value is capped at `USER_AI_CONTEXT_MAX_LENGTH` characters, 8000 by default. The API rejects a longer value outright; Copilot also trims when reading, so an oversized stored value can never bloat the prompt.
* Like `COPILOT_CUSTOM_CONTEXT`, the text is injected as a delimited, lower-authority **User AI Context** section. It can inform Copilot's answers, but cannot change its core role, override its safety or flow-building rules, or make it reveal the system prompt.
* The field is optional and backward compatible: a user without it produces exactly the same prompt as before the field existed.

## Using Copilot in the Designer

Open a flow in the Designer and click the Copilot button to open the chat panel. Type what you want to build or change. Copilot streams its progress, and when it produces a flow, the canvas is updated and reloaded.

* The conversation is retained per flow for the duration of your session, and restored when you re-open the panel.
* Replies are rendered as Markdown.
* The panel is resizable — drag its edge to change the width, or double-click the resize handle to reset it to the default width.
* When editing a **running** flow, Copilot's changes are applied to a draft — publish the draft to apply them (see [Flow Versioning](/api/flows.md#flow-versions)).

### Designer widget options

When embedding the Designer via the [Appmixer UI SDK](/appmixer-ui-sdk/ui-and-widgets/designer.md), the Copilot panel can be configured with the `options.copilot` object:

```javascript
appmixer.ui.Designer({
    el: '#designer',
    flowId: FLOW_ID,
    options: {
        copilot: {
            // Show the built-in, localized example prompts:
            examplePrompts: true

            // — or — provide your own (replaces the defaults):
            // examplePrompts: [
            //     'When a Stripe payment succeeds, add a row to Google Sheets',
            //     'Summarize new Zendesk tickets to Slack every morning'
            // ]

            // Omit or set false to keep them hidden (default).
        }
    }
});
```

## Monitoring AI Usage

The **Insights** menu in Appmixer Studio contains two sections:

* **Flow activity** — the flow execution logs (previously the "Insights" page).
* **AI usage** — Copilot consumption and cost monitoring. This section is visible to administrators only.

The **AI usage** page shows:

* Summary cards with the total spend, the number of requests and the total tokens consumed in the selected period, including an input/output token breakdown.
* Daily consumption for the selected month.
* A paged breakdown table grouped by user, group or scope, with free-text search. Expanding a row drills down into the individual LLM requests (or the users within a group/scope), also paged.

The page is backed by the usage API described below.

## Copilot API

All Copilot endpoints live under the `/system-plugins/copilot` prefix and require a valid access token.

### Chat

<mark style="color:green;">`POST`</mark> `[TENANT_API_URL]/system-plugins/copilot/chat`

Send a chat message. The response is a Server-Sent Events (SSE) stream.

#### Request Body

| Name                                      | Type   | Description                                                                                                             |
| ----------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------- |
| message<mark style="color:red;">\*</mark> | string | The user's message.                                                                                                     |
| threadId                                  | string | Conversation thread ID. Omit to start a new conversation (a new ID is generated and returned in the `connected` event). |
| flowId                                    | string | Optional ID of the flow to give Copilot as context, enabling editing/explaining of that flow.                           |

#### SSE events

`connected` (carries the `threadId`), `text-delta` (streamed assistant text), `tool-call` / `tool-result` / `tool-error` (progress of the assistant's tool use), `flow-result` (the generated flow descriptor with `flowName`, optional `followUpQuestions` and an optional `validationUnavailable` flag, set when flow validation could not be reached — clients can use it to warn that the flow was not validated), `done` (carries token `usage`) and `error`.

### Get Conversation

<mark style="color:blue;">`GET`</mark> `[TENANT_API_URL]/system-plugins/copilot/conversations/:threadId`

Return the transcript of a prior conversation (`{ threadId, messages: [{ role, content }] }`). Users can only read their own threads. Returns an empty message list for a thread the conversation garbage collector has already swept (see `COPILOT_CONVERSATION_RETENTION_DAYS`).

### Enabled Check

<mark style="color:blue;">`GET`</mark> `[TENANT_API_URL]/system-plugins/copilot/enabled`

Returns `{ "enabled": true }` when the Copilot plugin is loaded. Clients use this to decide whether to show Copilot UI.

### Usage

<mark style="color:blue;">`GET`</mark> `[TENANT_API_URL]/system-plugins/copilot/usage`

Aggregated LLM usage and cost. Admin only.

#### Query Parameters

| Name    | Type   | Description                                                                               |
| ------- | ------ | ----------------------------------------------------------------------------------------- |
| from    | string | Start of the period (ISO date). Defaults to the start of the current month.               |
| to      | string | End of the period (ISO date). Defaults to now.                                            |
| groupBy | string | `user` (default), `group` or `scope`.                                                     |
| user    | string | Filter by user ID.                                                                        |
| group   | string | Filter by group ID.                                                                       |
| scope   | string | Filter by scope.                                                                          |
| q       | string | Free-text filter.                                                                         |
| offset  | number | Optional paging offset. Omit both `offset` and `limit` to return the complete result set. |
| limit   | number | Optional page size (must be a positive integer).                                          |

#### Response

The response contains the aggregated `rows` for the requested page, a `totalCount` with the number of rows in the whole filtered set, and `totals` that are always computed over the **full** filtered set — a paged response still describes the whole reporting window, not just the page. Rows are ordered deterministically (request count descending), so pages are stable across requests. When grouping by `group` or `scope`, each row also contains a per-user breakdown.

{% hint style="info" %}
Cost is computed from the model pricing table (see `COPILOT_PRICING_PATH`). Requests made with models that have no pricing entry do not add to the cost — they are reported with their token counts but priced as zero.
{% endhint %}

### Daily Usage

<mark style="color:blue;">`GET`</mark> `[TENANT_API_URL]/system-plugins/copilot/usage/daily`

Daily consumption totals for a month (`month=YYYY-MM`, defaults to the current month; optional `user`, `group`, `scope` filters). Admin only.

### User Usage

<mark style="color:blue;">`GET`</mark> `[TENANT_API_URL]/system-plugins/copilot/usage/user/:userId`

Individual LLM request breakdown for a user (optional `from`/`to`). Admins can view any user; non-admin users can only view their own usage.

Supports the same optional `offset`/`limit` paging parameters as the [Usage](#usage) endpoint and returns a `totalCount` alongside the request rows. Omitting `offset` and `limit` returns the complete list. Rows are ordered by timestamp, newest first.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.appmixer.com/getting-started/ai-copilot.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
