OpenAI SDK

The exchange serves OpenAI Chat Completions and Responses. The official Python and TypeScript clients work without changes. Set the base URL and your key.

Use this SDK for direct API calls. For an agent that runs tools, use the OpenAI Agents SDK.

The Liquid CLI prints the setup for the OpenAI SDK with your model filled in. To run your own program with the exchange in its environment, use liquid exec. It sets OPENAI_BASE_URL, ANTHROPIC_BASE_URL and the keys for that process only.

curl -fsSL https://inference.ai.exchange/cli/install.sh | bash
liquid login
liquid connect openai-sdk
liquid exec -- python app.py

To set up the OpenAI SDK yourself, follow the steps below.

Before you start

1. Point the client at the exchange

The base URL ends in /v1, because the client adds /chat/completions to it.

Python

import os
from openai import OpenAI

client = OpenAI(
    base_url="https://router.inference.ai.exchange/v1",
    api_key=os.environ["LIQUID_API_KEY"],
)

client.chat.completions.create(
    model="liquid.auto",
    messages=[{"role": "user", "content": "hello"}],
    # The preset applies its saved models, strategy and limits.
)

TypeScript

import OpenAI from 'openai'

const client = new OpenAI({
  baseURL: 'https://router.inference.ai.exchange/v1',
  apiKey: process.env.LIQUID_API_KEY,
})

const completion = await client.chat.completions.create({
  model: 'liquid.auto',
  messages: [{ role: 'user', content: 'hello' }],
  // The preset applies its saved models, strategy and limits.
})

curl

# The preset applies its saved models, strategy and limits.
curl https://router.inference.ai.exchange/v1/chat/completions \
  -H "Authorization: Bearer $LIQUID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "liquid.auto",
    "messages": [{"role": "user", "content": "hello"}]
  }'

Use any model, router model or preset on the book. GET /v1/models lists the names. See Market data.

2. Stream and read the cap before the first token

The x-liquid-job-id and x-liquid-cap-usd headers arrive before any token. x-liquid-cap-usd is the maximum charge for the job. The final chunk has the settled charge in its exchange block.

Read the headers with with_raw_response (Python) or withResponse() (TypeScript).

# Python
raw = client.chat.completions.with_raw_response.create(
    model="liquid.auto",
    messages=[{"role": "user", "content": "hello"}],
    stream=True,
    stream_options={"include_usage": True},
)
print(raw.headers["x-liquid-job-id"], raw.headers["x-liquid-cap-usd"])
for chunk in raw.parse():
    print(chunk.choices[0].delta.content or "", end="")
// TypeScript
const { data: stream, response } = await client.chat.completions
  .create({
    model: 'liquid.auto',
    messages: [{ role: 'user', content: 'hello' }],
    stream: true,
    stream_options: { include_usage: true },
  })
  .withResponse()

console.log(response.headers.get('x-liquid-job-id'), response.headers.get('x-liquid-cap-usd'))
for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta.content ?? '')
}

Read the reasoning

A reasoning model sends its reasoning in the delta of each chunk as reasoning. It comes before the answer text or between parts of it.

# Python
for chunk in raw.parse():
    delta = chunk.choices[0].delta if chunk.choices else None
    if delta and getattr(delta, "reasoning", None):
        print(delta.reasoning, end="")
// TypeScript
for await (const chunk of stream) {
  const delta = chunk.choices[0]?.delta as { reasoning?: string } | undefined
  if (delta?.reasoning) process.stdout.write(delta.reasoning)
}

A response that is not streamed has the reasoning in message.reasoning. The exchange bills reasoning as output, and it counts toward max_tokens. See Output limits.

Ask for reasoning

Put a reasoning object in the request body. Use extra_body in Python and an extra field in TypeScript:

Field Meaning
effort A level name such as low, medium or high, or a whole number for a model that takes one. The same as reasoning_effort.
max_tokens A reasoning budget in tokens, for models that take one.
exclude true leaves the reasoning out of the response. It is still generated, counted and billed.

effort and max_tokens are requirements. Only offers that accept them compete for the request.

Keep reasoning blocks in a tool loop

Each chunk with reasoning also has reasoning_details. These are parts of the reasoning blocks, with a number in index. A model that signs its reasoning adds a signature. It can also return a block as encrypted data.

In a tool-use loop:

  1. Join the text of the parts with the same index.
  2. Keep their signature, data and format.
  3. Put the blocks back on the assistant turn that called the tool, as reasoning_details.

If you do not send them, a model that signs its reasoning refuses the next turn.

3. Send purchasing rules in the body

An exchange block sets the purchasing rules for one request: which models compete, how the exchange ranks offers, and limits such as a cost cap and a first-token deadline. The SDK types do not include it. Use extra_body in Python and an extra field in TypeScript. See Purchasing rules.

# Python
client.chat.completions.create(
    model="liquid.auto",
    messages=[{"role": "user", "content": "hello"}],
    extra_body={"exchange": {"cap_usd": 0.05, "limits": {"ttft_ms": 1500}}},
)
// TypeScript
await client.chat.completions.create({
  model: 'liquid.auto',
  messages: [{ role: 'user', content: 'hello' }],
  // @ts-expect-error -- the SDK types do not include the exchange block
  exchange: { cap_usd: 0.05, limits: { ttft_ms: 1500 } },
})

4. Use the Responses API

client.responses.create works with the same base URL. The exchange handles the request in the same way as a Chat Completions request.

# Python
response = client.responses.create(
    model="liquid.auto",
    instructions="Answer in one sentence.",
    input="What is an order book?",
)
print(response.output_text)
// TypeScript
const response = await client.responses.create({
  model: 'liquid.auto',
  instructions: 'Answer in one sentence.',
  input: 'What is an order book?',
})
console.log(response.output_text)

The exchange serves a stateless subset of the API:

Without max_output_tokens, the output limit is 32,768 tokens. The exchange lowers it to the limit that the offers allow. See Output limits.

Notes

Next steps