Skip to main content
A single POST /chat/completions call is one turn. To hold a conversation — ask a follow-up, refine a draft, react to what the model said — you continue the same chat across turns by passing its chat_id.

How it works

Every completion returns a chat_id in its result envelope. Pass that value back as chat_id on your next request and the new message is appended to the same conversation:
# Turn 1 — omit chat_id to start a new chat
curl -X POST https://app.gc.ai/api/external/v1/chat/completions \
  -H "Authorization: gcai_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "message": "Summarize the indemnification clause in plain English." }'
# → result.chat_id = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"

# Turn 2 — pass chat_id to continue
curl -X POST https://app.gc.ai/api/external/v1/chat/completions \
  -H "Authorization: gcai_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
        "chat_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "message": "Now redline it to be more favorable to the buyer."
      }'
The model sees the full prior conversation on turn 2 — the earlier messages, any tool calls it made, and the files you attached — so the follow-up can refer back to “it” without restating context.

State lives on the server

Conversation state is stored server-side and reconstructed for you on every turn. Send only the new message (plus any new file_ids) — never re-send prior turns. In particular, you do not echo back anything the API returned to you:
  • Files stay attached. A file you sent with file_ids on an earlier turn remains available; you don’t re-attach it.
  • Generated outputs are remembered. Documents, emails, and diagrams returned on result.documents / result.emails / result.diagrams are already persisted to the chat. The model can refer to and build on them without you sending them back. Those response fields are there for your convenience — to download or display — not state you must round-trip.
This keeps requests small and avoids the drift that comes from a client reassembling history by hand.

One turn at a time

Each turn runs as an async job. Only one turn may be in flight per chat: if you send a new message while a prior turn is still pending or running, the request is rejected with 409. Wait for the prior turn to reach a terminal state (succeeded / failed) before sending the next. This prevents two turns from racing on the same message history. If you long-poll (wait > 0) you’ll usually have the result before you send the next turn; with fire-and-forget (wait=0) poll GET /jobs/{id} until the turn is terminal first.

Who can continue what

Continuing a chat is scoped exactly like materialization, and only API-created chats can be continued:
Key typeCan continue
Personal (u:gcai_…)Only chats it created.
Organization (gcai_…)Only chats created by other organization-scoped keys.
A chat_id that doesn’t exist (or that this key can’t reach) returns 404; a chat owned by a different scope returns 403; a chat that wasn’t created through the API returns 409. Continuing a chat does not materialize it — the conversation stays headless until you explicitly materialize it.