# OpenAI SDK > Call Kiln with the official OpenAI clients in Python, TypeScript and Go: install, a verified request, the tool-calling round trip, and what differs — the cost field the OpenAI schema has no room for, and the reasoning budget inside max_tokens. Source: https://kiln.bricksum.com/docs/en/sdks/openai Kiln serves the OpenAI Chat Completions API, so the official OpenAI clients reach it with no adapter in between. Three values change: the base URL, the key and the model ID. ## Install **Python** ```bash pip install openai ``` **TypeScript** ```bash npm install openai ``` **Go** ```bash go get github.com/openai/openai-go/v3 ``` ## Connect **Python** ```python import os from openai import OpenAI client = OpenAI( base_url="https://api.bricksum.com/v1", api_key=os.environ["KILN_API_KEY"], # sk-bk-... ) resp = client.chat.completions.create( model="qwen3-32b", messages=[{"role": "user", "content": "Write a haiku about kilns."}], max_tokens=800, ) print(resp.choices[0].message.content) ``` **TypeScript** ```javascript import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://api.bricksum.com/v1", apiKey: process.env.KILN_API_KEY, // sk-bk-... }); const resp = await client.chat.completions.create({ model: "qwen3-32b", messages: [{ role: "user", content: "Write a haiku about kilns." }], max_tokens: 800, }); console.log(resp.choices[0].message.content); ``` **Go** ```go package main import ( "context" "fmt" "os" "github.com/openai/openai-go/v3" "github.com/openai/openai-go/v3/option" ) func main() { client := openai.NewClient( option.WithBaseURL("https://api.bricksum.com/v1"), option.WithAPIKey(os.Getenv("KILN_API_KEY")), ) resp, err := client.Chat.Completions.New(context.Background(), openai.ChatCompletionNewParams{ Model: "qwen3-32b", MaxTokens: openai.Int(800), Messages: []openai.ChatCompletionMessageParamUnion{ openai.UserMessage("Write a haiku about kilns."), }, }) if err != nil { panic(err) } fmt.Println(resp.Choices[0].Message.Content) } ``` ## Tool calling One round trip with tool_choice "auto": the model asks for the tool, your code runs it, and the result goes back as a tool message. Every tool needs a description — the model server rejects a tool without one with 400. **Python** ```python import json, os from openai import OpenAI client = OpenAI(base_url="https://api.bricksum.com/v1", api_key=os.environ["KILN_API_KEY"]) tools = [{"type": "function", "function": { "name": "get_weather", "description": "Current weather for a city", # required "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}, }}] messages = [{"role": "user", "content": "What's the weather in Seoul?"}] first = client.chat.completions.create(model="qwen3-32b", messages=messages, tools=tools, tool_choice="auto", max_tokens=800) reply = first.choices[0].message # The model may answer without a tool call, and one reply can carry several. if reply.tool_calls: messages.append(reply) for call in reply.tool_calls: messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps({"temp_c": 27})}) final = client.chat.completions.create(model="qwen3-32b", messages=messages, max_tokens=800) print(final.choices[0].message.content) else: print(reply.content) ``` **TypeScript** ```javascript import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://api.bricksum.com/v1", apiKey: process.env.KILN_API_KEY }); const tools: OpenAI.ChatCompletionTool[] = [{ type: "function", function: { name: "get_weather", description: "Current weather for a city", // required parameters: { type: "object", properties: { city: { type: "string" } }, required: ["city"] }, }}]; const messages: OpenAI.ChatCompletionMessageParam[] = [ { role: "user", content: "What's the weather in Seoul?" }, ]; const first = await client.chat.completions.create({ model: "qwen3-32b", messages, tools, tool_choice: "auto", max_tokens: 800 }); // The model may answer without a tool call, and one reply can carry several. const calls = first.choices[0].message.tool_calls ?? []; if (calls.length > 0) { messages.push(first.choices[0].message); for (const call of calls) { if (call.type !== "function") continue; messages.push({ role: "tool", tool_call_id: call.id, content: JSON.stringify({ temp_c: 27 }) }); } const final = await client.chat.completions.create({ model: "qwen3-32b", messages, max_tokens: 800 }); console.log(final.choices[0].message.content); } else { console.log(first.choices[0].message.content); } ``` ## What differs on Kiln - **usage.cost is not in the OpenAI schema** Kiln adds cost, in USD, to the usage object of every response. The OpenAI clients type usage from the OpenAI schema, which has no such field, so TypeScript rejects resp.usage.cost until the type is widened; the value is on the wire either way. - **Reasoning tokens count against max_tokens** The model reasons before it answers and those tokens are billed and counted inside max_tokens. A small budget returns finish_reason "length" with an empty message, so leave room for both — the example sends 800, a starting value not measured on this model — if content comes back empty with finish_reason "length", raise it. The base URL, authentication, the request parameters every endpoint shares, the response envelope, streaming and the error codes are the same whichever SDK you use. They are documented once on the [API Reference](https://kiln.bricksum.com/docs/en/api-reference). The calls above land on [Chat completions](https://kiln.bricksum.com/docs/en/api-reference/chat-completions). The model ID has to be one the API is serving right now; how to list those, and what to do when an ID is not among them, is on [Models](https://kiln.bricksum.com/docs/en/models). What this model does not support, whatever SDK sends it, is listed under [Migrating from OpenAI](https://kiln.bricksum.com/docs/en/migrating-from-openai). Note Snippets on this page were run against the Kiln API on 2026-09-21 with openai 3.16.2 (Python), openai 7.20.0 (Node), openai-go v3.64.0. Not verified: That run was on gpt-oss-120b; the page now names qwen3-32b, which it has not yet been re-run against. The tool-calling tabs' loop over every tool call was added after that run and has not been run against the API.