---
title: "Send message (OpenAI format)."
method: POST
path: "/api/v1/projects/{projectId}/chat/completions"
tags: ["Conversations"]
---

# Send message (OpenAI format).

`POST /api/v1/projects/{projectId}/chat/completions`

## Request

- Base URL: `https://app.customgpt.ai`
- URL: `https://app.customgpt.ai/api/v1/projects/{projectId}/chat/completions`
- Auth: HTTP bearer
- TypeScript: <https://skmtc.dev/poll-the-people/apis/customgpt-ai/docs/api/v1/projects/:projectId/chat/completions/post.ts> (a function with typed inputs; its response is checked against the schema)

Sends a message using the OpenAI-compatible request and response format. Use this if you are migrating from OpenAI or want to use existing OpenAI client libraries. Supports streaming for real-time responses.

Notes: This is a convenience endpoint for OpenAI compatibility. For full CustomGPT features, use the native message endpoint.

## Path parameters

- `projectId` integer, required

## Request body

- object
  - `messages` object[]: The messages to send to the agent
    - `role` 'system' | 'user' | 'assistant'
    - `content` string
  - `model` string, nullable: ID of the model to use
  - `stream` boolean: Whether to stream the response
  - `lang` string: Response language
  - `external_id` string, nullable: External identifier
  - `is_inline_citation` boolean: Whether to include inline citations
  - `labels` union[], nullable: Optional source-label filter. Accepts two formats: (1) Flat array of label IDs or names for OR semantics, e.g. ["A","B","C"]. (2) Array of arrays for AND/OR logic (inner=OR, outer=AND), e.g. [["A","B"],["C","D"]] means (A OR B) AND (C OR D). Max 20 groups, 50 labels per group, 500 total. Invalid values are ignored.
    - union
      - string
      - string[]
  - `labels_exclusive` boolean: If true, search only pages with provided labels. If false, search pages with provided labels plus unlabeled pages.
  - `sub_personas` object, nullable: Request-only sub-persona overrides keyed by sub-persona slug. Omitted keys use the saved generated/project value.
    - `persona-summary` string, nullable
    - `task-planner` string, nullable

## Response `200`

OpenAI-compatible chat completion response.

**Non-streaming** (`stream: false` or omitted): Returns a single JSON object with the complete response.

**Streaming** (`stream: true`): Returns Server-Sent Events (SSE) with `text/event-stream` content type.

Each SSE event is prefixed with `data: ` followed by a JSON object:

```
data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1743037634,"model":"gpt-4","choices":[{"index":0,"delta":{"content":"Hello"},"finish_reason":null}]}
```

The stream ends with:
```
data: [DONE]
```

**Key fields per chunk:**
- `id`: unique completion ID
- `object`: always `chat.completion.chunk`
- `created`: Unix timestamp
- `choices[0].delta.content`: incremental text fragment
- `choices[0].finish_reason`: `null` during stream, `stop` on final chunk

**JavaScript client example:**
```javascript
const response = await fetch(
  'https://app.customgpt.ai/api/v1/projects/{projectId}/chat/completions',
  {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      messages: [{ role: 'user', content: 'Hello' }],
      stream: true
    })
  }
);

const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  const lines = decoder.decode(value).split('\n');
  for (const line of lines) {
    if (!line.startsWith('data: ')) continue;
    const payload = line.slice(6);
    if (payload === '[DONE]') break;

    const chunk = JSON.parse(payload);
    const text = chunk.choices[0]?.delta?.content || '';
    process.stdout.write(text);
  }
}
```

- object
  - `id` string
  - `object` string
  - `created` integer
  - `model` string, nullable
  - `usage` object
    - `prompt_tokens` integer
    - `completion_tokens` integer
    - `total_tokens` integer
    - `completion_tokens_details` object
      - `reasoning_tokens` integer
      - `accepted_prediction_tokens` integer
      - `rejected_prediction_tokens` integer
  - `choices` object[]
    - `message` object
      - `role` string
      - `content` string
    - `logprobs` string, nullable
    - `finish_reason` string
    - `index` integer

## Other responses

- `400`: Bad Argument
- `401`: API Token is either missing or invalid
- `404`: Agent not found
- `429`: Resource Exhausted when sending a message to a conversation
- `500`: Internal Server Error
- `501`: Unsupported Argument

## Changes

- **2026-11-03** `77aab4354382`: 6 warning, 4 info
  - added the new `422.00` enum value to the `data/code` response property for the response status `400`
  - added the new `422.00` enum value to the `data/code` response property for the response status `401`
  - added the new `422.00` enum value to the `data/code` response property for the response status `404`
  - added the new `422.00` enum value to the `data/code` response property for the response status `429`
  - …6 more
- **2026-07-17** `b80e6dfcf78d`: 1 info
  - endpoint added
- **2023-06-02** `1842d7fd0430`: 1 breaking
  - api path removed without deprecation

[Operation history](https://skmtc.dev/poll-the-people/apis/customgpt-ai/changes/api/v1/projects/:projectId/chat/completions/post.md)

---

[API](https://skmtc.dev/poll-the-people/apis/customgpt-ai.md) - [All operations](https://skmtc.dev/poll-the-people/apis/customgpt-ai/llms.txt) - [OpenAPI document](https://skmtc.dev/poll-the-people/apis/customgpt-ai/revisions/77aab4354382?raw)
