The Messages API — send a conversation to Claude and get its reply.
create/2 waits for the whole reply. stream!/2 gives you a lazy stream of
events as they arrive, and stream_to/3 sends those events to a process
instead, for a GenServer or LiveView that can't block.
Summary
Functions
Counts the input tokens your messages, system prompt and tools would use. Claude generates nothing, and the call is free — it spends a request against a rate limit of its own, not tokens.
Sends a request to POST /v1/messages and returns the completed message.
Streams a reply, returning a lazy Stream of Claudex.Stream.Event
structs.
Runs a stream in its own process and returns straight away, delivering each event to a mailbox.
Functions
@spec count_tokens(Claudex.Client.t(), map() | keyword()) :: {:ok, non_neg_integer()} | {:error, Claudex.Error.t()}
Counts the input tokens your messages, system prompt and tools would use. Claude generates nothing, and the call is free — it spends a request against a rate limit of its own, not tokens.
{:ok, tokens} =
Claudex.Messages.count_tokens(client, %{
model: "claude-opus-5",
messages: [%{role: "user", content: "Hello, Claude"}]
})Only :model and :messages are required — this endpoint has no
:max_tokens, and passing one is an error from the API. Everything else
that affects the input counts (:system, :tools, :thinking,
:tool_choice) is optional and passed through, and :tools takes a module
the same way create/2 does.
The number is an estimate; a real request can come out a little different.
@spec create(Claudex.Client.t(), map() | keyword()) :: {:ok, Claudex.Message.t()} | {:error, Claudex.Error.t()}
Sends a request to POST /v1/messages and returns the completed message.
params must include :model, :messages, and :max_tokens. Everything
else the Messages API accepts (:system, :temperature, :thinking,
...) is optional and passed straight through, so any parameter the API
supports works here — see the
Messages API reference.
:tools takes a module that uses Claudex.Tool, a list of them, plain
tool maps, or any mix of the two — see Claudex.Tool.list/1.
Every other key in params goes into the JSON request body verbatim.
This function always sends a non-streaming request. Passing stream: true
returns {:error, %Claudex.Error{type: :bad_request}},
use stream!/2 or stream_to/3 instead.
Returns {:error, %Claudex.Error{}} for a non-2xx response, a timeout, or
a connection failure.
@spec stream!(Claudex.Client.t(), map() | keyword()) :: Enumerable.t()
Streams a reply, returning a lazy Stream of Claudex.Stream.Event
structs.
client
|> Claudex.Messages.stream!(params)
|> Enum.each(fn
%Event.ContentBlockDelta{delta: {:text, chunk}} -> IO.write(chunk)
_event -> :ok
end)Nothing happens until you enumerate it, and enumerating owns the request:
the process that starts consuming spawns the connection and only reads more
of the response when you ask for the next event. Stop enumerating — with
Enum.take/2, a break, or an exception — and the request is cancelled.
params takes exactly what create/2 takes; stream: true is set for
you. Use Claudex.Stream.final_message/1 if you want the assembled
Claudex.Message at the end.
Raises Claudex.Error on a missing parameter, a failed request, or an
error the API sends part-way through the stream. Use stream_to/3 if
you'd rather have errors delivered as messages than raised.
@spec stream_to(Claudex.Client.t(), map() | keyword(), keyword()) :: {:ok, Claudex.Stream.Handle.t()} | {:error, Claudex.Error.t()}
Runs a stream in its own process and returns straight away, delivering each event to a mailbox.
params takes exactly what create/2 takes; stream: true is set for you.
Returns {:ok, %Claudex.Stream.Handle{ref: ref}} and then sends:
{:claudex, ref, {:event, event}}for eachClaudex.Stream.Event{:claudex, ref, {:error, %Claudex.Error{}}}if the request fails{:claudex, ref, :done}when the reply is complete{:claudex, ref, :cancelled}afterClaudex.Stream.cancel/1
The forwarding process is linked to the caller, so it dies with it. Pass
to: pid to send the messages somewhere other than the calling process.