Send Async Messages

Su Suriya Ravichandran Updated 10 Nov 2026
7 min read ·Lesson 73 of 200

The Basic Pattern

Sending a message with the async client is the same as with the sync client, plus await:

import asyncio

from anthropic import AsyncAnthropic


async def main() -> None:
    async with AsyncAnthropic() as client:
        message = await client.messages.create(
            model="claude-opus-5-5",
            max_tokens=1024,
            system="You are a concise programming tutor.",
            messages=[{"role": "user", "content": "What is a Python decorator? Two sentences."}],
        )

    print("".join(b.text for b in message.content if b.type == "text"))
    print(f"[stop_reason={message.stop_reason}, output_tokens={message.usage.output_tokens}]")


asyncio.run(main())

await client.messages.create(...) returns the same Message object as the sync client (Lesson 14), so everything you know about reading responses applies: content blocks, stop reasons, usage, and IDs.


What await Does Here

coroutine = client.messages.create(...)   # 1. Creates a coroutine; nothing is sent yet
message = await coroutine                  # 2. Runs it: sends the request, waits, returns the Message
  1. Calling an async method returns a coroutine: a description of work that has not started.
  2. await runs the coroutine. While it waits for the network, the event loop can run other coroutines. When the response arrives, execution continues on the next line with the result.

This two-step nature is what makes concurrency possible: you can create several coroutines first, then run them together with asyncio.gather() (Lesson 71) or as tasks (Lesson 74).

Note: writing await directly in front of each call runs the calls one after another, just like sync code. Concurrency only happens when you run several coroutines together.

a = await client.messages.create(...)   # Waits for A
b = await client.messages.create(...)   # Starts B only after A finished: sequential

a, b = await asyncio.gather(             # A and B run at the same time: concurrent
    client.messages.create(...),
    client.messages.create(...),
)

Every Resource Has an Async Version

All SDK resources work the same way on the async client:

OperationSyncAsync
Create a messageclient.messages.create(...)await client.messages.create(...)
Structured outputclient.messages.parse(...)await client.messages.parse(...)
Count tokensclient.messages.count_tokens(...)await client.messages.count_tokens(...)
Retrieve a modelclient.models.retrieve(...)await client.models.retrieve(...)
List models (paginated)for m in client.models.list():async for m in client.models.list():
Raw responseclient.messages.with_raw_response.create(...)await client.messages.with_raw_response.create(...)
Parse a raw responseraw.parse()await raw.parse()

Structured Output

from pydantic import BaseModel


class Sentiment(BaseModel):
    label: str
    confidence: float


result = await client.messages.parse(
    model="claude-opus-5-5",
    max_tokens=1024,
    output_format=Sentiment,
    messages=[{"role": "user", "content": "Classify: 'The course was excellent!'"}],
)
print(result.parsed_output.label)   # positive

Paginated Lists

List methods return async iterators that fetch further pages automatically. Iterate them with async for:

async for model in client.models.list():
    print(model.id)

Raw Responses

raw = await client.messages.with_raw_response.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello"}],
)
print(raw.headers.get("request-id"))
message = await raw.parse()

With the async client, parse() on a raw response is also a coroutine, so it must be awaited.


Errors Are the Same Exceptions

The async client raises the same exception classes as the sync client (Lesson 9). Catch them with ordinary try/except around the await:

import anthropic

try:
    message = await client.messages.create(...)
except anthropic.RateLimitError:
    ...
except anthropic.APIStatusError as error:
    print(error.status_code)

The exception is raised at the await, where the coroutine actually runs. Lesson 77 covers errors in concurrent code.


Practical Example: An Async Helper Module

Applications usually wrap common operations in small, focused helper coroutines. This module provides three: plain text answers, structured answers, and token counting. A demo script uses them together.

claude_async.py

"""claude_async.py: small async helpers built on AsyncAnthropic."""

from typing import TypeVar

from anthropic import AsyncAnthropic
from pydantic import BaseModel

MODEL = "claude-opus-5-5"
T = TypeVar("T", bound=BaseModel)


def text_of(message) -> str:
    return "".join(block.text for block in message.content if block.type == "text").strip()


async def ask(client: AsyncAnthropic, question: str, system: str = "", max_tokens: int = 1024) -> str:
    request = {"model": MODEL, "max_tokens": max_tokens,
               "messages": [{"role": "user", "content": question}]}
    if system:
        request["system"] = system
    message = await client.messages.create(**request)
    if message.stop_reason == "refusal":
        raise ValueError("Claude declined this request.")
    return text_of(message)


async def ask_structured(client: AsyncAnthropic, prompt: str, model_type: type[T]) -> T:
    result = await client.messages.parse(
        model=MODEL,
        max_tokens=1024,
        output_config={"effort": "low"},
        output_format=model_type,
        messages=[{"role": "user", "content": prompt}],
    )
    if result.stop_reason != "end_turn" or result.parsed_output is None:
        raise ValueError(f"No structured result (stop_reason={result.stop_reason}).")
    return result.parsed_output


async def count_tokens(client: AsyncAnthropic, text: str) -> int:
    count = await client.messages.count_tokens(
        model=MODEL, messages=[{"role": "user", "content": text}]
    )
    return count.input_tokens

review_report.py

"""review_report.py: analyze several reviews concurrently with the async helpers."""

import asyncio

from anthropic import AsyncAnthropic
from pydantic import BaseModel

from claude_async import ask, ask_structured, count_tokens

REVIEWS = [
    "The Django course was clear and the projects were practical. Loved it!",
    "Videos are good, but the quizzes have several wrong answers. Please fix them.",
    "Too fast for beginners. I got lost in week two.",
]


class ReviewAnalysis(BaseModel):
    sentiment: str        # positive, negative, or mixed
    main_point: str


async def analyze(client: AsyncAnthropic, review: str) -> tuple[ReviewAnalysis, int]:
    prompt = (f"<review>\n{review}\n</review>\n\nClassify the sentiment as positive, negative, "
              "or mixed, and state the main point in under 12 words.")
    analysis, tokens = await asyncio.gather(
        ask_structured(client, prompt, ReviewAnalysis),
        count_tokens(client, review),
    )
    return analysis, tokens


async def main() -> None:
    async with AsyncAnthropic() as client:
        results = await asyncio.gather(*(analyze(client, review) for review in REVIEWS))
        summary = await ask(
            client,
            "Write one sentence for the course team summarizing these points:\n"
            + "\n".join(f"- {analysis.main_point}" for analysis, _ in results),
        )

    for (analysis, tokens), review in zip(results, REVIEWS):
        print(f"[{analysis.sentiment:<8}] {analysis.main_point}  ({tokens} tokens) <- {review[:35]}...")
    print(f"\nTeam summary: {summary}")


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

Example output:

[positive] Clear lessons and practical projects  (19 tokens) <- The Django course was clear and the...
[mixed   ] Good videos, but quiz answers contain errors  (21 tokens) <- Videos are good, but the quizzes ha...
[negative] Pace is too fast for beginners  (15 tokens) <- Too fast for beginners. I got lost...

Team summary: Students value the clear lessons and practical projects, but the quiz errors and the fast pace for beginners need attention.

How It Works

  1. The helpers take the client as a parameter (Lesson 72), so one client created in main() is shared by everything.
  2. ask() builds the request, adds the system prompt only when given (Lesson 22), awaits the response, and turns a refusal into an exception, so callers receive either text or an error.
  3. ask_structured() is generic: TypeVar("T", bound=BaseModel) and type[T] tell type checkers that passing ReviewAnalysis returns a ReviewAnalysis. It awaits messages.parse() and checks the stop reason before returning the parsed model (Lesson 36).
  4. count_tokens() awaits the token counting endpoint (Lesson 46).
  5. analyze() runs two independent requests for the same review, the analysis and the token count, concurrently with asyncio.gather().
  6. main() analyzes all reviews concurrently, so this is concurrency at two levels: three reviews at once, each with two requests at once. It then makes one final request that depends on all the results, so it runs after the gather().
  7. Results stay in order, because gather() preserves input order, so zip(results, REVIEWS) pairs each analysis with its review.

Common Mistakes

Awaiting Calls One by One When They Could Run Together

What happens: async code that runs no faster than sync code.

Fix: collect independent coroutines and run them with asyncio.gather().

Forgetting await on parse() for Raw Responses

What happens: you get a coroutine instead of a Message.

Fix: message = await raw.parse() with the async client.

Using for Instead of async for on Paginated Lists

What happens: TypeError, because the async paginator is not a regular iterator.

Fix: use async for.

Running Dependent Requests Concurrently

What happens: a request that needs another request's result runs before that result exists.

Fix: run dependent steps after awaiting the steps they depend on, as main() does with the summary.


Best Practices

  • await every async SDK call, including parse() on raw responses.
  • Run independent requests concurrently, and dependent ones in sequence.
  • Wrap common operations in small helper coroutines that receive the client.
  • Check stop reasons in helpers, and convert unusable results into clear exceptions.
  • Use async for with paginated lists.

Exercise

  1. Add a translate(client, text, language) helper to claude_async.py, and translate one review into Tamil and Hindi concurrently.
  2. Write an async function that lists all available models with async for and prints their IDs and maximum output tokens.
  3. Change analyze() so that it awaits the two requests sequentially. Measure the difference in total time.
  4. Explain why the final ask() call in main() cannot run concurrently with the reviews' analyses.
Show answers for steps 2 and 4

Step 2:

async def show_models(client: AsyncAnthropic) -> None:
    async for model in client.models.list():
        print(f"{model.id:<24} max output: {model.max_tokens}")

async for lets the paginator fetch more pages on demand, awaiting each page request.

Step 4: the summary prompt is built from the analyses' results (analysis.main_point). Those results only exist after the analyses have completed. A step that depends on other steps' outputs must run after them. Only independent operations can run concurrently.

0 Comments

Reviewed before they appear

No comments yet.

Anthropic SDK Using Python
Anthropic SDK Intro Claude API Intro SDK Requirements Install Anthropic SDK Upgrade the SDK Create an API Key Set API Keys Use Environment Variables Import Anthropic Create a Client Send Your First Message Choose a Claude Model Set Max Tokens Read a Response Print Response Text Understand Message Blocks Use System Prompts Send User Messages Set Temperature Handle Stop Reasons Messages API Intro Create Messages Use Message Roles Send Conversation History Continue a Conversation Build a Chat Loop Reset Chat History Use Multiple Turns Inspect Message IDs Read Usage Data Write Clear Prompts Set a System Prompt Add Context to Prompts Use Prompt Templates Add Examples to Prompts Request JSON Output Use XML Tags Control Response Length Improve Prompt Quality Avoid Prompt Injection Read Text Blocks Read Content Blocks Check Stop Reasons Count Input Tokens Count Output Tokens Count Tokens Before Sending Limit Output Tokens Use Stop Sequences Read Request IDs Parse API Responses Streaming Intro Stream Text Output Use Stream Events Use Stream Helpers Get Final Messages Stream Async Responses Handle Stream Errors Build a Live Chat Stream Long Responses Close Streams Safely Build a Chatbot Keep Chat History Manage Chat Sessions Add Chat Memory Build a CLI Chatbot Build a Web Chatbot Use Flask with Claude Use FastAPI with Claude Return JSON from an API Add Chat Error Messages Async SDK Intro Create an Async Client Send Async Messages Use asyncio Run Parallel Requests Stream Async Text Handle Async Errors Set Async Timeouts Close Async Clients Choose Sync or Async Tool Use Intro Define a Tool Create Tool Schemas Send Tools to Claude Read Tool Calls Run Python Tools Return Tool Results Continue After Tool Use Control Tool Choice Handle Tool Errors Use the beta_tool Decorator Create a Tool Runner Add Tool Docstrings Validate Tool Inputs Use Multiple Tools Limit Tool Calls Log Tool Activity Build a Weather Tool Build a Search Tool Build a Calculator Tool Structured Output Intro Request JSON Data Validate JSON Output Use Pydantic Models Handle Invalid JSON Extract Fields from Text Generate Typed Data Use Image URLs Send Base64 Images Read PDFs with Claude Use Output Schemas Build Data Extraction Test Structured Responses Send Image Inputs Upload Files Use File IDs Process Text Files Summarize Documents Extract Table Data Handle File Errors Read Cache Usage Reduce Repeated Input Costs Debug Cache Misses Prompt Caching Intro Enable Prompt Caching Cache System Prompts Cache Long Context Test Cache Performance Use Cache Breakpoints Cache Best Practices Message Batches Intro Create a Message Batch Set Batch IDs Check Batch Status List Message Batches Get Batch Results Process Batch Outputs Handle Failed Batch Items Use Batches for Bulk Tasks Compare Batches and Requests Handle API Errors Handle Rate Limits Retry Failed Requests Set Request Timeouts Handle Network Errors Use Exponential Backoff Build Reliable API Calls Use Extended Thinking Log API Errors Track Request IDs Paginate API Results Set Thinking Budgets Read Thinking Blocks Use Tool Search Use Web Search Tools Use Computer Tools Use Citations Use Token Counting Read Raw Responses Set Custom Headers Use Claude on AWS Bedrock Use Claude on Vertex AI Use Claude on Microsoft Foundry Configure Cloud Credentials Choose a Cloud Provider Compare API Integrations Configure Provider Clients Handle Cloud Errors Use Cloud SDK Extras Deploy Cloud Apps Protect API Keys Use a .env File Ignore Secrets in Git Validate User Input Reduce Prompt Injection Limit Tool Permissions Avoid Unsafe Tool Calls Sanitize Tool Results Set Usage Limits Review Security Logs Test SDK Functions Mock API Responses Test Error Handling Test Streaming Code Measure Token Usage Track API Costs Configure Logging Deploy with Docker Deploy a FastAPI App Monitor Production Apps Build a Claude Chatbot Build a Text Summarizer Build a Code Explainer Build a JSON Extractor Build a PDF Assistant Build a Streaming Chat App Build a Tool-Calling Agent Build a Batch Processor Build a Research Assistant Build a Claude API Dashboard
Ask about this post
AI Ask about this post

Ask questions about Send Async Messages and get answers drawn from it.

Signed-in readers only.