# Quick Start Connect with the OpenAI SDKs and your Inference Cheap key. ## Connect | Client | Base URL | | --- | --- | | OpenAI SDKs | `https://openai.inference.cheap/v1` | | Codex CLI / app ยท [setup](https://docs.inference.cheap/codex) | `https://codex.inference.cheap` | Get an API key from the Inference Cheap team. Pass it as `Authorization: Bearer YOUR_API_KEY` or the SDK's `api_key` option. Keep keys server-side. ### Identify each conversation Send a `thread-id` header with every request. Use one unique ID per conversation and reuse it for its messages and retries. **Sharing one ID across different chats causes request errors.** See [Chat Identity](https://docs.inference.cheap/api-spec#chat-identity) for the rules. ```http thread-id: UNIQUE_THREAD_ID_FOR_EACH_CHAT ``` ### Send the full conversation history Our Zero Data Retention (ZDR) policy for conversation history means we do not store your dialogues on our side. **`previous_response_id` is not supported.** Send the full conversation history with every request, including previous user messages, assistant replies, and any tool calls and results. Use `input` for Responses or `messages` for Chat Completions, and resend the instructions and tool definitions needed for that request. Keep the history in your application. A `thread-id` identifies the conversation but does not restore earlier messages; `store: true` does not enable history storage. Context omitted from a request cannot be recovered by the API. ### Check your API key ```bash export INFERENCE_CHEAP_API_KEY="YOUR_API_KEY" curl --fail-with-body https://openai.inference.cheap/v1/models \ -H "Authorization: Bearer $INFERENCE_CHEAP_API_KEY" ``` ## First request Install the `openai` package. Use a model returned by `/v1/models`. ### Python ```python import os from openai import OpenAI client = OpenAI( api_key=os.environ["INFERENCE_CHEAP_API_KEY"], base_url="https://openai.inference.cheap/v1", ) response = client.responses.create( model="gpt-5.6-sol", input="Say hello.", extra_headers={"thread-id": "UNIQUE_THREAD_ID_FOR_EACH_CHAT"}, ) print(response.output_text) ``` ### JavaScript ```javascript import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.INFERENCE_CHEAP_API_KEY, baseURL: "https://openai.inference.cheap/v1", }); const response = await client.responses.create({ model: "gpt-5.6-sol", input: "Say hello.", }, { headers: { "thread-id": "UNIQUE_THREAD_ID_FOR_EACH_CHAT" }, }); console.log(response.output_text); ``` ## Q&A ### How do I check my usage? Check the current usage for your API key at [inference.cheap/key-usage](https://inference.cheap/key-usage). You can also get it through the API with [`GET /v1/usage`](https://docs.inference.cheap/api-spec#totals-for-your-key). ### How much did a request cost? Read `usage.cost` in the final usage of the response. It is the amount charged to your balance after your discount. For daily and per-model totals, call `GET /v1/usage`. See [Usage and cost](https://docs.inference.cheap/api-spec#usage-and-cost) for the endpoints and fields. ## Reference See the [API Spec](https://docs.inference.cheap/api-spec) for endpoints, parameters, and errors. For agents, the whole documentation is one Markdown file at [docs.inference.cheap/llms-full.txt](https://docs.inference.cheap/llms-full.txt). --- # Connect Codex Use your Inference Cheap key in Codex CLI or the app. ## Configure Codex Paste this block at the **start** of `~/.codex/config.toml` (or `$CODEX_HOME/config.toml`), before any `[section]`, and replace `YOUR_API_KEY` with your key. If the file already has `inference_cheap` settings, replace them instead of adding a second copy. ```toml # model_providers.inference_cheap.experimental_bearer_token = "YOUR_API_KEY" model_providers.inference_cheap.name = "OpenAI" model_providers.inference_cheap.base_url = "https://codex.inference.cheap" model_providers.inference_cheap.wire_api = "responses" model_providers.inference_cheap.supports_websockets = false model_provider = "inference_cheap" # Set to "openai" to connect to OpenAI directly. # ``` ## Start a task Restart Codex and start a new chat. Existing chats keep their old provider; fork one to continue it through Inference Cheap. ## Troubleshooting - **Config error:** remove duplicate keys and put the block before any section. - **Auth error:** check your key. - **Codex still uses another provider:** check profile and project overrides. --- # Image generation Generate, edit, and stream images through the Inference Cheap API. Use `https://openai.inference.cheap/v1` with a key that allows image generation. ## Choose an interface | Interface | Use it for | | --- | --- | | `POST /v1/images/generations` | Generate images from a prompt. | | `POST /v1/images/edits` | Edit images, optionally with a mask. | | `POST /v1/responses` with an `image_generation` tool | Generate or edit images in a conversation. | All three interfaces support streaming. ## Models Only GPT Image 2.5 is available for now. Set `model` to `gpt-image-2.5` in every request. Requests with another model, or without `model`, fail with `503 Service temporarily unavailable`. ## Generate an image Install `openai` and set `INFERENCE_CHEAP_API_KEY`: ```python import base64 import os from pathlib import Path from openai import OpenAI client = OpenAI( api_key=os.environ["INFERENCE_CHEAP_API_KEY"], base_url="https://openai.inference.cheap/v1", timeout=300.0, max_retries=0, # Avoid duplicate generations. ) result = client.images.generate( model="gpt-image-2.5", prompt="A small red sailboat on a calm blue lake, watercolor illustration.", n=1, output_format="png", ) Path("sailboat.png").write_bytes(base64.b64decode(result.data[0].b64_json)) ``` ## Edit an image Pass a local image file: ```python with open("sailboat.png", "rb") as source: result = client.images.edit( model="gpt-image-2.5", image=source, prompt="Change the sail to yellow. Keep the boat and lake composition.", output_format="png", ) Path("sailboat-edited.png").write_bytes( base64.b64decode(result.data[0].b64_json) ) ``` For JSON requests, use an HTTPS URL or a base64 data URL in `images[].image_url`. Add a mask with `mask.image_url`: ```json { "model": "gpt-image-2.5", "prompt": "Change the sail to yellow.", "images": [ { "image_url": "data:image/png;base64,REPLACE_WITH_IMAGE_BASE64" } ] } ``` Multipart fields: `image` / `image[]`, optional `mask`. `file_id` is unsupported. Masks do not guarantee that areas outside the mask stay unchanged. ## Parameters and output The following parameters apply to `/v1/images/generations` and `/v1/images/edits`. **`quality`, `style`, and `input_fidelity` are not configurable** and silently ignored. | Parameter | Current behavior | | --- | --- | | `prompt` | Required, non-empty. | | `model` | Required: `gpt-image-2.5`. See [Models](#models). | | `n` | Positive integer limiting returned images. Omit for all. The count is not guaranteed. All generated images are billed, including those not returned. | | `size` | Sets only the aspect ratio, from 1:3 to 3:1; other values are ignored. Images are always about 1.57 megapixels, so `1024x1024` returns `1254x1254`. | | `aspect_ratio` | `W:H`, for example `16:9`. Not an OpenAI parameter: in the SDK, pass it in `extra_body`. Overrides `size`. | | `quality` | Ignored. Always `auto`. | | `style` | Ignored. | | `background` | `auto` (default), `opaque`, or `transparent`. With `auto`, the model chooses from the prompt: an isolated object often comes back transparent. `opaque` always gives a solid background. `transparent` requires `png` or `webp`. | | `output_format` | `png`, `jpeg`, or `webp`. | | `output_compression` | Controls JPEG and WebP compression. | | `moderation` | Does not disable safety checks. | | `stream` | `false` returns a final JSON response; `true` returns SSE events. | | `partial_images` | Requests streamed previews, which are not guaranteed. | | `response_format` | `b64_json` by default. `url` returns inline `data:` URLs, not download links. | | `input_fidelity` | Ignored. | ## Stream images Set `stream: true` to receive SSE events. Generation events include `image_generation.partial_image` and `image_generation.completed`; edits use the `image_edit` prefix. Handle error events; save only completed images. ## Use the Responses image tool Use a text model with the `image_generation` tool: ```python response = client.responses.create( model="gpt-5.6-sol", input="Draw a watercolor red sailboat on a calm blue lake.", tools=[{"type": "image_generation", "model": "gpt-image-2.5"}], tool_choice={"type": "image_generation"}, ) images = [ item for item in response.output if item.type == "image_generation_call" and item.status == "completed" and item.result ] if response.status != "completed" or not images: raise RuntimeError("The response did not contain a completed image.") Path("sailboat.png").write_bytes(base64.b64decode(images[0].result)) ``` For edits, include an input image and set `action: "edit"` in the tool. To add a mask, set `input_image_mask: { image_url: "..." }` in the tool. With `stream: true`: - Previews arrive as `response.image_generation_call.partial_image` in `partial_image_b64`. - The completed `image_generation_call.result` contains the final image. - `response.completed` marks the end. Previews are not guaranteed. API reference: [Images](https://developers.openai.com/api/reference/resources/images/methods/generate) and [Responses image tool](https://developers.openai.com/api/docs/guides/tools-image-generation). --- # API Spec Chat identity, usage and cost, endpoints, parameters, and errors. ## Chat Identity The API needs to know which conversation each request belongs to. `prompt_cache_key` alone is not enough for that, so the `thread-id` header tells conversations apart. OpenAI's Codex client identifies conversations the same way. With only `prompt_cache_key`, forks, subagents, and side chats are not guaranteed to work: the API cannot always tell which conversation a request belongs to. Using one `thread-id` for different chats, or one `prompt_cache_key` for different chats without `thread-id`, causes conversation conflicts and request errors. Send a `thread-id` header with every Responses and Chat Completions request: ```http thread-id: UNIQUE_THREAD_ID_FOR_EACH_CHAT ``` - Generate one ID per conversation, such as a UUID. Reuse it for every message and retry in that conversation. - Give each subagent, side chat, and fork its own ID, even when they share a `prompt_cache_key`. - The API routes requests with the same ID together, so they can reuse cached input. - IDs apply only within your API key. - If `thread-id` is missing, the API uses `prompt_cache_key` from the request body as the conversation ID. ## Usage and cost All amounts are in USD. The charge is the price after your discount. The API deducts it from your balance. `cost` means different things in different places. In a response and in a receipt, `usage.cost` is the charge. In `/v1/usage`, `cost` is the standard price before your discount and `actual_cost` is the charge. ### In each response Each response ends with its final `usage`. The fields below are inside it. | What you need | Chat Completions | Responses | Images | | --- | --- | --- | --- | | Where | Response body, or the last chunk when streaming | Response body, or `response.completed` when streaming | Response body, or the last `.completed` event when streaming | | Actual charge with your discount | `cost` | `cost` | `cost` | | Input tokens, including cached | `prompt_tokens` | `input_tokens` | `input_tokens` | | Cached input tokens | `cached_tokens` in `prompt_tokens_details` | `cached_tokens` in `input_tokens_details` | โ€” | | Output tokens, including reasoning | `completion_tokens` | `output_tokens` | `output_tokens` | | Reasoning tokens | `reasoning_tokens` in `completion_tokens_details` | `reasoning_tokens` in `output_tokens_details` | โ€” | | Total tokens | `total_tokens` | `total_tokens` | `total_tokens` | No `cost` means the API did not charge the request. To get the charge later, call `GET /v1/responses/{id}/receipt` with the `id` of the response and read `usage.cost`. ### Totals for your key `GET /v1/usage` returns totals for the API key that calls it. | What you need | Request | Field | | --- | --- | --- | | Spending | `GET /v1/usage` | `usage.today.actual_cost` for the current UTC day, `usage.total.actual_cost` for all time | | Tokens | `GET /v1/usage` | `input_tokens` without cached tokens, `cache_read_tokens`, `output_tokens`, `total_tokens` in `usage.today` and `usage.total` | | Spending and tokens per day | `GET /v1/usage?days=7&timezone=Europe/Berlin` | `daily_usage[]`. `days` is 1 to 90, default 30. `timezone` is an IANA name, default UTC. | | Spending and tokens per model | `GET /v1/usage?start_date=2026-10-01&end_date=2026-10-14` | `model_stats[]`. Dates are `YYYY-MM-DD` in UTC, default the last 30 days. | | Remaining balance | `GET /v1/usage` | `balance`, shared by all keys of your account | ## Endpoints Paths are relative to `https://openai.inference.cheap`. All require an API key. | Endpoint | Status | Notes | | --- | --- | --- | | `POST /v1/responses` | Supported | | | `POST /v1/chat/completions` | Supported | | | `POST /v1/responses/compact` | Supported | Compacts the input you send. | | `GET /v1/models`, `GET /v1/models/{id}` | Supported | Lists the models available to your key. | | `POST /v1/files`, `GET /v1/files` | Supported | Up to 32 MiB per file. Files belong to your API key and are stored apart from conversations. | | `GET /v1/files/{id}`, `DELETE /v1/files/{id}`, `GET /v1/files/{id}/content` | Supported | | | `POST /v1/images/generations`, `POST /v1/images/edits` | Supported | Requires image access for your key. See the [guide](https://docs.inference.cheap/images). | | `GET /v1/usage` | Supported | Balance and spending for your key. See [Usage and cost](#usage-and-cost). | | `GET /v1/responses/{id}/receipt` | Supported | Charge for one response. See [Usage and cost](#usage-and-cost). | | `POST /v1/responses/input_tokens` | Error | Returns `400 unsupported_operation`. | | `GET /v1/responses/{id}`, `DELETE /v1/responses/{id}` | Error | Returns `404`. The API does not store responses. | | `GET /v1/responses/{id}/input_items`, `POST /v1/responses/{id}/cancel` | Error | Returns `404`. | | `/v1/conversations` and its subroutes | Error | Returns `404`. | An `Idempotency-Key` header returns `501 unsupported_feature`. ## Parameter support All models are reasoning models. The tables use three statuses: - Supported The model receives the parameter. - Ignored The API drops the parameter and the request still succeeds. Where a note gives a range, the API checks it first and returns `400` for values outside it. - Error The API rejects the request. Both endpoints ignore unknown top-level fields. ### Chat Completions | Parameter | Status | Notes | | --- | --- | --- | | `messages`, `model` | Supported | Get model IDs from `/v1/models`. | | `audio`, `modalities` | Ignored | The API returns text only. | | `frequency_penalty`, `presence_penalty` | Ignored | Range โˆ’2 to 2. | | `functions`, `function_call` | Supported | The API converts them to `tools` and `tool_choice`. | | `logit_bias` | Ignored | | | `logprobs`, `top_logprobs` | Ignored | Responses have no log probabilities. | | `max_completion_tokens`, `max_tokens` | Supported | Minimum 16. The limit includes reasoning tokens. If you send both, `max_completion_tokens` applies. | | `metadata`, `moderation`, `prediction` | Ignored | | | `n` | Supported | 1 to 8. Each choice is a separate generation with its own charge. | | `parallel_tool_calls`, `tool_choice` | Supported | | | `prompt_cache_key` | Supported | Identifies the conversation when `thread-id` is missing. See [Chat Identity](#chat-identity). | | `prompt_cache_options`, `prompt_cache_retention` | Ignored | | | `reasoning_effort` | Supported | Allowed values depend on the model. | | `response_format` | Supported | `text`, `json_object`, and `json_schema` with `strict`. | | `safety_identifier`, `user` | Ignored | | | `seed` | Ignored | Repeated requests can return different output. | | `service_tier` | Supported | `fast` is an alias for `priority`. | | `stop` | Supported | Up to 4 strings. Generation ends at the first match, and the API estimates usage for the delivered text. | | `store` | Ignored | The API stores nothing. | | `stream`, `stream_options.include_usage` | Supported | An error can arrive inside the stream after HTTP 200. | | `temperature`, `top_p` | Ignored | `temperature` 0 to 2, `top_p` 0 to 1. | | `tools` | Supported | `function` and `custom` tools. | | `verbosity` | Ignored | Use `text.verbosity` in Responses. | | `web_search_options` | Ignored | Use the `web_search` tool in Responses. | ### Responses | Parameter | Status | Notes | | --- | --- | --- | | `input`, `instructions`, `model` | Supported | Get model IDs from `/v1/models`. | | `background` | Error | `true` returns `501`. | | `context_management` | Supported | Only the `compaction` type. | | `conversation`, `previous_response_id` | Error | Returns `501`. Send the full history instead. | | `include` | Supported | The API ignores `message.output_text.logprobs`. | | `max_output_tokens` | Supported | Minimum 16. The limit includes reasoning tokens. | | `max_tool_calls`, `moderation`, `top_logprobs` | Ignored | | | `metadata` | Ignored | Up to 16 string pairs. | | `parallel_tool_calls`, `tool_choice` | Supported | | | `prompt` | Ignored | Stored prompts are unavailable. Send `input`. | | `prompt_cache_key` | Supported | Identifies the conversation when `thread-id` is missing. See [Chat Identity](#chat-identity). | | `prompt_cache_options`, `prompt_cache_retention` | Ignored | | | `reasoning` | Supported | `effort` and `summary`. Allowed values depend on the model. | | `safety_identifier`, `user` | Ignored | | | `service_tier` | Supported | `fast` is an alias for `priority`. | | `store` | Ignored | The API stores nothing. | | `stream` | Supported | HTTP 200 alone does not mean success. Handle `response.failed` and `response.incomplete`. | | `stream_options` | Ignored | | | `temperature`, `top_p` | Ignored | `temperature` 0 to 2, `top_p` 0 to 1. | | `text` | Supported | `format` (`text`, `json_object`, `json_schema` with `strict`) and `verbosity`. | | `tools` | Supported | `function`, `custom`, `web_search`, and `image_generation` ([guide](https://docs.inference.cheap/images)). Support for other hosted tools depends on the model. `file_search` returns `400`. | | `truncation` | Ignored | | ## Errors | Status | What to do | | --- | --- | | `400` | Read `error.message` and `error.param`, then fix the request. | | `401` / `403` | Check your key and access. | | `404` | Check the endpoint, model, and resource IDs. | | `413` | Reduce the payload. | | `429` | Reduce concurrency and respect `Retry-After`. | | `501` | Remove the unsupported feature named in the error. Retrying unchanged does not help. | | Other `5xx` | Retry with bounded backoff. | After a timeout there is no stored response to fetch and no idempotent replay. A retry is a new execution and can be charged again. Keep the same `thread-id` and resend the full context.