/v1/me/ai/conversationsMakes a conversation, stores the question in it and queues the answer. The
response comes back at once, before the assistant has written a word: the
question as it was stored, and the answer as an empty message with status
streaming.
Draw both straight away, subscribe to the conversation's channel, and let
the websocket events
fill the answer in. Match them by the answer's id, which is the message_id
of every turn_state.
Request
curl -X POST https://api.proppertrading.com/v1/me/ai/conversations \
-H "Authorization: Bearer ptat_9f2c1a44_3b8e7d2f5c9a1b4e6d8f0a2c4e6b8d1f" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6c1f0d52-3a8e-4b7d-9e21-5f4a8c3b2d10" \
-d '{
"message": "What is the maximum daily loss on my account?",
"attachments": [
{ "name": "chart.png", "data": "iVBORw0KGgoAAAANSUhEUgAAAAgAAAAI..." }
]
}'
| Field | Required | Description |
|---|---|---|
message | yes | The question, at most 4000 characters |
attachments | no | Up to five files |
attachments[].name | yes | The file's name as the person sees it, at most 255 characters. Never used as a path on our side |
attachments[].data | yes | The file, base64. At most 10 MB once decoded. JPEG, PNG, GIF, WebP, PDF, plain text or CSV, worked out from the bytes |
Response
201:
{
"data": {
"object": "ai_turn",
"conversation": {
"id": "01a16152-7e42-70cd-91a4-b6b80ee47daa",
"object": "ai_conversation",
"title": null,
"pinned": false,
"surface": "trader_chat",
"channel": "private-ai-chat.01a16152-7e42-70cd-91a4-b6b80ee47daa",
"last_message_at": "2026-10-22T01:55:58+00:00",
"created_at": "2026-10-22T01:55:58+00:00"
},
"question": {
"id": "aimsg_dkr8n8ctvh9qtmce",
"object": "ai_message",
"role": "user",
"content": "What is the maximum daily loss on my account?",
"status": "complete",
"attachments": [
{
"index": 0,
"name": "chart.png",
"content_type": "image/png",
"size_bytes": 90,
"url": "https://api.proppertrading.com/v1/me/ai/messages/aimsg_dkr8n8ctvh9qtmce/attachments/0"
}
],
"actions": [],
"created_at": "2026-10-22T01:55:58+00:00"
},
"answer": {
"id": "aimsg_tjqupx579w3cpx9m",
"object": "ai_message",
"role": "assistant",
"content": "",
"status": "streaming",
"attachments": [],
"actions": [],
"created_at": "2026-10-22T01:55:58+00:00"
}
},
"meta": {
"request_id": "req_01m5gn4zh3jdhfxy2bxdyg8q29"
}
}
A message is the same shape everywhere:
| Field | Description |
|---|---|
id | A public id such as aimsg_dkr8n8ctvh9qtmce |
role | user for the question, assistant for the answer |
content | The text. Markdown in an answer |
status | complete, streaming, cancelled or failed |
attachments | The files on a question. content_type is the image type for an image and application/octet-stream for anything else. url downloads it with the same token |
actions | The action cards an answer proposed, oldest first, as they stand now. Always empty on a question |
What the answer uses counts against the firm's AI allowance and, for a trader, against a limit per day when the firm set one. A stopped answer counts for what it wrote.
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_attachment | A file's data is not base64, or it is empty. param says which, such as attachments.0.data |
| 400 | attachment_too_large | A file over 10 MB once decoded |
| 400 | missing_idempotency_key | Every POST needs one |
| 400 | workspace_required | Our own staff, without a firm in Propper-Workspace |
| 403 | ai_unavailable | The firm has the assistant switched off for this chat |
| 403 | ai_kyc_required | A trader whose account is locked until the identity check is done |
| 403 | forbidden | Staff whose role does not include use_ai_chat |
| 403 | insufficient_scope | This device does not hold ai:write |
| 422 | validation_failed | No message, one over 4000 characters, more than five files, or a file of a type that is not allowed |
| 429 | ai_limit_reached | An allowance is used up. state says which and resets_at when it comes back |
| 429 | rate_limit_exceeded | Twenty questions and deletions a minute, shared with the website |
A refusal from the allowance looks like this:
{
"error": {
"type": "rate_limit_error",
"code": "ai_limit_reached",
"message": "You have asked as many questions today as this firm allows. Ask again after resets_at.",
"doc_url": "https://proppertrading.com/docs/api/errors#ai_limit_reached",
"state": "daily_limit",
"resets_at": "2026-10-23T00:00:00+02:00",
"request_id": "req_01kz4bkr1aj1gv63d8fv3133ey"
}
}
state is session or week for the firm's AI allowance, none for a
package without AI, platform when our own capacity is used up, and
daily_limit for the limit per trader the firm set. resets_at is left out
when waiting will not help.