# AI

Part of AppEngine API Documentation. Full index: https://appengine.appmint.io/documentation.md
## GET /ai/mcp/tools

**List MCP tools**

`operationId: AIController_getMcpTools`

The tools the model can call against this org's data. Read this to know what a chat turn is capable of doing — the list is the blast radius.

#### Signature

```http
GET /ai/mcp/tools () -> { tools }
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /ai/mcp/execute`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | { tools } |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /ai/mcp/execute

**Execute an MCP tool**

`operationId: AIController_executeMcpTool`

Runs one tool directly, without going through the model. Useful for testing what a tool actually does before letting a conversation call it.

Tools act on real org data: a write tool writes. The arguments are passed to the tool as given.

#### Signature

```http
POST /ai/mcp/execute (body) -> The tool result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Tools can modify org data.
- A missing `toolName` fails as a server error (500), not a 400.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /ai/mcp/tools`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The tool and its arguments.

```json
{
  "toolName": "search_customers",
  "args": {
    "query": "ada"
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The tool result |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /ai/chat

**Chat with AI**

`operationId: AIController_chat`

A single chat turn. Files attached under `files` are processed and made available to the model, so a document can be asked about directly.

Two ways to carry history: send prior turns in `conversationHistory`, or pass a `conversationId` and let the server hold the memory. The second is cheaper — `conversationHistory` is re-sent and re-billed on every call.

`availableTools` narrows what the model may call; leaving it out means the full MCP tool set is in play.

#### Signature

```http
POST /ai/chat (body) -> The chat response
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Consumes billable AI credit.
- Tool calls can modify org data.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /ai/agent/chat`
- `POST /ai/agent/stream`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The chat request.

```json
{
  "task": "Summarise last month's refunds."
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The chat response |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /ai/agent/chat

**Chat with an AI agent**

`operationId: AIController_agentChat`

A chat turn where the caller chooses the role the assistant plays (`agentRole`, or a role named in `aiMode`) and can add system instructions (`agentInstructions`). Same file handling and memory options as `POST /ai/chat`.

#### Signature

```http
POST /ai/agent/chat (body) -> The chat response
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Consumes billable AI credit.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /ai/chat`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The chat request, plus the role and instructions.

```json
{
  "agentRole": "chat",
  "agentInstructions": "Answer as the support desk.",
  "task": "Draft a reply to ticket 4821."
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The chat response |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /ai/ui/ack

**Confirm a UI action landed**

`operationId: AIController_uiActionAck`

The client telling the server that a UI action the assistant asked for (e.g. `navigate`) actually happened. The waiting tool call is released with what the client reports, so the assistant can say "done" rather than "dispatched, not confirmed". Post it once the route is mounted or the change is made; `result` carries what the client did (e.g. ids of objects it added) back to the model.

#### Signature

```http
POST /ai/ui/ack (body) -> { received: true }
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /ai/agent/stream`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

What happened.

```json
{
  "conversationId": "CONV-4821",
  "action": "navigate",
  "route": "/crm/leads",
  "ok": true
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | { received: true } |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

Example response:

```json
{
  "received": true
}
```

## POST /ai/agent/stream

**Start a streaming chat**

`operationId: AIController_chatStream`

Starts a streaming response and returns a **stream id** — it does not return the text. Connect to `GET /ai/stream/{streamId}` to read it as it arrives.

The two-step shape exists because the generation outlives the request; a client that only calls this and never connects has still paid for the generation.

With a `conversationId`, the history is the **server's**: the last 20 messages of that conversation replace any `conversationHistory` sent. The user message and the reply are saved to the conversation. Tool calls the assistant makes run as the caller (their bearer token), so their own permissions apply.

The org needs a subscription or a positive balance to start.

#### Signature

```http
POST /ai/agent/stream (body) -> { streamId }
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Consumes billable AI credit.
- Returns a stream id, not the response.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `402` | INSUFFICIENT_BALANCE | Insufficient balance to use AI services | The org has no subscription and not enough balance for the minimum estimated cost. | Add credits (billingUrl) and retry. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /ai/stream/{streamId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `authorization` | header | string | yes |  |

### Request body

The chat request.

```json
{
  "task": "Write a summary of this quarter.",
  "conversationId": "CONV-4821"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | { streamId } |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `402` | Insufficient balance to use AI services — The org has no subscription and not enough balance for the minimum estimated cost. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

Example response:

```json
{
  "streamId": "STR-4821"
}
```

## GET /ai/stream/{streamId}

**Read a stream**

`operationId: AIController_streamEvents`

A **Server-Sent Events** connection to a stream started by `POST /ai/agent/stream`. The first event is `data` with `{ type: "status", status: "connected" }`; chunks already produced are then **replayed**, so a client that reconnects does not lose the start. Events: `data` (a chunk as JSON), `end` (done), `error` (`{ message }` — the provider's own reason when it refused).

An unknown stream id, or a missing `orgid` header, answers with a single `error` event and closes — not an HTTP error status.

#### Signature

```http
GET /ai/stream/{streamId} (streamId: string) -> The event stream
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /ai/agent/stream`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `streamId` | path | string | yes | Stream id from the stream start call. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The event stream |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /ai/generate/image

**Generate an image**

`operationId: AIController_generateImage`

One endpoint covering three modes, chosen by what is in the body:

- **From a prompt** — `prompt` alone creates a new image.
- **With references** — `prompt` plus `images` guides generation from existing pictures.
- **Inpainting** — `prompt` plus an image and a `mask` edits the masked region.

This replaces the deprecated variations and edit endpoints; new code should use this one.

#### Signature

```http
POST /ai/generate/image (body) -> The provider response plus `files` (the images saved to the org's storage) and a `message`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Consumes billable AI credit.
- Each image in `n` is billed separately.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | IMAGE_GENERATION_FAILED | <the provider's reason> | Any failure — including "No AI provider found" and a provider that does not make images — comes back as a 400 carrying the reason. | — |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /ai/generate/video`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The generation request.

```json
{
  "prompt": "A cola bottle on a marble counter, studio lighting",
  "size": "1024x1024"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The provider response plus `files` (the images saved to the org's storage) and a `message` |
| `400` | <the provider's reason> — Any failure — including "No AI provider found" and a provider that does not make images — comes back as a 400 carrying the reason. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /ai/generate/image/variations

**Generate image variations (deprecated)**

> **Deprecated.**

`operationId: AIController_generateImageVariations`

Deprecated. Use `POST /ai/generate/image` with `images` as references — it covers this and more. Kept for existing callers.

#### Signature

```http
POST /ai/generate/image/variations (body) -> The variations
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Deprecated.
- Consumes billable AI credit.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /ai/generate/image`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The request.

```json
{
  "path": "uploads/ref.jpg",
  "n": 2
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The variations |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /ai/generate/image/edit

**Edit an image (deprecated)**

> **Deprecated.**

`operationId: AIController_editImage`

Deprecated. Use `POST /ai/generate/image` with a `mask` for inpainting. Kept for existing callers.

#### Signature

```http
POST /ai/generate/image/edit (body) -> The edited image
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Deprecated.
- Consumes billable AI credit.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /ai/generate/image`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The request.

```json
{
  "prompt": "Plain white label",
  "imagePath": "uploads/bottle.jpg",
  "maskPath": "uploads/bottle-mask.png"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The edited image |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /ai/generate/video

**Generate a video**

`operationId: AIController_generateVideo`

Generates video from a prompt with a provider that supports video. The request waits for the result, which is slow — considerably slower and more expensive than an image. Each video returned is saved to the org's storage.

#### Signature

```http
POST /ai/generate/video (body) -> The provider response plus `files` (the saved videos) and a `message`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Consumes billable AI credit.
- Slow and comparatively expensive.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | VIDEO_GENERATION_FAILED | <the provider's reason> | No AI provider, a provider that does not support video ("Provider <name> does not support video generation"), or the provider refused. | — |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /ai/generate/image`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The generation request, passed to the provider.

```json
{
  "prompt": "A slow pan across a city skyline at dusk"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The provider response plus `files` (the saved videos) and a `message` |
| `400` | <the provider's reason> — No AI provider, a provider that does not support video ("Provider <name> does not support video generation"), or the provider refused. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

