# Migrating from OpenAI > Move an OpenAI client to Kiln: the base_url, key and model ID to change, the known limitations, and a checklist before production traffic. Source: https://kiln.bricksum.com/docs/en/migrating-from-openai Kiln serves OpenAI-style endpoints, so an existing OpenAI client reaches it once you change three values. This page lists what changes, what does not take effect yet, and the checks to run before you move production traffic. ## Client setup The same call, before and after. Only base_url, the API key and the model ID differ. **Before (OpenAI)** ```python from openai import OpenAI client = OpenAI(api_key="sk-...") resp = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": "Write a haiku about kilns."}], max_tokens=800, ) ``` **After (Kiln)** ```python from openai import OpenAI client = OpenAI(api_key="sk-bk-...", base_url="https://api.bricksum.com/v1") resp = client.chat.completions.create( model="qwen3-32b", messages=[{"role": "user", "content": "Write a haiku about kilns."}], max_tokens=800, ) ``` - base_url points at the Kiln endpoint. The SDK appends the path, so do not add /chat/completions yourself. - The API key is a Kiln key from the console. Your OpenAI key is not accepted. - The model ID comes from the Kiln catalog. An ID that is not in the catalog is rejected with 404. ## Known limitations The current serving stack does not support every OpenAI parameter. These are the known gaps: - response_format (json_object / json_schema) is not supported yet. Requests are accepted but return empty content. To get JSON, tell the model in the prompt to reply with JSON only, then parse and validate the result in your client. - stop sequences are not applied yet. Generation continues past the stop string. - tool_choice: required / a named function and parallel_tool_calls are honored only where the Models page capability table says so. On qwen3-32b (measured 2026-09-26), forced tool choice fails without a clear error: tool_choice "required" returns HTTP 500 with a plain-text "Internal Server Error" body, and naming a function returns 200 with empty arguments (finish_reason "stop"). Neither is a 4xx, so do not retry it — send tool_choice "auto" only. On qwen3-32b the parallel_tool_calls setting is not applied, which is not the same as replies holding one call: even with parallel_tool_calls: false, one reply can carry several tool calls. Answer every entry in tool_calls with its own tool message. Functions without a description are rejected with 400. - finish_reason: "length" (generation cut off by max_tokens) can return empty content in non-streaming responses. Prefer a generous max_tokens or streaming. - The gateway ends a request after 60 minutes. A non-streaming request sends no bytes until it completes, and the network edge can cut such a connection after about 100 seconds, so send long generations with stream: true. Which model honors automatic, forced or parallel tool choice is listed per model in the capability table on the [Models](https://kiln.bricksum.com/docs/en/models) page. ## Checklist 1. Swap base_url, the API key and the model ID. 2. Search your code for response_format and stop, and replace them as described above. 3. Set max_tokens generously, and send long generations with stream: true. 4. Branch your error handling on the HTTP status and error.code, not on the message text. 5. Retry 429 and 5xx with backoff, and read x-ratelimit-reset where the response carries it. 6. Run your test suite against one model ID before you move production traffic. For the status codes and the retry rules, see [Errors](https://kiln.bricksum.com/docs/en/errors) and [Rate limits](https://kiln.bricksum.com/docs/en/rate-limits).