Migrate from OpenAI or Azure OpenAI
Move existing code to uttapen by changing base_url and key — model id format, supported endpoints, fields that pass through, and common mistakes.
Updated: 14 Sep 2026
uttapen implements the same chat/completions contract the official OpenAI SDKs speak. If your code already works against OpenAI, Azure OpenAI or any other compatible service, migrating means two edits: the base URL and the key. Model ids take the provider/model shape. The rest of this page covers those details and a few small behavioural differences.
Change base_url
# before
client = OpenAI(api_key="sk-...")
# after
client = OpenAI(
base_url="https://api.uttapen.ir/v1",
api_key="sk-up-...",
)
In Node the field is baseURL; in PHP it's withBaseUri(); in Go it's option.WithBaseURL(). If your SDK reads the environment, set OPENAI_BASE_URL=https://api.uttapen.ir/v1 and OPENAI_API_KEY=sk-up-... and leave the code untouched. The /v1 segment is part of the address — without it you get 404.
Model ids
Ids look like maker/model, because our catalogue holds models from many makers side by side and a bare model name is not unique across them:
| Before (OpenAI) | After (uttapen) |
|---|---|
gpt-5 | openai/gpt-5 |
gpt-5-mini | openai/gpt-5-mini |
o3 | openai/o3 |
| — | anthropic/claude-sonnet-4.5 |
| — | google/gemini-2.5-flash |
| — | deepseek/deepseek-chat |
The full list with toman prices is on the models page or from GET /v1/models, which works without a key. The :free, :batch and :online suffixes are supported too. An id without a maker prefix (gpt-5) is rejected with 404 model_not_found.
What's supported
| Endpoint | Status |
|---|---|
POST /v1/chat/completions | Full: streaming and non-streaming, tools, vision, files, response_format, reasoning |
POST /v1/embeddings | Full |
POST /v1/completions | Legacy, for models that still support it |
GET /v1/models, GET /v1/models/{id} | Full, plus an extra uttapen_pricing block |
POST /v1/responses | 404 for now, with a message pointing you at chat/completions |
/v1/files, /v1/fine_tuning, /v1/assistants, /v1/batches, /v1/audio, /v1/images | Not available; 404 not_found |
The Responses API will land once the shape of its events is stable enough to meter cost reliably. Until then, everything you would use it for — tools, images, JSON, reasoning — is available through chat/completions; only the request body differs.
Fields that pass through
Your request body reaches the model all but untouched, and a few fields beyond the OpenAI contract are supported too:
provider— prefer or pin a specific provider for a model (for example{"order": ["Anthropic"], "allow_fallbacks": false}in the body). Useful if you care about latency or region.models— a fallback list. If the first model doesn't answer, the next one is tried. The hold is sized using the most expensive model in the list, but you are charged for the model that actually answered.reasoning,plugins,web_search_options,transforms— passed through and taken into account when sizing the hold.
We add two fields of our own: usage: {include: true}, so that real usage comes back, and stream: true towards the upstream even when you didn't ask for a stream. In the second case the gateway collects the chunks and hands you one standard JSON response, so nothing changes from the outside.
Behavioural differences
- Numeric fields must be JSON integers.
max_tokens,max_completion_tokens,max_output_tokens,nandbest_ofare rejected with400 invalid_requestif you send"100"(a string),100.0or1e2. The reason is that the hold must not be side-stepped through a notation the provider accepts but we don't parse. 402happens before the request goes out. If your available balance is below what this request needs to hold, it never reaches the provider. A smallermax_tokensmeans a smaller hold (pricing).- The body limit is 20 MB; anything larger returns
413. - Provider error messages come back verbatim with the original status, with internal routing details removed.
- No content is stored. Prompts and responses on the API path are never written down (privacy).
- Added latency compared with calling a model maker directly is around 100 ms, because the route leaves Iran through our own infrastructure.
Common mistakes
| Symptom | Cause | Fix |
|---|---|---|
404 not_found on every request | base_url missing /v1, or including the full /v1/chat/completions | Just https://api.uttapen.ir/v1 |
404 model_not_found with a valid model | Id without a provider, or with uppercase letters | openai/gpt-5-mini, all lowercase |
401 after days of working fine | Key expired or revoked, or an old OpenAI key left in the environment | Check UTTAPEN_API_KEY; a stray OPENAI_API_KEY doesn't take priority but it does confuse people |
400 invalid_request on max_tokens | A string or a decimal value | Send an integer |
402 on a small request | max_tokens not set, so the default 32,768 output tokens were held for an expensive model | Set a realistic max_tokens; otherwise the ceiling is lowered to what the balance carries (X-Uttapen-Max-Tokens) |
| The stream stalls in your client | An intermediate proxy (nginx, Cloudflare) is buffering | proxy_buffering off, or the X-Accel-Buffering: no header on your own proxy |
client.responses.create fails | The Responses API isn't enabled yet | Use chat.completions.create |
| PHP SDK output breaks while streaming | The X-Uttapen-Include-Meta header used with createStreamed | Send that header only from a raw HTTP client (PHP) |
Coming from another compatible gateway
If you were calling another OpenAI-compatible gateway that names models as maker/model, only the base URL and the key change; the model ids stay as they are. The practical differences: settlement happens in toman from your uttapen wallet, every response carries the request's cost in a header, and no request travels from an Iranian IP to a foreign service.
Coming from Azure OpenAI
Replace the AzureOpenAI client with the plain OpenAI one. api_version and azure_endpoint have no meaning here, and the deployment name gives way to a provider/model id.
Sign up with a phone number, top up your wallet from 100,000 toman and create a key. Free models work without a top-up — up to 50 requests a day. Create an API key →