1. SDK
  2. First Message

​
Overview

Call send_message / sendMessage with just a string. A thread and assistant are created automatically. The response includes thread_id and assistant_id so you can continue the conversation or reuse the assistant.

​
Parameters

ParameterTypeDefaultDescription
contentstring—Text content of the message
thread_iduuid—Omit to auto-create a new thread; pass to continue an existing conversation
assistant_iduuid—Pin the new thread to an existing assistant (shares its memory, documents, and config)
system_promptstring—Instructions for this turn. Falls back to the assistant’s stored description if omitted
streambooleanfalseStream response as SSE chunks
llm_providerstring"openai"LLM provider (openai, anthropic, google, xai, openrouter, etc.)
model_namestring"gpt-4o"Model name
memorystring"off"Memory Lite: "Auto", "Readonly", or "off"
memory_prostring—Memory Pro (higher accuracy): "Auto" or "Readonly"
memory_response_citationbooleanfalseInclude memory citations in the assistant reply
web_searchstring"off""Auto" to enable web search
toolsarray—Tool definitions for this turn (OpenAI-style)
json_outputbooleanfalseRequest JSON object output from the model
thinkingobjectnullEnable reasoning. See Thinking guide

​
Non-streaming

  • Python

  • JavaScript

  • TypeScript

import asyncio
from backboard import BackboardClient

async def main():
    client = BackboardClient(api_key="YOUR_API_KEY")

    # Send a message — a thread is auto-created behind the scenes
    response = await client.send_message(
        "Hello! Tell me a fun fact about space.",
        llm_provider="openrouter",
        model_name="moonshotai/kimi-k2.6",
    )

    # The response contains the assistant's reply plus IDs to continue later
    print(f"Reply: {response.content}")
    print(f"Thread ID:    {response.thread_id}")
    print(f"Assistant ID: {response.assistant_id}")

if __name__ == "__main__":
    asyncio.run(main())

​
Streaming

Set stream=true and iterate over SSE chunks as they arrive.

​
SSE event types

Event typeWhen it firesKey fields
run_startedThe run has begunrun_id, message_id, provider, model_name
message_startThe assistant message is about to streammessage_id, run_id
content_streamingEach piece of the assistant’s textcontent
reasoning_streamingEach piece of reasoning (when thinking is enabled)content
reasoning_endedReasoning phase complete, content about to begin—
tool_call_startA tool call’s name is known, arguments still streamingrun_id, tool_call_id, name
tool_call_readyA tool call’s arguments have finished streamingrun_id, tool_call
tool_submit_requiredModel is requesting one or more tool callstool_calls, run_id, model_provider, model_name, usage fields
run_endedThe run finished successfullythread_id, assistant_id, status, usage fields
run_failedThe run failedrun_id, error, error_type
errorThe request could not be processederror

Handle run_failed and error explicitly — a stream that ends without run_ended has failed, and a client that only waits for run_ended will hang.

The SDKs yield every chunk through to you, including event types they do not model explicitly. This table covers the events most clients need; others exist. Switch on chunk["type"] and ignore anything you do not recognize — new event types are additive.

Usage fields on run_ended and tool_submit_required are input_tokens, output_tokens, total_tokens, cached_input_tokens, cache_write_input_tokens, and cost_usd. See Messages for details, and Tool Calls for the early tool-call events.

​
Example: basic streaming

  • Python

  • JavaScript

  • TypeScript

import asyncio
from backboard import BackboardClient

async def main():
    client = BackboardClient(api_key="YOUR_API_KEY")

    full_reply = ""
    async for chunk in await client.send_message(
        "Tell me a fun fact about space.",
        stream=True,
    ):
        event_type = chunk.get("type")

        if event_type == "content_streaming":
            # Print each token as it arrives
            piece = chunk.get("content", "")
            full_reply += piece
            print(piece, end="", flush=True)

        elif event_type == "run_ended":
            # Final metadata is available here
            print(f"\n\nTokens used: {chunk.get('total_tokens')}")

    print(f"\nFull reply: {full_reply}")

if __name__ == "__main__":
    asyncio.run(main())

​
Example: streaming with thinking (reasoning)

When thinking is enabled, the stream emits reasoning tokens before content tokens:

async for chunk in await client.send_message(
    "What is the derivative of x^3 * sin(x)?",
    llm_provider="openai",
    model_name="o3",
    thinking={"effort": "high"},
    stream=True,
):
    event_type = chunk.get("type")

    if event_type == "reasoning_streaming":
        # The model's internal reasoning — show it or hide it
        print(chunk.get("content", ""), end="", flush=True)

    elif event_type == "reasoning_ended":
        print("\n--- reasoning done, answer starting ---")

    elif event_type == "content_streaming":
        print(chunk.get("content", ""), end="", flush=True)

​
Continuing a conversation

Pass thread_id from the first response to continue on the same thread:

  • Python

  • JavaScript

  • TypeScript

# First message — thread auto-created
first_reply = await client.send_message("What is quantum computing?")

# Follow-up on the same thread — the model sees prior history
follow_up = await client.send_message(
    "Can you explain that more simply?",
    thread_id=first_reply.thread_id,
)
print(follow_up.content)

​
Using an assistant

Pass assistant_id to pin the conversation to a specific assistant. New threads created under that assistant share its memory, documents, and default configuration:

  • Python

  • JavaScript

  • TypeScript

# Tell the assistant something — memory saves it
first = await client.send_message(
    "My name is Sarah and I prefer dark mode.",
    assistant_id="your-assistant-id",
    memory="Auto",
)

# New thread, same assistant — memory carries over
second = await client.send_message(
    "What do you remember about me?",
    assistant_id="your-assistant-id",
    memory="Auto",
)
print(second.content)  # "You told me your name is Sarah and you prefer dark mode."

​
Per-turn system prompt

Pass system_prompt to give the model instructions for this turn:

reply = await client.send_message(
    "What should I eat today?",
    system_prompt="You are a nutritionist who gives concise meal suggestions.",
)
print(reply.content)

If you omit system_prompt, the assistant’s stored description (set via create_assistant or update_assistant) is used as fallback.

​
Switching models mid-conversation

You can use a different model for each message within the same thread:

# Turn 1 with OpenAI
first = await client.send_message(
    "What is quantum computing?",
    llm_provider="openai",
    model_name="gpt-4o",
)

# Turn 2 with Anthropic — same thread, different model
second = await client.send_message(
    "Can you explain that more simply?",
    thread_id=first.thread_id,
    llm_provider="anthropic",
    model_name="claude-sonnet-4-20250514",
)

​
Thinking (reasoning)

Enable the model to reason step-by-step before answering:

response = await client.send_message(
    "What is the derivative of x^3 * sin(x)?",
    llm_provider="openai",
    model_name="o3",
    thinking={"effort": "high"},
)
print("Reasoning:", response.reasoning)
print("Answer:", response.content)

Different providers use different fields (e.g. effort for OpenAI, budget_tokens for Anthropic). See the full Thinking guide for every provider.

​
Response fields (non-streaming)

FieldDescription
thread_idThread UUID (auto-created or existing)
assistant_idAssistant UUID used
contentThe assistant’s reply
reasoningThe model’s reasoning trace (when thinking is enabled)
message_idUnique message identifier
statusCOMPLETED, REQUIRES_ACTION (tool calls pending), FAILED
tool_callsArray of tool call objects (when REQUIRES_ACTION)
model_provider / model_nameModel used
input_tokens / output_tokens / total_tokensToken usage
memory_operation_idPoll this to check memory save status
retrieved_memoriesMemories used as context
retrieved_files / retrieved_files_countDocuments used as context
context_usageContext window utilization (percent, used_tokens, context_limit)

Legacy approach: The older add_message / addMessage methods (which require a pre-created assistant and thread) are still fully supported. See Create & update assistants for that flow.