Skip to main content
This documentation is for Wise Wolf release (February 2026) and later. If you are on Unique Urchin (December 2025) or Victorious Vicuna (January 2026), please refer to the legacy agentic threads documentation.

Motivation

The Thread API, allows you to engage in conversations leveraging multi-tool reasoning capabilities and using specialized agents. It is the recommended way to interact with Paradigm tools.

Terminology

Turn

A single interaction between a user and the model, comprising a user query, multi-steps reasoning, tool calls and the model’s final answer.

Thread

A conversation comprising a sequence of turns.

Parts

A component of the conversation turn’s answer, it can be of type reasoning, tool_call or text (final answer to the user). The parts in an agent message within a turn are structured in the following sequence:
  • a reasoning part explaining the reasoning about wether the agent will choose tyo use a tool or return the final answer.
  • a tool_call part containing information about the tool called as well as the tool’s raw result.
  • repeat the 2 first steps until the agent choose to return the final answer or the reasoning budget is reached.
  • a text part containing the final answer.

Message

A set of parts corresponding to, within a turn, either the agent answer or the user query. A turn is thus primarily composed of a list of 2 messages: the user query and the agent answer.

Source

A source used by a tool to generate the turn’s final answer, it can either of type web or document.

Artifact

A file generated by a tool, attached to a turn.

Quickstart

You can initialize a new conversation thread using the POST /api/v3/threads/turns endpoint.
The payload field chat_setting_id has been deprecated and now the use of agent_id is preffered, for back compatibility issues chat_setting_id is still supported. Note that if you specify both agent_id and chat_setting_id, the agent_id field will take precedence.
import os
import requests

# Get API key from environment
api_key = os.getenv("PARADIGM_API_KEY")
# Get base URL from environment (defaults to public instance)
base_url = os.getenv("PARADIGM_BASE_URL", "https://paradigm.lighton.ai/api/v3")

response = requests.post(
    f"{base_url}/threads/turns",
    headers={"Authorization": f"Bearer {api_key}"},
    json={
        "agent_id": 1,
        "query": "What is the capital of France?"
    }
).json()
While specifying:
  • agent_id (optional): the ID of the Agent to use to perform this query, if left blank, your company’s default agent will be used. You can list the available agents using the [GET /api/v3/agents](api-reference-v3/threads/ endpoint.
  • query: the user query.
{
  "id": "9339cf12-8530-421d-8dda-79cd3016a182",
  "object": "turn",
  "thread": "783b089b-0ecc-496b-b0c3-70d0f327d9b8",
  "status": "completed",
  "messages": [
    {
      "id": "2951681d-9477-4e8d-889b-0f63bef890f6",
      "object": "message",
      "role": "user",
      "parts": [
        {
          "type": "text",
          "text": "What is the capital of France?"
        }
      ],
      "created_at": "2025-12-16T16:01:53.806331Z"
    },
    {
      "id": "5ec22b40-ba6c-40ab-b94d-97fc05d5c142",
      "object": "message",
      "role": "assistant",
      "parts": [
        {
          "type": "reasoning",
          "reasoning": "Basic factual question - no tools needed."
        },
        {
          "type": "text",
          "text": "La capitale de la France est Paris."
        }
      ],
      "created_at": "2025-12-16T16:01:56.210968Z"
    }
  ],
  "created_at": "2025-12-16T16:01:53.804779Z"
}
In this case the agent answer on this turn is made of of two parts:
  • a reasoning part explaining the reasoning behind the answer.
  • a text part containing the final answer.
Note that the last part of the agent answer is always of type text and consitute the final answer and that the agent answer is always the second and last message.
If the turn takes too long to generate you will receive an HTTP 202 response with the created thread ID (tread) and the turn ID (turn_id) in the payload. You can then use the GET /api/v3/threads/:id/turns/:turn_id endpoint to poll for the status until it is in state completed and then retrieve the final answer.
Alternatively you can skip the waiting and use Background Mode to generate the turn in the background.
You can access the agent final answer like this:
# Minimal structure example to illustrate access pattern
response = {
    "messages": [
        {"parts": []},
        {"parts": [{"type": "text", "text": "Paris"}]}
    ]
}
answer: str = response["messages"][-1]["parts"][-1]["text"]
You can also retrieve the thread_id like this:
response = {"thread": "783b089b-0ecc-496b-b0c3-70d0f327d9b8"}
thread_id: str = response["thread"]
And then you can follow-up on the same conversations using the POST /api/v3/threads/:id/turns endpoint to create a new conversation turn.

Recipes

For the following recipes, you can either use:
While the POST /api/v3/threads/turns endpoint provides a shortcut to directly create a thread with a turn, it does not support streaming mode, if you want to use streaming then create a thread using the POST /api/v3/threads endpoint and then create a turn in this thread using the POST /api/v3/threads/:id/turns while specifying stream=true.
In those examples we will use the method involving using a new thread, but both endpoints take the same payload and return the same response schema. It will also be assumed that you parse the API response into a Python dictionary, like done in the Quickstart section.
For single call usage in workflows it is recommented to use the first endpoint, creating a fresh thread per turn.

Scoping by file, workspaces or tags

You can scope a query within a list of Worspkaces and/or Tags or Files (documents) using the workspace_ids, tag_ids or file_ids parameters in the payload. For instance, using the POST /api/v3/threads/turns endpoint for scoping for files within workspaces of id 1 or 2 and that are attached to tags of id 4 or 5:
{
  "agent_id": 1,
  "query": "What is the conclusion of the last quaterly meeting note?",
  "workspace_ids": [1, 2],
  "tag_ids": [4, 5],
}
Same here for scoping a query to files of id 8 and 9:
{
  "agent_id": 1,
  "query": "What is the conclusion of the last quaterly meeting note?",
  "file_ids": [8, 9],
}
While you can mix workspace_ids and tag_ids, you cannot mix either of these with file_ids.
The agent_id field is optional, if omitted the default agent of your company will be used. You can list available agents using the GET /api/v3/agents endpoint.
It is recommended to not force a tool when scoping, as the automatic routing will ensure the optimal tool for your type of file(s) is used.
  • You can retrieve the list of workspaces available for you when using a specific agent by calling the GET /api/v3/agents/:id endpoint and use the workspaces field of the component.
  • You can retrieve the list of files available for you when using a specific agent by calling the GET /api/v3/agents/:id/files endpoint.
  • You can retrieve the list of tags available for you when using a specified agent by calling the GET /api/v3/agents/:id/tags endpoint.
If you are not specifying a particular agent then you are using your company’s default agent, you can retrieve it by calling the GET /api/v3/agents?is_default=true endpoint (if you are sys-admin you might want to use the additional query parameter filter &company_id=:id with your company id).

Using a specific tool

Call the POST /api/v3/threads/turns endpoint, while specifying the tool name to use in the payload like this:
{
  "agent_id": 1,
  "query": "What is the conclusion of the last quaterly meeting note?",
  "force_tool": "document_search"
}
The agent_id field is optional, if omitted the default agent of your company will be used. You can list available agents using the GET /api/v3/agents endpoint.
Forcing a tool when working with documents is not recommended, prefer scoping files/workspaces to your query and let the automatic routing decides which tool is the best for your file(s).
You can list the available native tools available for the agent you are using through the GET /api/v3/agents/:id/tools endpoint.
Ensure the selected native tool is enabled for the agent you are using, you can check by calling the GET /api/v3/agents/:id endpoint, if you are not specifying any agent then you can check using the GET api/v3/agents?is_default=true endpoint (if you have a sys-admin role you might want to use the ?company_id=:id with your company id).
As seen in the Quickstart section, the agent answer is the last message of the turn. You can then retrieve the final answer like this:
# Minimal structure example to illustrate access pattern
response = {
    "messages": [
        {"parts": []},
        {"parts": [{"type": "text", "text": "Paris"}]}
    ]
}
answer: str = response["messages"][-1]["parts"][-1]["text"]
In this scenario the first part of the agent answer is a tool_call part. It contains information about the tool called as well as the tool’s raw result.
Note that since the tool was forced to a specific value, this turn won’t contain a reasoning part.
You can retrieve it like this:
tool_call: dict = response["messages"][-1]["parts"][0]

Using a specific MCP server

If you want to restrict tool routing to the tools provided by a specific MCP server, call the the POST /api/v3/threads/turns endpoint, while specifying the MCP server name in the payload like this:
Ensure the selected tool is enabled in the Agent MCP Servers section of your Chat Settings.
{
  "agent_id": 1,
  "query": "What are the latest news about LightOn ?",
  "force_mcp_server": "websearch-linkup"
}
The agent_id field is optional, if omitted the default agent of your company will be used. You can list available agents using the GET /api/v3/agents endpoint.
It will restrain the tool routing to the tools provided by the MCP server named websearch-linkup attached to the company linked to the API key used.
Note that even when forcing a specific MCP server, the model can still choose to use none of its tools if it believes it knows the answer.

Extending the system prompt

You can extend the system prompt during one query and pass specific instructions using the system_prompt_suffix parameter of the payload. For instance, using the POST /api/v3/threads/turns endpoint:
{
  "agent_id": 1,
  "query": "What is the conclusion of the last quaterly meeting note?",
  "force_tool": "document_search",
  "system_prompt_suffix": "Rephrase technical term into more accessible language."
}
The agent_id field is optional, if omitted the default agent of your company will be used. You can list available agents using the GET /api/v3/agents endpoint.
Using this method rather than adding more instructions in your query allow to tune the agent behaviour while ensuring an optimal search accuracy for your query in the case of document_search or document_analysis tools.

Requesting structured output

You can request structured output from the agent by specifying the response_format parameter in the payload. For instance, using the POST /api/v3/threads/turns endpoint:
{
  agent_id: 1,
  "query": "What is the capital of France?",
  "response_format": {
    "type": "object",
    "properties": {
      "capital": {
        "type": "string"
      },
      "country": {
        "type": "string"
      }
    },
    "required": [
      "capital",
      "country"
    ]
  }
}
The agent_id field is optional, if omitted the default agent of your company will be used. You can list available agents using the GET /api/v3/agents endpoint.
For more information about response_format, please consult the Guided JSON documentation.

Generating artifacts

Some tools like code_execution can generate artifacts that can be downloaded afterwards. For instance, when using the POST /api/v3/threads/turns endpoint:
{
  "agent_id": 1,
  "query": "Draw me a graph of a sinusoidal function.",
  "force_tool": "code_execution"
}
The agent_id field is optional, if omitted the default agent of your company will be used. You can list available agents using the GET /api/v3/agents endpoint.
It will result into an agent answer consisting of 2 parts:
  • a tool_call part in which you can find the generated artifacts.
  • a text part containing the final answer.
You can then retrieve the artifacts like this:
artifacts: list[dict] = response["messages"][-1]["parts"][0]["tool_call"]["result"]["file_artifacts"]
Each file artifact contains the following fields:
  • id: the ID of the artifact.
  • thumbnail_base64: a base64 encoded 256x256 webp thumbnail of the artifact if the artifact is an image.
Once you have the artifact ID you can retrieve the artifact content using the GET /api/v3/artifacts/:id/content endpoint.

Background mode

For heavy query that needs to be handled asynchronously, you can use the POST /api/v3/threads/turns endpoint with the background parameter set to true. For instance:
{
    "agent_id": 1,
    "query": "What is the capital of France?",
    "background": true
}
The agent_id field is optional, if omitted the default agent of your company will be used. You can list available agents using the GET /api/v3/agents endpoint.
You will receive an HTTP 200 response with the thread object but containing only the user query, notice how the status field is set to running:
{
    "id": "6d0d54c3-a87b-4c66-8af2-8ef59418358e",
    "object": "turn",
    "thread": "12df8e86-e8b0-49ee-8634-f5ce8944591c",
    "status": "running",
    "messages": [
      {
        "id": "22461762-afd8-4533-8cf2-2e7d516a38d6",
        "object": "message",
        "role": "user",
        "parts": [
          {
            "type": "text",
            "text": "What is the capital of France?"
          }
    ],
    "created_at": "2025-12-17T14:46:00.703903Z"
    }
  ],
  "created_at": "2025-12-17T14:46:00.702207Z"
}
You can then retrieve the thread id in the following way:
thread_id: str = response["thread"]
You can now poll periodically the GET /api/v3/threads/:id endpoint until the status field is set to completed. When the thread is back in its completed status, you can fetch its turns using the GET /api/v3/threads/:id/turns endpoint.
To only retrieve the last turn, you can set the limit query parameter to 1.