# OpenAI API docs — full documentation > Single-file Markdown export of API guides and docs. Curated index: https://developers.openai.com/api/docs/llms.txt # Data retrieval with GPT Actions One of the most common tasks an action in a GPT can perform is data retrieval. An action might: 1. Access an API to retrieve data based on a keyword search 2. Access a relational database to retrieve records based on a structured query 3. Access a vector database to retrieve text chunks based on semantic search We’ll explore considerations specific to the various types of retrieval integrations in this guide. ## Data retrieval using APIs Many organizations rely on 3rd party software to store important data. Think Salesforce for customer data, Zendesk for support data, Confluence for internal process data, and Google Drive for business documents. These providers often provide REST APIs which enable external systems to search for and retrieve information. When building an action to integrate with a provider's REST API, start by reviewing the existing documentation. You’ll need to confirm a few things: 1. Retrieval methods - **Search** - Each provider will support different search semantics, but generally you want a method which takes a keyword or query string and returns a list of matching documents. See [Google Drive’s `file.list` method](https://developers.google.com/drive/api/guides/search-files) for an example. - **Get** - Once you’ve found matching documents, you need a way to retrieve them. See [Google Drive’s `file.get` method](https://developers.google.com/drive/api/reference/rest/v3/files/get) for an example. 2. Authentication scheme - For example, [Google Drive uses OAuth](https://developers.google.com/workspace/guides/configure-oauth-consent) to authenticate users and ensure that only their available files are available for retrieval. 3. OpenAPI spec - Some providers will provide an OpenAPI spec document which you can import directly into your action. See [Zendesk](https://developer.zendesk.com/api-reference/ticketing/introduction/#download-openapi-file), for an example. - You may want to remove references to methods your GPT _won’t_ access, which constrains the actions your GPT can perform. - For providers who _don’t_ provide an OpenAPI spec document, you can create your own using the [ActionsGPT](https://chatgpt.com/g/g-TYEliDU6A-actionsgpt) (a GPT developed by OpenAI). Your goal is to get the GPT to use the action to search for and retrieve documents containing context which are relevant to the user’s prompt. Your GPT follows your instructions to use the provided search and get methods to achieve this goal. ## Data retrieval using Relational Databases Organizations use relational databases to store a variety of records pertaining to their business. These records can contain useful context that will help improve your GPT’s responses. For example, let’s say you are building a GPT to help users understand the status of an insurance claim. If the GPT can look up claims in a relational database based on a claims number, the GPT will be much more useful to the user. When building an action to integrate with a relational database, there are a few things to keep in mind: 1. Availability of REST APIs - Many relational databases do not natively expose a REST API for processing queries. In that case, you may need to build or buy middleware which can sit between your GPT and the database. - This middleware should do the following: - Accept a formal query string - Pass the query string to the database - Respond back to the requester with the returned records 2. Accessibility from the public internet - Unlike APIs which are designed to be accessed from the public internet, relational databases are traditionally designed to be used within an organization’s application infrastructure. Because GPTs are hosted on OpenAI’s infrastructure, you’ll need to make sure that any APIs you expose are accessible outside of your firewall. 3. Complex query strings - Relational databases uses formal query syntax like SQL to retrieve relevant records. This means that you need to provide additional instructions to the GPT indicating which query syntax is supported. The good news is that GPTs are usually very good at generating formal queries based on user input. 4. Database permissions - Although databases support user-level permissions, it is likely that your end users won’t have permission to access the database directly. If you opt to use a service account to provide access, consider giving the service account read-only permissions. This can avoid inadvertently overwriting or deleting existing data. Your goal is to get the GPT to write a formal query related to the user’s prompt, submit the query via the action, and then use the returned records to augment the response. ## Data retrieval using Vector Databases If you want to equip your GPT with the most relevant search results, you might consider integrating your GPT with a vector database which supports semantic search as described above. There are many managed and self hosted solutions available on the market, [see here for a partial list](https://github.com/openai/chatgpt-retrieval-plugin#choosing-a-vector-database). When building an action to integrate with a vector database, there are a few things to keep in mind: 1. Availability of REST APIs - Many relational databases do not natively expose a REST API for processing queries. In that case, you may need to build or buy middleware which can sit between your GPT and the database (more on middleware below). 2. Accessibility from the public internet - Unlike APIs which are designed to be accessed from the public internet, relational databases are traditionally designed to be used within an organization’s application infrastructure. Because GPTs are hosted on OpenAI’s infrastructure, you’ll need to make sure that any APIs you expose are accessible outside of your firewall. 3. Query embedding - As discussed above, vector databases typically accept a vector embedding (as opposed to plain text) as query input. This means that you need to use an embedding API to convert the query input into a vector embedding before you can submit it to the vector database. This conversion is best handled in the REST API gateway, so that the GPT can submit a plaintext query string. 4. Database permissions - Because vector databases store text chunks as opposed to full documents, it can be difficult to maintain user permissions which might have existed on the original source documents. Remember that any user who can access your GPT will have access to all of the text chunks in the database and plan accordingly. ### Middleware for vector databases As described above, middleware for vector databases typically needs to do two things: 1. Expose access to the vector database via a REST API 2. Convert plaintext query strings into vector embeddings ![Middleware for vector databases](https://cdn.openai.com/API/docs/images/actions-db-diagram.webp) The goal is to get your GPT to submit a relevant query to a vector database to trigger a semantic search, and then use the returned text chunks to augment the response. --- # Getting started with GPT Actions ## Weather.gov example The NSW (National Weather Service) maintains a [public API](https://www.weather.gov/documentation/services-web-api) that users can query to receive a weather forecast for any lat-long point. To retrieve a forecast, there’s 2 steps: 1. A user provides a lat-long to the api.weather.gov/points API and receives back a WFO (weather forecast office), grid-X, and grid-Y coordinates 2. Those 3 elements feed into the api.weather.gov/forecast API to retrieve a forecast for that coordinate For the purpose of this exercise, let’s build a Custom GPT where a user writes a city, landmark, or lat-long coordinates, and the Custom GPT answers questions about a weather forecast in that location. ## Step 1: Write and test Open API schema (using Actions GPT) A GPT Action requires an [Open API schema](https://swagger.io/specification/) to describe the parameters of the API call, which is a standard for describing APIs. OpenAI released a public [Actions GPT](https://chatgpt.com/g/g-TYEliDU6A-actionsgpt) to help developers write this schema. For example, go to the Actions GPT and ask: _“Go to https://www.weather.gov/documentation/services-web-api and read the documentation on that page. Build an Open API Schema for the /points/\{latitude},\{longitude} and /gridpoints/\{office}/\{gridX},\{gridY}/forecast” API calls”_ The above Actions GPT request Below is the full Open API Schema that the Actions GPT Returned: ```yaml openapi: 3.1.0 info: title: NWS Weather API description: Access to weather data including forecasts, alerts, and observations. version: 1.0.0 servers: - url: https://api.weather.gov description: Main API Server paths: /points/{latitude},{longitude}: get: operationId: getPointData summary: Get forecast grid endpoints for a specific location parameters: - name: latitude in: path required: true schema: type: number format: float description: Latitude of the point - name: longitude in: path required: true schema: type: number format: float description: Longitude of the point responses: "200": description: Successfully retrieved grid endpoints content: application/json: schema: type: object properties: properties: type: object properties: forecast: type: string format: uri forecastHourly: type: string format: uri forecastGridData: type: string format: uri /gridpoints/{office}/{gridX},{gridY}/forecast: get: operationId: getGridpointForecast summary: Get forecast for a given grid point parameters: - name: office in: path required: true schema: type: string description: Weather Forecast Office ID - name: gridX in: path required: true schema: type: integer description: X coordinate of the grid - name: gridY in: path required: true schema: type: integer description: Y coordinate of the grid responses: "200": description: Successfully retrieved gridpoint forecast content: application/json: schema: type: object properties: properties: type: object properties: periods: type: array items: type: object properties: number: type: integer name: type: string startTime: type: string format: date-time endTime: type: string format: date-time temperature: type: integer temperatureUnit: type: string windSpeed: type: string windDirection: type: string icon: type: string format: uri shortForecast: type: string detailedForecast: type: string ``` ChatGPT uses the **info** at the top (including the description in particular) to determine if this action is relevant for the user query. ```yaml info: title: NWS Weather API description: Access to weather data including forecasts, alerts, and observations. version: 1.0.0 ``` Then the **parameters** below further define each part of the schema. For example, we're informing ChatGPT that the _office_ parameter refers to the Weather Forecast Office (WFO). ```yaml /gridpoints/{office}/{gridX},{gridY}/forecast: get: operationId: getGridpointForecast summary: Get forecast for a given grid point parameters: - name: office in: path required: true schema: type: string description: Weather Forecast Office ID ``` **Key:** Pay special attention to the **schema names** and **descriptions** that you use in this Open API schema. ChatGPT uses those names and descriptions to understand (a) which API action should be called and (b) which parameter should be used. If a field is restricted to only certain values, you can also provide an "enum" with descriptive category names. While you can just try the Open API schema directly in a GPT Action, debugging directly in ChatGPT can be a challenge. We recommend using a 3rd party service, like [Postman](https://www.postman.com/), to test that your API call is working properly. Postman is free to sign up, verbose in its error-handling, and comprehensive in its authentication options. It even gives you the option of importing Open API schemas directly (see below). Choosing to import your API with Postman ## Step 2: Identify authentication requirements This Weather 3rd party service does not require authentication, so you can skip that step for this Custom GPT. For other GPT Actions that do require authentication, there are 2 options: API Key or OAuth. Asking ChatGPT can help you get started for most common applications. For example, if I needed to use OAuth to authenticate to Google Cloud, I can provide a screenshot and ask for details: _“I’m building a connection to Google Cloud via OAuth. Please provide instructions for how to fill out each of these boxes.”_ The above ChatGPT request Often, ChatGPT provides the correct directions on all 5 elements. Once you have those basics ready, try testing and debugging the authentication in Postman or another similar service. If you encounter an error, provide the error to ChatGPT, and it can usually help you debug from there. ## Step 3: Create the GPT Action and test Now is the time to create your Custom GPT. If you've never created a Custom GPT before, start at our [Creating a GPT guide](https://help.openai.com/en/articles/8554397-creating-a-gpt). 1. Provide a name, description, and image to describe your Custom GPT 2. Go to the Action section and paste in your Open API schema. Take a note of the Action names and json parameters when writing your instructions. 3. Add in your authentication settings 4. Go back to the main page and add in instructions There are many ways to write successful instructions: the most important thing is that the instructions enable the model to reflect the user's preferences. Typically, there are three sections: 1. _Context_ to explain to the model what the GPT Action(s) is doing 2. _Instructions_ on the sequence of steps – this is where you reference the Action name and any parameters the API call needs to pay attention to 3. _Additional Notes_ if there’s anything to keep in mind Here’s an example of the instructions for the Weather GPT. Notice how the instructions refer to the API action name and json parameters from the Open API schema. ``` **Context**: A user needs information related to a weather forecast of a specific location. **Instructions**: 1. The user will provide a lat-long point or a general location or landmark (e.g. New York City, the White House). If the user does not provide one, ask for the relevant location 2. If the user provides a general location or landmark, convert that into a lat-long coordinate. If required, browse the web to look up the lat-long point. 3. Run the "getPointData" API action and retrieve back the gridId, gridX, and gridY parameters. 4. Apply those variables as the office, gridX, and gridY variables in the "getGridpointForecast" API action to retrieve back a forecast 5. Use that forecast to answer the user's question **Additional Notes**: - Assume the user uses US weather units (e.g. Fahrenheit) unless otherwise specified - If the user says "Let's get started" or "What do I do?", explain the purpose of this Custom GPT ``` ### Test the GPT Action Next to each action, you'll see a **Test** button. Click on that for each action. In the test, you can see the detailed input and output of each API call. Available actions If your API call is working in a 3rd party tool like Postman and not in ChatGPT, there are a few possible culprits: - The parameters in ChatGPT are wrong or missing - An authentication issue in ChatGPT - Your instructions are incomplete or unclear - The descriptions in the Open API schema are unclear A preview response from testing the weather API call ## Step 4: Set up callback URL in the 3rd party app If your GPT Action uses OAuth Authentication, you’ll need to set up the callback URL in your 3rd party application. Once you set up a GPT Action with OAuth, ChatGPT provides you with a callback URL (this will update any time you update one of the OAuth parameters). Copy that callback URL and add it to the appropriate place in your application. Setting up a callback URL ## Step 5: Evaluate the Custom GPT Even though you tested the GPT Action in the step above, you still need to evaluate if the Instructions and GPT Action function in the way users expect. Try to come up with at least 5-10 representative questions (the more, the better) of an **“evaluation set”** of questions to ask your Custom GPT. **Key:** Test that the Custom GPT handles each one of your questions as you expect. An example question: _“What should I pack for a trip to the White House this weekend?”_ tests the Custom GPT’s ability to: (1) convert a landmark to a lat-long, (2) run both GPT Actions, and (3) answer the user’s question. The response to the above ChatGPT request, including weather data A continuation of the response above ## Common Debugging Steps _Challenge:_ The GPT Action is calling the wrong API call (or not calling it at all) - _Solution:_ Make sure the descriptions of the Actions are clear - and refer to the Action names in your Custom GPT Instructions _Challenge:_ The GPT Action is calling the right API call but not using the parameters correctly - _Solution:_ Add or modify the descriptions of the parameters in the GPT Action _Challenge:_ The Custom GPT is not working but I am not getting a clear error - _Solution:_ Make sure to test the Action - there are more robust logs in the test window. If that is still unclear, use Postman or another 3rd party service to better diagnose. _Challenge:_ The Custom GPT is giving an authentication error - _Solution:_ Make sure your callback URL is set up correctly. Try testing the exact same authentication settings in Postman or another 3rd party service _Challenge:_ The Custom GPT cannot handle more difficult / ambiguous questions - _Solution:_ Try to prompt engineer your instructions in the Custom GPT. See examples in our [prompt engineering guide](https://developers.openai.com/api/docs/guides/prompt-engineering) This concludes the guide to building a Custom GPT. Good luck building and leveraging the [OpenAI developer forum](https://community.openai.com/) if you have additional questions. --- # GPT Action authentication Actions offer different authentication schemas to accommodate various use cases. To specify the authentication schema for your action, use the GPT editor and select "None", "API Key", or "OAuth". By default, the authentication method for all actions is set to "None", but you can change this and allow different actions to have different authentication methods. ## No authentication We support flows without authentication for applications where users can send requests directly to your API without needing an API key or signing in with OAuth. Consider using no authentication for initial user interactions as you might experience a user drop off if they are forced to sign into an application. You can create a "signed out" experience and then move users to a "signed in" experience by enabling a separate action. ## API key authentication Just like how a user might already be using your API, we allow API key authentication through the GPT editor UI. We encrypt the secret key when we store it in our database to keep your API key secure. This approach is useful if you have an API that takes slightly more consequential actions than the no authentication flow but does not require an individual user to sign in. Adding API key authentication can protect your API and give you more fine-grained access controls along with visibility into where requests are coming from. ## OAuth Actions allow OAuth sign in for each user. This is the best way to provide personalized experiences and make the most powerful actions available to users. A simple example of the OAuth flow with actions will look like the following: - To start, select "Authentication" in the GPT editor UI, and select "OAuth". - You will be prompted to enter the OAuth client ID, client secret, authorization URL, token URL, and scope. - The client ID and secret can be simple text strings but should [follow OAuth best practices](https://www.oauth.com/oauth2-servers/client-registration/client-id-secret/). - We store an encrypted version of the client secret, while the client ID is available to end users. - OAuth requests will include the following information: `request={'grant_type': 'authorization_code', 'client_id': 'YOUR_CLIENT_ID', 'client_secret': 'YOUR_CLIENT_SECRET', 'code': 'abc123', 'redirect_uri': 'https://chat.openai.com/aip/{g-YOUR-GPT-ID-HERE}/oauth/callback'}` Note: `https://chatgpt.com/aip/{g-YOUR-GPT-ID-HERE}/oauth/callback` is also valid. - In order for someone to use an action with OAuth, they will need to send a message that invokes the action and then the user will be presented with a "Sign in to [domain]" button in the ChatGPT UI. - The `authorization_url` endpoint should return a response that looks like: `{ "access_token": "example_token", "token_type": "bearer", "refresh_token": "example_token", "expires_in": 59 }` - During the user sign in process, ChatGPT makes a request to your `authorization_url` using the specified `authorization_content_type`, we expect to get back an access token and optionally a [refresh token](https://auth0.com/learn/refresh-tokens) which we use to periodically fetch a new access token. - Each time a user makes a request to the action, the user’s token will be passed in the Authorization header: ("Authorization": "[Bearer/Basic] [user’s token]"). - We require that OAuth applications make use of the [state parameter](https://auth0.com/docs/secure/attack-protection/state-parameters#set-and-compare-state-parameter-values) for security reasons. Failure to login issues on Custom GPTs (Redirect URLs)? - Be sure to enable this redirect URL in your OAuth application: - #1 Redirect URL: `https://chat.openai.com/aip/{g-YOUR-GPT-ID-HERE}/oauth/callback` (Different domain possible for some clients) - #2 Redirect URL: `https://chatgpt.com/aip/{g-YOUR-GPT-ID-HERE}/oauth/callback` (Get your GPT ID in the URL bar of the ChatGPT UI once you save) if you have several GPTs you'd need to enable for each or a wildcard depending on risk tolerance. - Debug Note: Your Auth Provider will typically log failures (e.g. 'redirect_uri is not registered for client'), which helps debug login issues as well. --- # GPT Actions GPT Actions are stored in [Custom GPTs](https://openai.com/blog/introducing-gpts), which enable users to customize ChatGPT for specific use cases by providing instructions, attaching documents as knowledge, and connecting to 3rd party services. GPT Actions empower ChatGPT users to interact with external applications via RESTful APIs calls outside of ChatGPT simply by using natural language. They convert natural language text into the json schema required for an API call. GPT Actions are usually either used to do [data retrieval](https://developers.openai.com/api/docs/actions/data-retrieval) to ChatGPT (e.g. query a Data Warehouse) or take action in another application (e.g. file a JIRA ticket). ## How GPT Actions work At their core, GPT Actions leverage [Function Calling](https://developers.openai.com/api/docs/guides/function-calling) to execute API calls. Similar to ChatGPT's Data Analysis capability (which generates Python code and then executes it), they leverage Function Calling to (1) decide which API call is relevant to the user's question and (2) generate the json input necessary for the API call. Then finally, the GPT Action executes the API call using that json input. Developers can even specify the authentication mechanism of an action, and the Custom GPT will execute the API call using the third party app’s authentication. GPT Actions obfuscates the complexity of the API call to the end user: they simply ask a question in natural language, and ChatGPT provides the output in natural language as well. ## The Power of GPT Actions APIs allow for **interoperability** to enable your organization to access other applications. However, enabling users to access the right information from 3rd-party APIs can require significant overhead from developers. GPT Actions provide a viable alternative: developers can now simply describe the schema of an API call, configure authentication, and add in some instructions to the GPT, and ChatGPT provides the bridge between the user's natural language questions and the API layer. ## Simplified example The [getting started guide](https://developers.openai.com/api/docs/actions/getting-started) walks through an example using two API calls from [weather.gov](https://developers.openai.com/api/docs/actions/weather.gov) to generate a forecast: - /points/\{latitude},\{longitude} inputs lat-long coordinates and outputs forecast office (wfo) and x-y coordinates - /gridpoints/\{office}/\{gridX},\{gridY}/forecast inputs wfo,x,y coordinates and outputs a forecast Once a developer has encoded the json schema required to populate both of those API calls in a GPT Action, a user can simply ask "What I should pack on a trip to Washington DC this weekend?" The GPT Action will then figure out the lat-long of that location, execute both API calls in order, and respond with a packing list based on the weekend forecast it receives back. In this example, GPT Actions will supply api.weather.gov with two API inputs: /points API call: ```json { "latitude": 38.9072, "longitude": -77.0369 } ``` /forecast API call: ```json { "wfo": "LWX", "x": 97, "y": 71 } ``` ## Get started on building Check out the [getting started guide](https://developers.openai.com/api/docs/actions/getting-started) for a deeper dive on this weather example and our [actions library](https://developers.openai.com/api/docs/actions/actions-library) for pre-built example GPT Actions of the most common 3rd party apps. ## Additional information - Familiarize yourself with our [GPT policies](https://openai.com/policies/usage-policies#:~:text=or%20educational%20purposes.-,Building%20with%20ChatGPT,-Shared%20GPTs%20allow) - Check out the [GPT data privacy FAQs](https://help.openai.com/en/articles/8554402-gpts-data-privacy-faqs) - Find answers to [common GPT questions](https://help.openai.com/en/articles/8554407-gpts-faq) --- # GPT Actions library ## Purpose While GPT Actions should be significantly less work for an API developer to set up than an entire application using those APIs from scratch, there’s still some set up required to get GPT Actions up and running. A Library of GPT Actions is meant to provide guidance for building GPT Actions on common applications. ## Getting started If you’ve never built an action before, start by reading the [getting started guide](https://developers.openai.com/api/docs/actions/getting-started) first to understand better how actions work. Generally, this guide is meant for people with familiarity and comfort with calling API calls. For debugging help, try to explain your issues to ChatGPT - and include screenshots. ## How to access [The OpenAI Cookbook](https://developers.openai.com/cookbook) has a [directory](https://developers.openai.com/cookbook/topic/chatgpt) of 3rd party applications and middleware application. ### 3rd party Actions cookbook GPT Actions can integrate with HTTP services directly. GPT Actions leveraging SaaS API directly will authenticate and request resources directly from SaaS providers, such as [Google Drive](https://developers.openai.com/cookbook/examples/chatgpt/gpt_actions_library/gpt_action_google_drive) or [Snowflake](https://developers.openai.com/cookbook/examples/chatgpt/gpt_actions_library/gpt_action_snowflake_direct). ### Middleware Actions cookbook GPT Actions can benefit from having a middleware. It allows pre-processing, data formatting, data filtering or even connection to endpoints not exposed through HTTP (e.g: databases). Multiple middleware cookbooks are available describing an example implementation path, such as [Azure](https://developers.openai.com/cookbook/examples/chatgpt/gpt_actions_library/gpt_middleware_azure_function), [GCP](https://developers.openai.com/cookbook/examples/chatgpt/gpt_actions_library/gpt_middleware_google_cloud_function) and [AWS](https://developers.openai.com/cookbook/examples/chatgpt/gpt_actions_library/gpt_middleware_aws_function). ## Give us feedback Are there integrations that you’d like us to prioritize? Are there errors in our integrations? File a PR or issue on the cookbook page's github, and we’ll take a look. ## Contribute to our library If you’re interested in contributing to our library, please follow the below guidelines, then submit a PR in github for us to review. In general, follow the template similar to [this example GPT Action](https://developers.openai.com/cookbook/examples/chatgpt/gpt_actions_library/gpt_action_bigquery). Guidelines - include the following sections: - Application Information - describe the 3rd party application, and include a link to app website and API docs - Custom GPT Instructions - include the exact instructions to be included in a Custom GPT - OpenAPI Schema - include the exact OpenAPI schema to be included in the GPT Action - Authentication Instructions - for OAuth, include the exact set of items (authorization URL, token URL, scope, etc.); also include instructions on how to write the callback URL in the application (as well as any other steps) - FAQ and Troubleshooting - what are common pitfalls that users may encounter? Write them here and workarounds ## Disclaimers This action library is meant to be a guide for interacting with 3rd parties that OpenAI have no control over. These 3rd parties may change their API settings or configurations, and OpenAI cannot guarantee these Actions will work in perpetuity. Please see them as a starting point. This guide is meant for developers and people with comfort writing API calls. Non-technical users will likely find these steps challenging. --- # Production notes on GPT Actions ## Rate limits Consider implementing rate limiting on the API endpoints you expose. ChatGPT will respect 429 response codes and dynamically back off from sending requests to your action after receiving a certain number of 429's or 500's in a short period of time. ## Timeouts When making API calls during the actions experience, timeouts take place if the following thresholds are exceeded: - 45 seconds round trip for API calls ## Use TLS and HTTPS All traffic to your action must use TLS 1.2 or later on port 443 with a valid public certificate. ## IP egress ranges ChatGPT will call your action from one of the [published IP ranges](https://developers.openai.com/api/docs/guides/ip-addresses). You may wish to explicitly allowlist these IP addresses. ## Multiple authentication schemas When defining an action, you can mix a single authentication type (OAuth or API key) along with endpoints that do not require authentication. You can learn more about action authentication on our [actions authentication page](https://developers.openai.com/api/docs/actions/authentication). ## Open API specification limits Keep in mind the following limits in your OpenAPI specification, which are subject to change: - 300 characters max for each API endpoint description/summary field in API specification - 700 characters max for each API parameter description field in API specification ## Additional limitations There are a few limitations to be aware of when building with actions: - Custom headers are not supported - With the exception of Google, Microsoft and Adobe OAuth domains, all domains used in an OAuth flow must be the same as the domain used for the primary endpoints - Request and response payloads must be less than 100,000 characters each - Requests timeout after 45 seconds - Requests and responses can only contain text (no images or video) ## Consequential flag In the OpenAPI specification, you can now set certain endpoints as "consequential" as shown below: ```yaml paths: /todo: get: operationId: getTODOs description: Fetches items in a TODO list from the API. security: [] post: operationId: updateTODOs description: Mutates the TODO list. x-openai-isConsequential: true ``` A good example of a consequential action is booking a hotel room and paying for it on behalf of a user. - If the `x-openai-isConsequential` field is `true`, ChatGPT treats the operation as "must always prompt the user for confirmation before running" and don't show an "always allow" button (both are features of GPTs designed to give builders and users more control over actions). - If the `x-openai-isConsequential` field is `false`, ChatGPT shows the "always allow button". - If the field isn't present, ChatGPT defaults all GET operations to `false` and all other operations to `true` ## Best practices on feeding examples Here are some best practices to follow when writing your GPT instructions and descriptions in your schema, as well as when designing your API responses: 1. Your descriptions should not encourage the GPT to use the action when the user hasn't asked for your action's particular category of service. _Bad example_: > Whenever the user mentions any type of task, ask if they would like to use the TODO action to add something to their todo list. _Good example_: > The TODO list can add, remove and view the user's TODOs. 2. Your descriptions should not prescribe specific triggers for the GPT to use the action. ChatGPT is designed to use your action automatically when appropriate. _Bad example_: > When the user mentions a task, respond with "Would you like me to add this to your TODO list? Say 'yes' to continue." _Good example_: > [no instructions needed for this] 3. Action responses from an API should return raw data instead of natural language responses unless it's necessary. The GPT will provide its own natural language response using the returned data. _Bad example_: > I was able to find your todo list! You have 2 todos: get groceries and walk the dog. I can add more todos if you'd like! _Good example_: > \{ "todos": [ "get groceries", "walk the dog" ] } ## How GPT Action data is used GPT Actions connect ChatGPT to external apps. If a user interacts with a GPT’s custom action, ChatGPT may send parts of their conversation to the action’s endpoint. If you have questions or run into additional limitations, you can join the discussion on the [OpenAI developer forum](https://community.openai.com). --- # Sending and returning files with GPT Actions ## Sending files POST requests can include up to ten files (including DALL-E generated images) from the conversation. They will be sent as URLs which are valid for five minutes. For files to be part of your POST request, the parameter must be named `openaiFileIdRefs` and the description should explain to the model the type and quantity of the files which your API is expecting. The `openaiFileIdRefs` parameter will be populated with an array of JSON objects. Each object contains: - `name` The name of the file. This will be an auto generated name when created by DALL-E. - `id` A stable identifier for the file. - `mime_type` The mime type of the file. For user uploaded files this is based on file extension. - `download_link` The URL to fetch the file which is valid for five minutes. Here’s an example of an `openaiFileIdRefs` array with two elements: ```json [ { "name": "dalle-Lh2tg7WuosbyR9hk", "id": "file-XFlOqJYTPBPwMZE3IopCBv1Z", "mime_type": "image/webp", "download_link": "https://files.oaiusercontent.com/file-XFlOqJYTPBPwMZE3IopCBv1Z?se=2024-03-11T20%3A29%3A52Z&sp=r&sv=2021-08-06&sr=b&rscc=max-age%3D31536000%2C%20immutable&rscd=attachment%3B%20filename%3Da580bae6-ea30-478e-a3e2-1f6c06c3e02f.webp&sig=ZPWol5eXACxU1O9azLwRNgKVidCe%2BwgMOc/TdrPGYII%3D" }, { "name": "2023 Benefits Booklet.pdf", "id": "file-s5nX7o4junn2ig0J84r8Q0Ew", "mime_type": "application/pdf", "download_link": "https://files.oaiusercontent.com/file-s5nX7o4junn2ig0J84r8Q0Ew?se=2024-03-11T20%3A29%3A52Z&sp=r&sv=2021-08-06&sr=b&rscc=max-age%3D299%2C%20immutable&rscd=attachment%3B%20filename%3D2023%2520Benefits%2520Booklet.pdf&sig=Ivhviy%2BrgoyUjxZ%2BingpwtUwsA4%2BWaRfXy8ru9AfcII%3D" } ] ``` Actions can include files uploaded by the user, images generated by DALL-E, and files created by Code Interpreter. ### OpenAPI Example ```yaml /createWidget: post: operationId: createWidget summary: Creates a widget based on an image. description: Uploads a file reference using its file id. This file should be an image created by DALL·E or uploaded by the user. JPG, WEBP, and PNG are supported for widget creation. requestBody: required: true content: application/json: schema: type: object properties: openaiFileIdRefs: type: array items: type: string ``` While this schema shows `openaiFileIdRefs` as being an array of type `string`, at runtime this will be populated with an array of JSON objects as previously shown. ## Returning files Requests may return up to 10 files. Each file may be up to 10 MB and cannot be an image or video. These files will become part of the conversation similarly to if a user uploaded them, meaning they may be made available to code interpreter, file search, and sent as part of subsequent action invocations. In the web app users will see that the files have been returned and can download them. To return files, the body of the response must contain an `openaiFileResponse` parameter. This parameter must always be an array and must be populated in one of two ways. ### Inline option Each element of the array is a JSON object which contains: - `name` The name of the file. This will be visible to the user. - `mime_type` The MIME type of the file. This is used to determine eligibility and which features have access to the file. - `content` The base64 encoded contents of the file. Here’s an example of an openaiFileResponse array with two elements: ```json [ { "name": "example_document.pdf", "mime_type": "application/pdf", "content": "JVBERi0xLjQKJcfsj6IKNSAwIG9iago8PC9MZW5ndGggNiAwIFIvRmlsdGVyIC9GbGF0ZURlY29kZT4+CnN0cmVhbQpHhD93PQplbmRzdHJlYW0KZW5kb2JqCg==" }, { "name": "sample_spreadsheet.csv", "mime_type": "text/csv", "content": "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" } ] ``` OpenAPI example ```yaml /papers: get: operationId: findPapers summary: Retrieve PDFs of relevant academic papers. description: Provided an academic topic, up to five relevant papers will be returned as PDFs. parameters: - in: query name: topic required: true schema: type: string description: The topic the papers should be about. responses: "200": description: Zero to five academic paper PDFs content: application/json: schema: type: object properties: openaiFileResponse: type: array items: type: object properties: name: type: string description: The name of the file. mime_type: type: string description: The MIME type of the file. content: type: string format: byte description: The content of the file in base64 encoding. ``` ### URL option Each element of the array is a URL referencing a file to be downloaded. The headers `Content-Disposition` and `Content-Type` must be set such that a file name and MIME type can be determined. The name of the file will be visible to the user. The MIME type of the file determines eligibility and which features have access to the file. There is a 10 second timeout for fetching each file. Here’s an example of an `openaiFileResponse` array with two elements: ```json [ "https://example.com/f/dca89f18-16d4-4a65-8ea2-ededced01646", "https://example.com/f/01fad6b0-635b-4803-a583-0f678b2e6153" ] ``` Here’s an example of the required headers for each URL: ``` Content-Type: application/pdf Content-Disposition: attachment; filename="example_document.pdf" ``` OpenAPI example ```yaml /papers: get: operationId: findPapers summary: Retrieve PDFs of relevant academic papers. description: Provided an academic topic, up to five relevant papers will be returned as PDFs. parameters: - in: query name: topic required: true schema: type: string description: The topic the papers should be about. responses: '200': description: Zero to five academic paper PDFs content: application/json: schema: type: object properties: openaiFileResponse: type: array items: type: string format: uri description: URLs to fetch the files. ``` --- # Assistants API deep dive After achieving feature parity in the Responses API, we've deprecated the Assistants API. It will shut down on August 26, 2026. Follow the [migration guide](https://developers.openai.com/platform/assistants/migration) to update your integration. [Learn more](https://platform.openai.com/docs/guides/migrate-to-responses). ## Overview Don't start a new integration on the Assistants API. We've announced plans to deprecate it soon, as the Responses API now provides the same features and a more elegant integration. There are several concepts involved in building an app with the Assistants API, covered below in case it helps with your [migration to Responses](https://developers.openai.com/api/docs/assistants/migration). ## Creating assistants We recommend using OpenAI's [latest models](https://developers.openai.com/api/docs/models) with the Assistants API for best results and maximum compatibility with tools. To get started, creating an Assistant only requires specifying the `model` to use. But you can further customize the behavior of the Assistant: 1. Use the `instructions` parameter to guide the personality of the Assistant and define its goals. Instructions are similar to system messages in the Chat Completions API. 2. Use the `tools` parameter to give the Assistant access to up to 128 tools. You can give it access to OpenAI built-in tools like `code_interpreter` and `file_search`, or call a third-party tools via a `function` calling. 3. Use the `tool_resources` parameter to give the tools like `code_interpreter` and `file_search` access to files. Files are uploaded using the `File` [upload endpoint](https://developers.openai.com/api/reference/resources/files/methods/create) and must have the `purpose` set to `assistants` to be used with this API. For example, to create an Assistant that can create data visualization based on a `.csv` file, first upload a file. ```javascript const file = await openai.files.create({ file: fs.createReadStream("revenue-forecast.csv"), purpose: "assistants", }); ``` ```python file = client.files.create( file=open("revenue-forecast.csv", "rb"), purpose="assistants" ) ``` ```go input, err := os.Open("revenue-forecast.csv") if err != nil { panic(err) } defer input.Close() file, err := client.Files.New(context.Background(), openai.FileNewParams{ File: input, Purpose: openai.FilePurposeAssistants, }) if err != nil { panic(err) } ``` ```bash curl https://api.openai.com/v1/files \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -F purpose="assistants" \ -F file="@revenue-forecast.csv" ``` Then, create the Assistant with the `code_interpreter` tool enabled and provide the file as a resource to the tool. ```javascript const assistant = await openai.beta.assistants.create({ name: "Data visualizer", description: "You are great at creating beautiful data visualizations. You analyze data present in .csv files, understand trends, and come up with data visualizations relevant to those trends. You also share a brief text summary of the trends observed.", model: "gpt-4o", tools: [{ type: "code_interpreter" }], tool_resources: { code_interpreter: { file_ids: [file.id], }, }, }); ``` ```python assistant = client.beta.assistants.create( name="Data visualizer", description="You are great at creating beautiful data visualizations. You analyze data present in .csv files, understand trends, and come up with data visualizations relevant to those trends. You also share a brief text summary of the trends observed.", model="gpt-4o", tools=[{"type": "code_interpreter"}], tool_resources={"code_interpreter": {"file_ids": [file.id]}}, ) ``` ```go assistant, err := client.Beta.Assistants.New(context.Background(), openai.BetaAssistantNewParams{ Name: openai.String("Data visualizer"), Description: openai.String("You are great at creating beautiful data visualizations. You analyze data present in .csv files, understand trends, and come up with data visualizations relevant to those trends. You also share a brief text summary of the trends observed."), Model: shared.ChatModelGPT4o, Tools: []openai.AssistantToolUnionParam{{OfCodeInterpreter: &openai.CodeInterpreterToolParam{}}}, ToolResources: openai.BetaAssistantNewParamsToolResources{ CodeInterpreter: openai.BetaAssistantNewParamsToolResourcesCodeInterpreter{FileIDs: []string{"file-BK7bzQj3FfZFXr7DbL6xJwfo"}}, }, }) if err != nil { panic(err) } ``` ```bash curl https://api.openai.com/v1/assistants \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" \ -H "OpenAI-Beta: assistants=v2" \ -d '{ "name": "Data visualizer", "description": "You are great at creating beautiful data visualizations. You analyze data present in .csv files, understand trends, and come up with data visualizations relevant to those trends. You also share a brief text summary of the trends observed.", "model": "gpt-4o", "tools": [{"type": "code_interpreter"}], "tool_resources": { "code_interpreter": { "file_ids": ["file-BK7bzQj3FfZFXr7DbL6xJwfo"] } } }' ``` You can attach a maximum of 20 files to `code_interpreter` and 10,000 files to `file_search` (using `vector_store` [objects](https://developers.openai.com/api/reference/resources/vector_stores)). For vector stores created starting in November 2025, the `file_search` limit is 100,000,000 files. Each file can be at most 512 MB in size and have a maximum of 5,000,000 tokens. By default, each project can store up to 2.5 TB of files total. There is no organization-wide storage limit. You can reach out to our support team to increase this limit. ## Managing Threads and Messages Threads and Messages represent a conversation session between an Assistant and a user. There is a limit of 100,000 Messages per Thread. Once the size of the Messages exceeds the context window of the model, the Thread will attempt to smartly truncate messages, before fully dropping the ones it considers the least important. You can create a Thread with an initial list of Messages like this: ```javascript const thread = await openai.beta.threads.create({ messages: [ { role: "user", content: "Create 3 data visualizations based on the trends in this file.", attachments: [ { file_id: file.id, tools: [{ type: "code_interpreter" }], }, ], }, ], }); ``` ```python thread = client.beta.threads.create( messages=[ { "role": "user", "content": "Create 3 data visualizations based on the trends in this file.", "attachments": [ {"file_id": file.id, "tools": [{"type": "code_interpreter"}]} ], } ] ) ``` ```go thread, err := client.Beta.Threads.New(context.Background(), openai.BetaThreadNewParams{ Messages: []openai.BetaThreadNewParamsMessage{{ Role: "user", Content: openai.BetaThreadNewParamsMessageContentUnion{ OfString: openai.String("Create 3 data visualizations based on the trends in this file."), }, Attachments: []openai.BetaThreadNewParamsMessageAttachment{{ FileID: openai.String("file-ACq8OjcLQm2eIG0BvRM4z5qX"), Tools: []openai.BetaThreadNewParamsMessageAttachmentToolUnion{{OfCodeInterpreter: &openai.CodeInterpreterToolParam{}}}, }}, }}, }) if err != nil { panic(err) } ``` ```bash curl https://api.openai.com/v1/threads \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" \ -H "OpenAI-Beta: assistants=v2" \ -d '{ "messages": [ { "role": "user", "content": "Create 3 data visualizations based on the trends in this file.", "attachments": [ { "file_id": "file-ACq8OjcLQm2eIG0BvRM4z5qX", "tools": [{"type": "code_interpreter"}] } ] } ] }' ``` Messages can contain text, images, or file attachment. Message `attachments` are helper methods that add files to a thread's `tool_resources`. You can also choose to add files to the `thread.tool_resources` directly. ### Creating image input content Message content can contain either external image URLs or File IDs uploaded via the [File API](https://developers.openai.com/api/reference/resources/files/methods/create). Only [models](https://developers.openai.com/api/docs/models) with Vision support can accept image input. Supported image content types include png, jpg, gif, and webp. When creating image files, pass `purpose="vision"` to allow you to later download and display the input content. Projects are limited to 2.5 TB total file storage, and there is no organization-wide storage limit. Please contact us to request a limit increase. Tools cannot access image content unless specified. To pass image files to Code Interpreter, add the file ID in the message `attachments` list to allow the tool to read and analyze the input. Image URLs cannot be downloaded in Code Interpreter today. ```javascript import fs from "fs"; const file = await openai.files.create({ file: fs.createReadStream("myimage.png"), purpose: "vision", }); const thread = await openai.beta.threads.create({ messages: [ { role: "user", content: [ { type: "text", text: "What is the difference between these images?", }, { type: "image_url", image_url: { url: "https://openai-documentation.vercel.app/images/cat_and_otter.png", }, }, { type: "image_file", image_file: { file_id: file.id }, }, ], }, ], }); ``` ```python file = client.files.create(file=open("myimage.png", "rb"), purpose="vision") thread = client.beta.threads.create( messages=[ { "role": "user", "content": [ { "type": "text", "text": "What is the difference between these images?", }, { "type": "image_url", "image_url": { "url": "https://openai-documentation.vercel.app/images/cat_and_otter.png" }, }, {"type": "image_file", "image_file": {"file_id": file.id}}, ], } ] ) ``` ```go image, err := os.Open("myimage.png") if err != nil { panic(err) } defer image.Close() file, err := client.Files.New(context.Background(), openai.FileNewParams{ File: image, Purpose: openai.FilePurposeVision, }) if err != nil { panic(err) } thread, err := client.Beta.Threads.New(context.Background(), openai.BetaThreadNewParams{ Messages: []openai.BetaThreadNewParamsMessage{{ Role: "user", Content: openai.BetaThreadNewParamsMessageContentUnion{OfArrayOfContentParts: []openai.MessageContentPartParamUnion{ openai.MessageContentPartParamOfText("What is the difference between these images?"), openai.MessageContentPartParamOfImageURL(openai.ImageURLParam{URL: "https://openai-documentation.vercel.app/images/cat_and_otter.png"}), openai.MessageContentPartParamOfImageFile(openai.ImageFileParam{FileID: file.ID}), }}, }}, }) if err != nil { panic(err) } ``` ```bash # Upload a file with an "vision" purpose curl https://api.openai.com/v1/files \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -F purpose="vision" \ -F file="@/path/to/myimage.png" ## Pass the file ID in the content curl https://api.openai.com/v1/threads \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" \ -H "OpenAI-Beta: assistants=v2" \ -d '{ "messages": [ { "role": "user", "content": [ { "type": "text", "text": "What is the difference between these images?" }, { "type": "image_url", "image_url": {"url": "https://openai-documentation.vercel.app/images/cat_and_otter.png"} }, { "type": "image_file", "image_file": {"file_id": file.id} } ] } ] }' ``` #### Low or high fidelity image understanding By controlling the `detail` parameter, which has three options, `low`, `high`, or `auto`, you have control over how the model processes the image and generates its textual understanding. - `low` will enable the "low res" mode. The model will receive a low-res 512px x 512px version of the image, and represent the image with a budget of 85 tokens. This allows the API to return faster responses and consume fewer input tokens for use cases that do not require high detail. - `high` will enable "high res" mode, which first allows the model to see the low res image and then creates detailed crops of input images based on the input image size. Use the [pricing calculator](https://openai.com/api/pricing/) to see token counts for various image sizes. ```javascript const thread = await openai.beta.threads.create({ messages: [ { role: "user", content: [ { type: "text", text: "What is this an image of?", }, { type: "image_url", image_url: { url: "https://openai-documentation.vercel.app/images/cat_and_otter.png", detail: "high", }, }, ], }, ], }); ``` ```python thread = client.beta.threads.create( messages=[ { "role": "user", "content": [ {"type": "text", "text": "What is this an image of?"}, { "type": "image_url", "image_url": { "url": "https://openai-documentation.vercel.app/images/cat_and_otter.png", "detail": "high", }, }, ], } ] ) ``` ```go thread, err := client.Beta.Threads.New(context.Background(), openai.BetaThreadNewParams{ Messages: []openai.BetaThreadNewParamsMessage{{ Role: "user", Content: openai.BetaThreadNewParamsMessageContentUnion{OfArrayOfContentParts: []openai.MessageContentPartParamUnion{ openai.MessageContentPartParamOfText("What is this an image of?"), openai.MessageContentPartParamOfImageURL(openai.ImageURLParam{ URL: "https://openai-documentation.vercel.app/images/cat_and_otter.png", Detail: openai.ImageURLDetailHigh, }), }}, }}, }) if err != nil { panic(err) } ``` ```bash curl https://api.openai.com/v1/threads \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" \ -H "OpenAI-Beta: assistants=v2" \ -d '{ "messages": [ { "role": "user", "content": [ { "type": "text", "text": "What is this an image of?" }, { "type": "image_url", "image_url": { "url": "https://openai-documentation.vercel.app/images/cat_and_otter.png", "detail": "high" } }, ] } ] }' ``` ### Context window management The Assistants API automatically manages the truncation to ensure it stays within the model's maximum context length. You can customize this behavior by specifying the maximum tokens you'd like a run to utilize and/or the maximum number of recent messages you'd like to include in a run. #### Max Completion and Max Prompt Tokens To control the token usage in a single Run, set `max_prompt_tokens` and `max_completion_tokens` when creating the Run. These limits apply to the total number of tokens used in all completions throughout the Run's lifecycle. For example, initiating a Run with `max_prompt_tokens` set to 500 and `max_completion_tokens` set to 1000 means the first completion will truncate the thread to 500 tokens and cap the output at 1000 tokens. If only 200 prompt tokens and 300 completion tokens are used in the first completion, the second completion will have available limits of 300 prompt tokens and 700 completion tokens. If a completion reaches the `max_completion_tokens` limit, the Run will terminate with a status of `incomplete`, and details will be provided in the `incomplete_details` field of the Run object. When using the File Search tool, we recommend setting the max_prompt_tokens to no less than 20,000. For longer conversations or multiple interactions with File Search, consider increasing this limit to 50,000, or ideally, removing the max_prompt_tokens limits altogether to get the highest quality results. #### Truncation Strategy You may also specify a truncation strategy to control how your thread should be rendered into the model's context window. Using a truncation strategy of type `auto` will use OpenAI's default truncation strategy. Using a truncation strategy of type `last_messages` will allow you to specify the number of the most recent messages to include in the context window. ### Message annotations Messages created by Assistants may contain [`annotations`](https://developers.openai.com/api/reference/resources/beta/subresources/threads/subresources/messages#messages/object-content) within the `content` array of the object. Annotations provide information around how you should annotate the text in the Message. There are two types of Annotations: 1. `file_citation`: File citations are created by the [`file_search`](https://developers.openai.com/api/docs/assistants/tools/file-search) tool and define references to a specific file that was uploaded and used by the Assistant to generate the response. 2. `file_path`: File path annotations are created by the [`code_interpreter`](https://developers.openai.com/api/docs/assistants/tools/code-interpreter) tool and contain references to the files generated by the tool. When annotations are present in the Message object, you'll see illegible model-generated substrings in the text that you should replace with the annotations. These strings may look something like `【13†source】` or `sandbox:/mnt/data/file.csv`. Here’s an example python code snippet that replaces these strings with the annotations. ```python import os from pathlib import Path thread_id = os.environ["OPENAI_THREAD_ID"] message_id = os.environ["OPENAI_MESSAGE_ID"] downloads = Path("downloads") downloads.mkdir(exist_ok=True) # Retrieve the message object message = client.beta.threads.messages.retrieve( thread_id=thread_id, message_id=message_id, ) # Extract the message content message_content = message.content[0].text annotations = message_content.annotations citations = [] # Iterate over the annotations and add footnotes for index, annotation in enumerate(annotations): # Replace the text with a footnote. message_content.value = message_content.value.replace( annotation.text, f" [{index}]" ) # Gather citations based on annotation attributes if file_citation := getattr(annotation, "file_citation", None): cited_file = client.files.retrieve(file_citation.file_id) citations.append(f"[{index}] {file_citation.quote} from {cited_file.filename}") elif file_path := getattr(annotation, "file_path", None): cited_file = client.files.retrieve(file_path.file_id) file_content = client.files.content(file_path.file_id) output_path = downloads / Path(cited_file.filename).name output_path.write_bytes(file_content.read()) citations.append(f"[{index}] Downloaded {output_path}") # Add footnotes to the end of the message before displaying to user message_content.value += "\n" + "\n".join(citations) ``` ```go message, err := client.Beta.Threads.Messages.Get(context.Background(), "thread_abc123", "msg_abc123") if err != nil { panic(err) } if len(message.Content) == 0 || message.Content[0].Type != "text" { panic("message does not contain text") } messageContent := message.Content[0].AsText().Text citations := make([]string, 0, len(messageContent.Annotations)) for index, annotation := range messageContent.Annotations { messageContent.Value = strings.ReplaceAll(messageContent.Value, annotation.Text, fmt.Sprintf(" [%d]", index)) switch annotation.Type { case "file_citation": citation := annotation.AsFileCitation() file, err := client.Files.Get(context.Background(), citation.FileCitation.FileID) if err != nil { panic(err) } citations = append(citations, fmt.Sprintf("[%d] %s", index, file.Filename)) case "file_path": filePath := annotation.AsFilePath() file, err := client.Files.Get(context.Background(), filePath.FilePath.FileID) if err != nil { panic(err) } response, err := client.Files.Content(context.Background(), filePath.FilePath.FileID) if err != nil { panic(err) } defer response.Body.Close() if err := os.MkdirAll("downloads", 0o755); err != nil { panic(err) } outputPath := filepath.Join("downloads", filepath.Base(file.Filename)) output, err := os.Create(outputPath) if err != nil { panic(err) } if _, err := io.Copy(output, response.Body); err != nil { output.Close() panic(err) } if err := output.Close(); err != nil { panic(err) } citations = append(citations, fmt.Sprintf("[%d] Downloaded %s", index, outputPath)) } } messageContent.Value += "\n" + strings.Join(citations, "\n") fmt.Println(messageContent.Value) ``` ## Runs and Run Steps When you have all the context you need from your user in the Thread, you can run the Thread with an Assistant of your choice. ```javascript const run = await openai.beta.threads.runs.create(thread.id, { assistant_id: assistant.id, }); ``` ```python run = client.beta.threads.runs.create( thread_id=thread.id, assistant_id=assistant.id, ) ``` ```go _, err := client.Beta.Threads.Runs.New(context.Background(), "thread_abc123", openai.BetaThreadRunNewParams{ AssistantID: "asst_ToSF7Gb04YMj8AMMm50ZLLtY", }) if err != nil { panic(err) } ``` ```bash curl https://api.openai.com/v1/threads/THREAD_ID/runs \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" \ -H "OpenAI-Beta: assistants=v2" \ -d '{ "assistant_id": "asst_ToSF7Gb04YMj8AMMm50ZLLtY" }' ``` By default, a Run will use the `model` and `tools` configuration specified in Assistant object, but you can override most of these when creating the Run for added flexibility: ```javascript const run = await openai.beta.threads.runs.create(thread.id, { assistant_id: assistant.id, model: "gpt-4o", instructions: "New instructions that override the Assistant instructions", tools: [{ type: "code_interpreter" }, { type: "file_search" }], }); ``` ```python run = client.beta.threads.runs.create( thread_id=thread.id, assistant_id=assistant.id, model="gpt-4o", instructions="New instructions that override the Assistant instructions", tools=[{"type": "code_interpreter"}, {"type": "file_search"}], ) ``` ```go _, err := client.Beta.Threads.Runs.New(context.Background(), "thread_abc123", openai.BetaThreadRunNewParams{ AssistantID: "asst_ToSF7Gb04YMj8AMMm50ZLLtY", Model: shared.ChatModelGPT4o, Instructions: openai.String("New instructions that override the Assistant instructions"), Tools: []openai.AssistantToolUnionParam{ {OfCodeInterpreter: &openai.CodeInterpreterToolParam{}}, {OfFileSearch: &openai.FileSearchToolParam{}}, }, }) if err != nil { panic(err) } ``` ```bash curl https://api.openai.com/v1/threads/THREAD_ID/runs \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" \ -H "OpenAI-Beta: assistants=v2" \ -d '{ "assistant_id": "ASSISTANT_ID", "model": "gpt-4o", "instructions": "New instructions that override the Assistant instructions", "tools": [{"type": "code_interpreter"}, {"type": "file_search"}] }' ``` Note: `tool_resources` associated with the Assistant cannot be overridden during Run creation. You must use the [modify Assistant](https://developers.openai.com/api/reference/resources/beta/subresources/assistants/methods/update) endpoint to do this. #### Run lifecycle Run objects can have multiple statuses. ![Run lifecycle - diagram showing possible status transitions](https://cdn.openai.com/API/docs/images/diagram-run-statuses-v2.png) | Status | Definition | | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `queued` | When Runs are first created or when you complete the `required_action`, they are moved to a queued status. They should almost immediately move to `in_progress`. | | `in_progress` | While `in_progress`, the Assistant uses the model and tools to perform steps. You can view progress being made by the Run by examining the [Run Steps](https://developers.openai.com/api/reference/resources/beta/subresources/threads/subresources/runs/subresources/steps). | | `completed` | The Run successfully completed! You can now view all Messages the Assistant added to the Thread, and all the steps the Run took. You can also continue the conversation by adding more user Messages to the Thread and creating another Run. | | `requires_action` | When using the [Function calling](https://developers.openai.com/api/docs/assistants/tools/function-calling) tool, the Run will move to a `required_action` state once the model determines the names and arguments of the functions to be called. You must then run those functions and [submit the outputs](https://developers.openai.com/api/reference/resources/beta/subresources/threads/subresources/runs/methods/submit_tool_outputs) before the run proceeds. If the outputs are not provided before the `expires_at` timestamp passes (roughly 10 mins past creation), the run will move to an expired status. | | `expired` | This happens when the function calling outputs were not submitted before `expires_at` and the run expires. Additionally, if the runs take too long to execute and go beyond the time stated in `expires_at`, our systems will expire the run. | | `cancelling` | You can attempt to cancel an `in_progress` run using the [Cancel Run](https://developers.openai.com/api/reference/resources/beta/subresources/threads/subresources/runs/methods/cancel) endpoint. Once the attempt to cancel succeeds, status of the Run moves to `cancelled`. Cancellation is attempted but not guaranteed. | | `cancelled` | Run was successfully cancelled. | | `failed` | You can view the reason for the failure by looking at the `last_error` object in the Run. The timestamp for the failure will be recorded under `failed_at`. | | `incomplete` | Run ended due to `max_prompt_tokens` or `max_completion_tokens` reached. You can view the specific reason by looking at the `incomplete_details` object in the Run. | #### Polling for updates If you are not using [streaming](https://developers.openai.com/api/docs/assistants/migration#step-4-create-a-run?context=with-streaming), in order to keep the status of your run up to date, you will have to periodically [retrieve the Run](https://developers.openai.com/api/reference/resources/beta/subresources/threads/subresources/runs/methods/retrieve) object. You can check the status of the run each time you retrieve the object to determine what your application should do next. You can optionally use Polling Helpers in our [Node](https://github.com/openai/openai-node?tab=readme-ov-file#polling-helpers) and [Python](https://github.com/openai/openai-python?tab=readme-ov-file#polling-helpers) SDKs to help you with this. These helpers will automatically poll the Run object for you and return the Run object when it's in a terminal state. #### Thread locks When a Run is `in_progress` and not in a terminal state, the Thread is locked. This means that: - New Messages cannot be added to the Thread. - New Runs cannot be created on the Thread. #### Run steps ![Run steps lifecycle - diagram showing possible status transitions](https://cdn.openai.com/API/docs/images/diagram-2.png) Run step statuses have the same meaning as Run statuses. Most of the interesting detail in the Run Step object lives in the `step_details` field. There can be two types of step details: 1. `message_creation`: This Run Step is created when the Assistant creates a Message on the Thread. 2. `tool_calls`: This Run Step is created when the Assistant calls a tool. Details around this are covered in the relevant sections of the [Tools](https://developers.openai.com/api/docs/assistants/tools) guide. ## Data Access Guidance Currently, Assistants, Threads, Messages, and Vector Stores created via the API are scoped to the Project they're created in. As such, any person with API key access to that Project is able to read or write Assistants, Threads, Messages, and Runs in the Project. We strongly recommend the following data access controls: - _Implement authorization._ Before performing reads or writes on Assistants, Threads, Messages, and Vector Stores, ensure that the end-user is authorized to do so. For example, store in your database the object IDs that the end-user has access to, and check it before fetching the object ID with the API. - _Restrict API key access._ Carefully consider who in your organization should have API keys and be part of a Project. Periodically audit this list. API keys enable a wide range of operations including reading and modifying sensitive information, such as Messages and Files. - _Create separate accounts._ Consider creating separate Projects for different applications in order to isolate data across multiple applications. --- # Assistants API tools After achieving feature parity in the Responses API, we've deprecated the Assistants API. It will shut down on August 26, 2026. Follow the [migration guide](https://developers.openai.com/platform/assistants/migration) to update your integration. [Learn more](https://platform.openai.com/docs/guides/migrate-to-responses). ## Overview Assistants created using the Assistants API can be equipped with tools that allow them to perform more complex tasks or interact with your application. We provide built-in tools for assistants, but you can also define your own tools to extend their capabilities using Function Calling. The Assistants API currently supports the following tools: File Search Built-in RAG tool to process and search through files Code Interpreter Write and run python code, process files and diverse data Function Calling Use your own custom functions to interact with your application ## Next steps - See the API reference to [submit tool outputs](https://developers.openai.com/api/reference/resources/beta/subresources/threads/subresources/runs/methods/submit_tool_outputs) - Build a tool-using assistant with our [Quickstart app](https://github.com/openai/openai-assistants-quickstart) --- # Assistants Code Interpreter After achieving feature parity in the Responses API, we've deprecated the Assistants API. It will shut down on August 26, 2026. Follow the [migration guide](https://developers.openai.com/platform/assistants/migration) to update your integration. [Learn more](https://platform.openai.com/docs/guides/migrate-to-responses). ## Overview Code Interpreter allows Assistants to write and run Python code in a sandboxed execution environment. This tool can process files with diverse data and formatting, and generate files with data and images of graphs. Code Interpreter allows your Assistant to run code iteratively to solve challenging code and math problems. When your Assistant writes code that fails to run, it can iterate on this code by attempting to run different code until the code execution succeeds. See a quickstart of how to get started with Code Interpreter [here](https://developers.openai.com/api/docs/assistants/migration#step-1-create-an-assistant?context=with-streaming). ## How it works Code Interpreter is charged at $0.03 per session. If your Assistant calls Code Interpreter simultaneously in two different threads (e.g., one thread per end-user), two Code Interpreter sessions are created. Each session is active by default for one hour, which means that you only pay for one session per if users interact with Code Interpreter in the same thread for up to one hour. ### Enabling Code Interpreter Pass `code_interpreter` in the `tools` parameter of the Assistant object to enable Code Interpreter: ```javascript const assistant = await openai.beta.assistants.create({ instructions: "You are a personal math tutor. When asked a math question, write and run code to answer the question.", model: "gpt-4o", tools: [{ type: "code_interpreter" }], }); ``` ```python assistant = client.beta.assistants.create( instructions="You are a personal math tutor. When asked a math question, write and run code to answer the question.", model="gpt-4o", tools=[{"type": "code_interpreter"}], ) ``` ```go assistant, err := client.Beta.Assistants.New(context.Background(), openai.BetaAssistantNewParams{ Instructions: openai.String("You are a personal math tutor. When asked a math question, write and run code to answer the question."), Model: shared.ChatModelGPT4o, Tools: []openai.AssistantToolUnionParam{{OfCodeInterpreter: &openai.CodeInterpreterToolParam{}}}, }) if err != nil { panic(err) } ``` ```bash curl https://api.openai.com/v1/assistants \ -u :$OPENAI_API_KEY \ -H 'Content-Type: application/json' \ -H 'OpenAI-Beta: assistants=v2' \ -d '{ "instructions": "You are a personal math tutor. When asked a math question, write and run code to answer the question.", "tools": [ { "type": "code_interpreter" } ], "model": "gpt-4o" }' ``` The model then decides when to invoke Code Interpreter in a Run based on the nature of the user request. This behavior can be promoted by prompting in the Assistant's `instructions` (e.g., “write code to solve this problem”). ### Passing files to Code Interpreter Files that are passed at the Assistant level are accessible by all Runs with this Assistant: ```javascript // Upload a file with an "assistants" purpose const file = await openai.files.create({ file: fs.createReadStream("mydata.csv"), purpose: "assistants", }); // Create an assistant using the file ID const assistant = await openai.beta.assistants.create({ instructions: "You are a personal math tutor. When asked a math question, write and run code to answer the question.", model: "gpt-4o", tools: [{ type: "code_interpreter" }], tool_resources: { code_interpreter: { file_ids: [file.id], }, }, }); ``` ```python # Upload a file with an "assistants" purpose file = client.files.create(file=open("mydata.csv", "rb"), purpose="assistants") # Create an assistant using the file ID assistant = client.beta.assistants.create( instructions="You are a personal math tutor. When asked a math question, write and run code to answer the question.", model="gpt-4o", tools=[{"type": "code_interpreter"}], tool_resources={"code_interpreter": {"file_ids": [file.id]}}, ) ``` ```go input, err := os.Open("mydata.csv") if err != nil { panic(err) } defer input.Close() file, err := client.Files.New(context.Background(), openai.FileNewParams{ File: input, Purpose: openai.FilePurposeAssistants, }) if err != nil { panic(err) } assistant, err := client.Beta.Assistants.New(context.Background(), openai.BetaAssistantNewParams{ Instructions: openai.String("You are a personal math tutor. When asked a math question, write and run code to answer the question."), Model: shared.ChatModelGPT4o, Tools: []openai.AssistantToolUnionParam{{OfCodeInterpreter: &openai.CodeInterpreterToolParam{}}}, ToolResources: openai.BetaAssistantNewParamsToolResources{ CodeInterpreter: openai.BetaAssistantNewParamsToolResourcesCodeInterpreter{FileIDs: []string{file.ID}}, }, }) if err != nil { panic(err) } ``` ```bash # Upload a file with an "assistants" purpose curl https://api.openai.com/v1/files \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -F purpose="assistants" \ -F file="@/path/to/mydata.csv" # Create an assistant using the file ID curl https://api.openai.com/v1/assistants \ -u :$OPENAI_API_KEY \ -H 'Content-Type: application/json' \ -H 'OpenAI-Beta: assistants=v2' \ -d '{ "instructions": "You are a personal math tutor. When asked a math question, write and run code to answer the question.", "tools": [{"type": "code_interpreter"}], "model": "gpt-4o", "tool_resources": { "code_interpreter": { "file_ids": ["file-BK7bzQj3FfZFXr7DbL6xJwfo"] } } }' ``` Files can also be passed at the Thread level. These files are only accessible in the specific Thread. Upload the File using the [File upload](https://developers.openai.com/api/reference/resources/files/methods/create) endpoint and then pass the File ID as part of the Message creation request: ```javascript const thread = await openai.beta.threads.create({ messages: [ { role: "user", content: "I need to solve the equation `3x + 11 = 14`. Can you help me?", attachments: [ { file_id: file.id, tools: [{ type: "code_interpreter" }], }, ], }, ], }); ``` ```python thread = client.beta.threads.create( messages=[ { "role": "user", "content": "I need to solve the equation `3x + 11 = 14`. Can you help me?", "attachments": [ {"file_id": file.id, "tools": [{"type": "code_interpreter"}]} ], } ] ) ``` ```go thread, err := client.Beta.Threads.New(context.Background(), openai.BetaThreadNewParams{ Messages: []openai.BetaThreadNewParamsMessage{{ Role: "user", Content: openai.BetaThreadNewParamsMessageContentUnion{OfString: openai.String("I need to solve the equation `3x + 11 = 14`. Can you help me?")}, Attachments: []openai.BetaThreadNewParamsMessageAttachment{{ FileID: openai.String("file-ACq8OjcLQm2eIG0BvRM4z5qX"), Tools: []openai.BetaThreadNewParamsMessageAttachmentToolUnion{{OfCodeInterpreter: &openai.CodeInterpreterToolParam{}}}, }}, }}, }) if err != nil { panic(err) } ``` ```bash curl https://api.openai.com/v1/threads/thread_abc123/messages \ -u :$OPENAI_API_KEY \ -H 'Content-Type: application/json' \ -H 'OpenAI-Beta: assistants=v2' \ -d '{ "role": "user", "content": "I need to solve the equation `3x + 11 = 14`. Can you help me?", "attachments": [ { "file_id": "file-ACq8OjcLQm2eIG0BvRM4z5qX", "tools": [{"type": "code_interpreter"}] } ] }' ``` Files have a maximum size of 512 MB. Code Interpreter supports a variety of file formats including `.csv`, `.pdf`, `.json` and many more. More details on the file extensions (and their corresponding MIME-types) supported can be found in the [Supported files](#supported-files) section below. ### Reading images and files generated by Code Interpreter Code Interpreter in the API also outputs files, such as generating image diagrams, CSVs, and PDFs. There are two types of files that are generated: 1. Images 2. Data files (e.g. a `csv` file with data generated by the Assistant) When Code Interpreter generates an image, you can look up and download this file in the `file_id` field of the Assistant Message response: ```json { "id": "msg_abc123", "object": "thread.message", "created_at": 1698964262, "thread_id": "thread_abc123", "role": "assistant", "content": [ { "type": "image_file", "image_file": { "file_id": "file-abc123" } } ] # ... } ``` The file content can then be downloaded by passing the file ID to the Files API: ```javascript import fs from "fs"; import OpenAI from "openai"; const openai = new OpenAI(); async function main() { const response = await openai.files.content("file-abc123"); // Extract the binary data from the Response object const image_data = await response.arrayBuffer(); // Convert the binary data to a Buffer const image_data_buffer = Buffer.from(image_data); // Save the image to a specific location fs.writeFileSync("./my-image.png", image_data_buffer); } main(); ``` ```python import os from openai import OpenAI file_id = os.environ["OPENAI_FILE_ID"] client = OpenAI() image_data = client.files.content(file_id) image_data_bytes = image_data.read() with open("./my-image.png", "wb") as file: file.write(image_data_bytes) ``` ```go response, err := client.Files.Content(context.Background(), "file-abc123") if err != nil { panic(err) } defer response.Body.Close() output, err := os.Create("./my-image.png") if err != nil { panic(err) } if _, err := io.Copy(output, response.Body); err != nil { output.Close() panic(err) } if err := output.Close(); err != nil { panic(err) } ``` ```bash curl https://api.openai.com/v1/files/file-abc123/content \ -H "Authorization: Bearer $OPENAI_API_KEY" \ --output image.png ``` When Code Interpreter references a file path (e.g., ”Download this csv file”), file paths are listed as annotations. You can convert these annotations into links to download the file: ```json { "id": "msg_abc123", "object": "thread.message", "created_at": 1699073585, "thread_id": "thread_abc123", "role": "assistant", "content": [ { "type": "text", "text": { "value": "The rows of the CSV file have been shuffled and saved to a new CSV file. You can download the shuffled CSV file from the following link:\\n\\n[Download Shuffled CSV File](sandbox:/mnt/data/shuffled_file.csv)", "annotations": [ { "type": "file_path", "text": "sandbox:/mnt/data/shuffled_file.csv", "start_index": 167, "end_index": 202, "file_path": { "file_id": "file-abc123" } } ... ``` ### Input and output logs of Code Interpreter By listing the steps of a Run that called Code Interpreter, you can inspect the code `input` and `outputs` logs of Code Interpreter: ```javascript const runSteps = await openai.beta.threads.runs.steps.list(run.id, { thread_id: thread.id, }); ``` ```python import os thread_id = os.environ["OPENAI_THREAD_ID"] run_id = os.environ["OPENAI_RUN_ID"] run_steps = client.beta.threads.runs.steps.list( thread_id=thread_id, run_id=run_id, ) ``` ```go runSteps, err := client.Beta.Threads.Runs.Steps.List(context.Background(), "thread_abc123", "run_abc123", openai.BetaThreadRunStepListParams{}) if err != nil { panic(err) } fmt.Println(runSteps.Data) ``` ```bash curl https://api.openai.com/v1/threads/thread_abc123/runs/RUN_ID/steps \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "OpenAI-Beta: assistants=v2" \ ``` ```bash { "object": "list", "data": [ { "id": "step_abc123", "object": "thread.run.step", "type": "tool_calls", "run_id": "run_abc123", "thread_id": "thread_abc123", "status": "completed", "step_details": { "type": "tool_calls", "tool_calls": [ { "type": "code", "code": { "input": "# Calculating 2 + 2\\nresult = 2 + 2\\nresult", "outputs": [ { "type": "logs", "logs": "4" } ... } ``` ## Supported files | File format | MIME type | | ----------- | --------------------------------------------------------------------------- | | `.c` | `text/x-c` | | `.cs` | `text/x-csharp` | | `.cpp` | `text/x-c++` | | `.csv` | `text/csv` | | `.doc` | `application/msword` | | `.docx` | `application/vnd.openxmlformats-officedocument.wordprocessingml.document` | | `.html` | `text/html` | | `.java` | `text/x-java` | | `.json` | `application/json` | | `.md` | `text/markdown` | | `.pdf` | `application/pdf` | | `.php` | `text/x-php` | | `.pptx` | `application/vnd.openxmlformats-officedocument.presentationml.presentation` | | `.py` | `text/x-python` | | `.py` | `text/x-script.python` | | `.rb` | `text/x-ruby` | | `.tex` | `text/x-tex` | | `.txt` | `text/plain` | | `.css` | `text/css` | | `.js` | `text/javascript` | | `.sh` | `application/x-sh` | | `.ts` | `application/typescript` | | `.csv` | `application/csv` | | `.jpeg` | `image/jpeg` | | `.jpg` | `image/jpeg` | | `.gif` | `image/gif` | | `.pkl` | `application/octet-stream` | | `.png` | `image/png` | | `.tar` | `application/x-tar` | | `.xlsx` | `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` | | `.xml` | `application/xml or "text/xml"` | | `.zip` | `application/zip` | --- # Assistants File Search After achieving feature parity in the Responses API, we've deprecated the Assistants API. It will shut down on August 26, 2026. Follow the [migration guide](https://developers.openai.com/platform/assistants/migration) to update your integration. [Learn more](https://platform.openai.com/docs/guides/migrate-to-responses). ## Overview File Search augments the Assistant with knowledge from outside its model, such as proprietary product information or documents provided by your users. OpenAI automatically parses and chunks your documents, creates and stores the embeddings, and use both vector and keyword search to retrieve relevant content to answer user queries. ## Quickstart In this example, we’ll create an assistant that can help answer questions about companies’ financial statements. ### Step 1: Create a new Assistant with File Search Enabled Create a new assistant with `file_search` enabled in the `tools` parameter of the Assistant. ```javascript import OpenAI from "openai"; const openai = new OpenAI(); async function main() { const assistant = await openai.beta.assistants.create({ name: "Financial Analyst Assistant", instructions: "You are an expert financial analyst. Use you knowledge base to answer questions about audited financial statements.", model: "gpt-4o", tools: [{ type: "file_search" }], }); } main(); ``` ```python from openai import OpenAI client = OpenAI() assistant = client.beta.assistants.create( name="Financial Analyst Assistant", instructions="You are an expert financial analyst. Use you knowledge base to answer questions about audited financial statements.", model="gpt-4o", tools=[{"type": "file_search"}], ) ``` ```go assistant, err := client.Beta.Assistants.New(context.Background(), openai.BetaAssistantNewParams{ Name: openai.String("Financial Analyst Assistant"), Instructions: openai.String("You are an expert financial analyst. Use your knowledge base to answer questions about audited financial statements."), Model: shared.ChatModelGPT4o, Tools: []openai.AssistantToolUnionParam{{OfFileSearch: &openai.FileSearchToolParam{}}}, }) if err != nil { panic(err) } ``` ```bash curl https://api.openai.com/v1/assistants \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "OpenAI-Beta: assistants=v2" \ -d '{ "name": "Financial Analyst Assistant", "instructions": "You are an expert financial analyst. Use you knowledge base to answer questions about audited financial statements.", "tools": [{"type": "file_search"}], "model": "gpt-4o" }' ``` Once the `file_search` tool is enabled, the model decides when to retrieve content based on user messages. ### Step 2: Upload files and add them to a Vector Store To access your files, the `file_search` tool uses the Vector Store object. Upload your files and create a Vector Store to contain them. Once the Vector Store is created, you should poll its status until all files are out of the `in_progress` state to ensure that all content has finished processing. The SDK provides helpers to uploading and polling in one shot. ```javascript const fileStreams = [ fs.createReadStream("edgar/goog-10k.pdf"), fs.createReadStream("edgar/brka-10k.txt"), ]; // Create a vector store including our two files. let vectorStore = await openai.vectorStores.create({ name: "Financial Statement", }); await openai.vectorStores.fileBatches.uploadAndPoll(vectorStore.id, { files: fileStreams, }); ``` ```python # Create a vector store called "Financial Statements" vector_store = client.vector_stores.create(name="Financial Statements") # Ready the files for upload to OpenAI file_paths = ["edgar/goog-10k.pdf", "edgar/brka-10k.txt"] file_streams = [open(path, "rb") for path in file_paths] # Use the upload and poll SDK helper to upload the files, add them to the vector store, # and poll the status of the file batch for completion. file_batch = client.vector_stores.file_batches.upload_and_poll( vector_store_id=vector_store.id, files=file_streams ) # You can print the status and the file counts of the batch to see the result of this operation. print(file_batch.status) print(file_batch.file_counts) ``` ### Step 3: Update the assistant to use the new Vector Store To make the files accessible to your assistant, update the assistant’s `tool_resources` with the new `vector_store` id. ```javascript await openai.beta.assistants.update(assistant.id, { tool_resources: { file_search: { vector_store_ids: [vectorStore.id] } }, }); ``` ```python assistant = client.beta.assistants.update( assistant_id=assistant.id, tool_resources={"file_search": {"vector_store_ids": [vector_store.id]}}, ) ``` ```go _, err := client.Beta.Assistants.Update(context.Background(), "asst_abc123", openai.BetaAssistantUpdateParams{ ToolResources: openai.BetaAssistantUpdateParamsToolResources{ FileSearch: openai.BetaAssistantUpdateParamsToolResourcesFileSearch{ VectorStoreIDs: []string{"vs_abc123"}, }, }, }) if err != nil { panic(err) } ``` ### Step 4: Create a thread You can also attach files as Message attachments on your thread. Doing so will create another `vector_store` associated with the thread, or, if there is already a vector store attached to this thread, attach the new files to the existing thread vector store. When you create a Run on this thread, the file search tool will query both the `vector_store` from your assistant and the `vector_store` on the thread. In this example, the user attached a copy of Apple’s latest 10-K filing. ```javascript // A user wants to attach a file to a specific message, let's upload it. const aapl10k = await openai.files.create({ file: fs.createReadStream("edgar/aapl-10k.pdf"), purpose: "assistants", }); const thread = await openai.beta.threads.create({ messages: [ { role: "user", content: "How many shares of AAPL were outstanding at the end of October 2023?", // Attach the new file to the message. attachments: [{ file_id: aapl10k.id, tools: [{ type: "file_search" }] }], }, ], }); // The thread now has a vector store in its tool resources. console.log(thread.tool_resources?.file_search); ``` ```python # Upload the user provided file to OpenAI message_file = client.files.create( file=open("edgar/aapl-10k.pdf", "rb"), purpose="assistants" ) # Create a thread and attach the file to the message thread = client.beta.threads.create( messages=[ { "role": "user", "content": "How many shares of AAPL were outstanding at the end of of October 2023?", # Attach the new file to the message. "attachments": [ {"file_id": message_file.id, "tools": [{"type": "file_search"}]} ], } ] ) # The thread now has a vector store with that file in its tool resources. print(thread.tool_resources.file_search) ``` Vector stores created using message attachments have a default expiration policy of 7 days after they were last active (defined as the last time the vector store was part of a run). This default exists to help you manage your vector storage costs. You can override these expiration policies at any time. Learn more [here](#managing-costs-with-expiration-policies). ### Step 5: Create a run and check the output Now, create a Run and observe that the model uses the File Search tool to provide a response to the user’s question. With streaming ```typescript const stream = openai.beta.threads.runs .stream(thread.id, { assistant_id: assistant.id, }) .on("textCreated", () => console.log("assistant >")) .on("toolCallCreated", (event) => console.log("assistant " + event.type)) .on("messageDone", async (event) => { if (event.content[0].type === "text") { const { text } = event.content[0]; const { annotations } = text; const citations: string[] = []; let index = 0; for (const annotation of annotations) { text.value = text.value.replace(annotation.text, `[${index}]`); if (annotation.type === "file_citation") { const citedFile = await openai.files.retrieve( annotation.file_citation.file_id ); citations.push(`[${index}]${citedFile.filename}`); } index++; } console.log(text.value); console.log(citations.join("\n")); } }); ``` ```python from typing_extensions import override from openai import AssistantEventHandler, OpenAI client = OpenAI() class EventHandler(AssistantEventHandler): @override def on_text_created(self, text) -> None: print("\nassistant > ", end="", flush=True) @override def on_tool_call_created(self, tool_call): print(f"\nassistant > {tool_call.type}\n", flush=True) @override def on_message_done(self, message) -> None: # print a citation to the file searched message_content = message.content[0].text annotations = message_content.annotations citations = [] for index, annotation in enumerate(annotations): message_content.value = message_content.value.replace( annotation.text, f"[{index}]" ) if file_citation := getattr(annotation, "file_citation", None): cited_file = client.files.retrieve(file_citation.file_id) citations.append(f"[{index}] {cited_file.filename}") print(message_content.value) print("\n".join(citations)) # Then, we use the stream SDK helper # with the EventHandler class to create the Run # and stream the response. with client.beta.threads.runs.stream( thread_id=thread.id, assistant_id=assistant.id, instructions="Please address the user as Jane Doe. The user has a premium account.", event_handler=EventHandler(), ) as stream: stream.until_done() ``` Without streaming ```typescript const run = await openai.beta.threads.runs.createAndPoll(thread.id, { assistant_id: assistant.id, }); const messages = await openai.beta.threads.messages.list(thread.id, { run_id: run.id, }); const message = messages.data.pop()!; if (message.content[0].type === "text") { const { text } = message.content[0]; const { annotations } = text; const citations: string[] = []; let index = 0; for (const annotation of annotations) { text.value = text.value.replace(annotation.text, `[${index}]`); if (annotation.type === "file_citation") { const citedFile = await openai.files.retrieve( annotation.file_citation.file_id ); citations.push(`[${index}]${citedFile.filename}`); } index++; } console.log(text.value); console.log(citations.join("\n")); } ``` ```python # Use the create and poll SDK helper to create a run and poll the status of # the run until it's in a terminal state. run = client.beta.threads.runs.create_and_poll( thread_id=thread.id, assistant_id=assistant.id, ) messages = list( client.beta.threads.messages.list(thread_id=thread.id, run_id=run.id) ) message_content = messages[0].content[0].text annotations = message_content.annotations citations = [] for index, annotation in enumerate(annotations): message_content.value = message_content.value.replace( annotation.text, f"[{index}]" ) if file_citation := getattr(annotation, "file_citation", None): cited_file = client.files.retrieve(file_citation.file_id) citations.append(f"[{index}] {cited_file.filename}") print(message_content.value) print("\n".join(citations)) ``` Your new assistant will query both attached vector stores (one containing `goog-10k.pdf` and `brka-10k.txt`, and the other containing `aapl-10k.pdf`) and return this result from `aapl-10k.pdf`. To retrieve the contents of the file search results that were used by the model, use the `include` query parameter and provide a value of `step_details.tool_calls[*].file_search.results[*].content` in the format `?include[]=step_details.tool_calls[*].file_search.results[*].content`. --- ## How it works The `file_search` tool implements several retrieval best practices out of the box to help you extract the right data from your files and augment the model’s responses. The `file_search` tool: - Rewrites user queries to optimize them for search. - Breaks down complex user queries into multiple searches it can run in parallel. - Runs both keyword and semantic searches across both assistant and thread vector stores. - Reranks search results to pick the most relevant ones before generating the final response. By default, the `file_search` tool uses the following settings but these can be [configured](#customizing-file-search-settings) to suit your needs: - Chunk size: 800 tokens - Chunk overlap: 400 tokens - Embedding model: `text-embedding-3-large` at 256 dimensions - Maximum number of chunks added to context: 20 (could be fewer) - Ranker: `auto` (OpenAI will choose which ranker to use) - Score threshold: 0 minimum ranking score **Known Limitations** We have a few known limitations we're working on adding support for in the coming months: 1. Support for deterministic pre-search filtering using custom metadata. 2. Support for parsing images within documents (including images of charts, graphs, tables etc.) 3. Support for retrievals over structured file formats (like `csv` or `jsonl`). 4. Better support for summarization — the tool today is optimized for search queries. ## Vector stores Vector Store objects give the File Search tool the ability to search your files. Adding a file to a `vector_store` automatically parses, chunks, embeds and stores the file in a vector database that's capable of both keyword and semantic search. Each `vector_store` can hold up to 10,000 files. For vector stores created starting in November 2025, this limit is 100,000,000 files. Vector stores can be attached to both Assistants and Threads. Today, you can attach at most one vector store to an assistant and at most one vector store to a thread. #### Creating vector stores and adding files You can create a vector store and add files to it in a single API call: ```javascript const vectorStore = await openai.vectorStores.create({ name: "Product Documentation", file_ids: [ "file_1", "file_2", "file_3", "file_4", "file_5", ], }); ``` ```python vector_store = client.vector_stores.create( name="Product Documentation", file_ids=[ "file_1", "file_2", "file_3", "file_4", "file_5", ], ) ``` ```go vectorStore, err := client.VectorStores.New(context.Background(), openai.VectorStoreNewParams{ Name: openai.String("Product Documentation"), FileIDs: []string{"file_1", "file_2", "file_3", "file_4", "file_5"}, }) if err != nil { panic(err) } ``` Adding files to vector stores is an async operation. To ensure the operation is complete, we recommend that you use the 'create and poll' helpers in our official SDKs. If you're not using the SDKs, you can retrieve the `vector_store` object and monitor its [`file_counts`](https://developers.openai.com/api/reference/resources/vector_stores#vector-stores/object-file_counts) property to see the result of the file ingestion operation. Files can also be added to a vector store after it's created by [creating vector store files](https://developers.openai.com/api/reference/resources/vector_stores/subresources/files/methods/create). Adding files is rate limited per vector store ID. Requests to `/vector_stores/{vector_store_id}/files` and `/vector_stores/{vector_store_id}/file_batches` share a per-vector-store limit of 300 requests per minute. ```javascript const file = await openai.vectorStores.files.createAndPoll( "vs_abc123", { file_id: "file-abc123", } ); ``` ```python file = client.vector_stores.files.create_and_poll( vector_store_id="vs_abc123", file_id="file-abc123" ) ``` ```go _, err := client.VectorStores.Files.NewAndPoll(context.Background(), "vs_abc123", openai.VectorStoreFileNewParams{ FileID: "file-abc123", }, 1000) if err != nil { panic(err) } ``` Alternatively, you can add several files to a vector store by [creating batches](https://developers.openai.com/api/reference/resources/vector_stores/subresources/file_batches/methods/create) of up to 500 files. Batch creation accepts either a simple list of `file_ids` or a `files` array made up of objects with a `file_id` plus optional `attributes` and `chunking_strategy`. Use `files` when you need per-file metadata or chunking settings, and note that `file_ids` and `files` are mutually exclusive in a single request. For high-throughput ingestion into one vector store, prefer file batches whenever possible to reduce request volume and improve latency. ```javascript const batch = await openai.vectorStores.fileBatches.createAndPoll( "vs_abc123", { files: [ { file_id: "file_1", attributes: { category: "finance" }, }, { file_id: "file_2", chunking_strategy: { type: "static", static: { max_chunk_size_tokens: 1000, chunk_overlap_tokens: 200, }, }, }, ], } ); ``` ```python batch = client.vector_stores.file_batches.create_and_poll( vector_store_id="vs_abc123", files=[ {"file_id": "file_1", "attributes": {"category": "finance"}}, { "file_id": "file_2", "chunking_strategy": { "type": "static", "max_chunk_size_tokens": 1000, "chunk_overlap_tokens": 200, }, }, ], ) ``` ```go _, err := client.VectorStores.FileBatches.NewAndPoll(context.Background(), "vs_abc123", openai.VectorStoreFileBatchNewParams{ Files: []openai.VectorStoreFileBatchNewParamsFile{ { FileID: "file_1", Attributes: map[string]openai.VectorStoreFileBatchNewParamsFileAttributeUnion{ "category": {OfString: openai.String("finance")}, }, }, { FileID: "file_2", ChunkingStrategy: openai.FileChunkingStrategyParamUnion{OfStatic: &openai.StaticFileChunkingStrategyObjectParam{ Static: openai.StaticFileChunkingStrategyParam{MaxChunkSizeTokens: 1000, ChunkOverlapTokens: 200}, }}, }, }, }, 1000) if err != nil { panic(err) } ``` Similarly, these files can be removed from a vector store by either: - Deleting the [vector store file object](https://developers.openai.com/api/reference/resources/vector_stores/subresources/files/methods/delete) or, - By deleting the underlying [file object](https://developers.openai.com/api/reference/resources/files/methods/delete) (which removes the file it from all `vector_store` and `code_interpreter` configurations across all assistants and threads in your organization) The maximum file size is 512 MB. Each file should contain no more than 5,000,000 tokens per file (computed automatically when you attach a file). File Search supports a variety of file formats including `.pdf`, `.md`, and `.docx`. More details on the file extensions (and their corresponding MIME-types) supported can be found in the [Supported files](#supported-files) section below. #### Attaching vector stores You can attach vector stores to your Assistant or Thread using the `tool_resources` parameter. ```javascript const assistant = await openai.beta.assistants.create({ instructions: "You are a helpful product support assistant and you answer questions based on the files provided to you.", model: "gpt-4o", tools: [{ type: "file_search" }], tool_resources: { file_search: { vector_store_ids: ["vs_1"], }, }, }); const thread = await openai.beta.threads.create({ messages: [{ role: "user", content: "How do I cancel my subscription?" }], tool_resources: { file_search: { vector_store_ids: ["vs_2"], }, }, }); ``` ```python assistant = client.beta.assistants.create( instructions="You are a helpful product support assistant and you answer questions based on the files provided to you.", model="gpt-4o", tools=[{"type": "file_search"}], tool_resources={"file_search": {"vector_store_ids": ["vs_1"]}}, ) thread = client.beta.threads.create( messages=[{"role": "user", "content": "How do I cancel my subscription?"}], tool_resources={"file_search": {"vector_store_ids": ["vs_2"]}}, ) ``` ```go assistant, err := client.Beta.Assistants.New(context.Background(), openai.BetaAssistantNewParams{ Instructions: openai.String("You are a helpful product support assistant and you answer questions based on the files provided to you."), Model: shared.ChatModelGPT4o, Tools: []openai.AssistantToolUnionParam{{OfFileSearch: &openai.FileSearchToolParam{}}}, ToolResources: openai.BetaAssistantNewParamsToolResources{ FileSearch: openai.BetaAssistantNewParamsToolResourcesFileSearch{VectorStoreIDs: []string{"vs_1"}}, }, }) if err != nil { panic(err) } thread, err := client.Beta.Threads.New(context.Background(), openai.BetaThreadNewParams{ Messages: []openai.BetaThreadNewParamsMessage{{ Role: "user", Content: openai.BetaThreadNewParamsMessageContentUnion{OfString: openai.String("How do I cancel my subscription?")}, }}, ToolResources: openai.BetaThreadNewParamsToolResources{ FileSearch: openai.BetaThreadNewParamsToolResourcesFileSearch{VectorStoreIDs: []string{"vs_2"}}, }, }) if err != nil { panic(err) } ``` You can also attach a vector store to Threads or Assistants after they're created by updating them with the right `tool_resources`. #### Ensuring vector store readiness before creating runs We highly recommend that you ensure all files in a `vector_store` are fully processed before you create a run. This will ensure that all the data in your `vector_store` is searchable. You can check for `vector_store` readiness by using the polling helpers in our SDKs, or by manually polling the `vector_store` object to ensure the [`status`](https://developers.openai.com/api/reference/resources/vector_stores#vector-stores/object-status) is `completed`. As a fallback, we've built a **60 second maximum wait** in the Run object when the **thread’s** vector store contains files that are still being processed. This is to ensure that any files your users upload in a thread a fully searchable before the run proceeds. This fallback wait _does not_ apply to the assistant's vector store. #### Customizing File Search settings You can customize how the `file_search` tool chunks your data and how many chunks it returns to the model context. **Chunking configuration** By default, `max_chunk_size_tokens` is set to `800` and `chunk_overlap_tokens` is set to `400`, meaning every file is indexed by being split up into 800-token chunks, with 400-token overlap between consecutive chunks. You can adjust this by setting [`chunking_strategy`](https://developers.openai.com/api/reference/resources/vector_stores/subresources/files/methods/create#vector-stores-files-createfile-chunking_strategy) when adding files to the vector store. There are certain limitations to `chunking_strategy`: - `max_chunk_size_tokens` must be between 100 and 4096 inclusive. - `chunk_overlap_tokens` must be non-negative and should not exceed `max_chunk_size_tokens / 2`. **Number of chunks** By default, the `file_search` tool outputs up to 20 chunks for `gpt-4*` and o-series models and up to 5 chunks for `gpt-3.5-turbo`. You can adjust this by setting [`file_search.max_num_results`](https://developers.openai.com/api/reference/resources/beta/subresources/assistants/methods/create#assistants-createassistant-tools) in the tool when creating the assistant or the run. Note that the `file_search` tool may output fewer than this number for a myriad of reasons: - The total number of chunks is fewer than `max_num_results`. - The total token size of all the retrieved chunks exceeds the token "budget" assigned to the `file_search` tool. The `file_search` tool currently has a token budget of: - 4,000 tokens for `gpt-3.5-turbo` - 16,000 tokens for `gpt-4*` models - 16,000 tokens for o-series models #### Improve file search result relevance with chunk ranking By default, the file search tool will return all search results to the model that it thinks have any level of relevance when generating a response. However, if responses are generated using content that has low relevance, it can lead to lower quality responses. You can adjust this behavior by both inspecting the file search results that are returned when generating responses, and then tuning the behavior of the file search tool's ranker to change how relevant results must be before they are used to generate a response. **Inspecting file search chunks** The first step in improving the quality of your file search results is inspecting the current behavior of your assistant. Most often, this will involve investigating responses from your assistant that are not not performing well. You can get [granular information about a past run step](https://developers.openai.com/api/reference/resources/beta/subresources/threads/subresources/runs/subresources/steps/methods/retrieve) using the REST API, specifically using the `include` query parameter to get the file chunks that are being used to generate results. Include file search results in response when creating a run ```javascript import OpenAI from "openai"; const openai = new OpenAI(); const runStep = await openai.beta.threads.runs.steps.retrieve("step_abc123", { thread_id: "thread_abc123", run_id: "run_abc123", include: ["step_details.tool_calls[*].file_search.results[*].content"], }); console.log(runStep); ``` ```python from openai import OpenAI client = OpenAI() run_step = client.beta.threads.runs.steps.retrieve( thread_id="thread_abc123", run_id="run_abc123", step_id="step_abc123", include=["step_details.tool_calls[*].file_search.results[*].content"], ) print(run_step) ``` ```go runStep, err := client.Beta.Threads.Runs.Steps.Get( context.Background(), "thread_abc123", "run_abc123", "step_abc123", openai.BetaThreadRunStepGetParams{Include: []openai.RunStepInclude{ openai.RunStepIncludeStepDetailsToolCallsFileSearchResultsContent, }}, ) if err != nil { panic(err) } fmt.Println(runStep) ``` ```bash curl -g https://api.openai.com/v1/threads/thread_abc123/runs/run_abc123/steps/step_abc123?include[]=step_details.tool_calls[*].file_search.results[*].content \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" \ -H "OpenAI-Beta: assistants=v2" ``` You can then log and inspect the search results used during the run step, and determine whether or not they are consistently relevant to the responses your assistant should generate. **Configure ranking options** If you have determined that your file search results are not sufficiently relevant to generate high quality responses, you can adjust the settings of the result ranker used to choose which search results should be used to generate responses. You can adjust this setting [`file_search.ranking_options`](https://developers.openai.com/api/reference/resources/beta/subresources/assistants/methods/create#assistants-createassistant-tools) in the tool when **creating the assistant** or **creating the run**. The settings you can configure are: - `ranker` - Which ranker to use in determining which chunks to use. The available values are `auto`, which uses the latest available ranker, and `default_2024_08_21`. - `score_threshold` - a ranking between 0.0 and 1.0, with 1.0 being the highest ranking. A higher number will constrain the file chunks used to generate a result to only chunks with a higher possible relevance, at the cost of potentially leaving out relevant chunks. - `hybrid_search.embedding_weight` (also referred to as `rrf_embedding_weight`) - determines how much weight to give to semantic similarity when combining dense (embedding) and sparse (text) rankings with [reciprocal rank fusion](https://en.wikipedia.org/wiki/Reciprocal_rank_fusion). Increase this weight to favor chunks that are close in embedding space. - `hybrid_search.text_weight` (also referred to as `rrf_text_weight`) - determines how much weight to give to keyword/text matching when hybrid search is enabled. Increase this weight to favor chunks that share exact terms with the query. At least one of `hybrid_search.embedding_weight` or `hybrid_search.text_weight` must be greater than zero when hybrid search is configured. #### Managing costs with expiration policies The `file_search` tool uses the `vector_stores` object as its resource and you will be billed based on the [size](https://developers.openai.com/api/reference/resources/vector_stores#vector-stores/object-bytes) of the `vector_store` objects created. The size of the vector store object is the sum of all the parsed chunks from your files and their corresponding embeddings. You first GB is free and beyond that, usage is billed at $0.10/GB/day of vector storage. There are no other costs associated with vector store operations. In order to help you manage the costs associated with these `vector_store` objects, we have added support for expiration policies in the `vector_store` object. You can set these policies when creating or updating the `vector_store` object. ```javascript let vectorStore = await openai.vectorStores.create({ name: "rag-store", file_ids: [ "file_1", "file_2", "file_3", "file_4", "file_5", ], expires_after: { anchor: "last_active_at", days: 7, }, }); ``` ```python vector_store = client.vector_stores.create( name="Product Documentation", file_ids=[ "file_1", "file_2", "file_3", "file_4", "file_5", ], expires_after={"anchor": "last_active_at", "days": 7}, ) ``` ```go vectorStore, err := client.VectorStores.New(context.Background(), openai.VectorStoreNewParams{ Name: openai.String("Product Documentation"), FileIDs: []string{"file_1", "file_2", "file_3", "file_4", "file_5"}, ExpiresAfter: openai.VectorStoreNewParamsExpiresAfter{Days: 7}, }) if err != nil { panic(err) } ``` **Thread vector stores have default expiration policies** Vector stores created using thread helpers (like [`tool_resources.file_search.vector_stores`](https://developers.openai.com/api/reference/resources/beta/subresources/threads/methods/create#threads-createthread-tool_resources) in Threads or [message.attachments](https://developers.openai.com/api/reference/resources/beta/subresources/threads/subresources/messages/methods/create#messages-createmessage-attachments) in Messages) have a default expiration policy of 7 days after they were last active (defined as the last time the vector store was part of a run). When a vector store expires, runs on that thread will fail. To fix this, you can simply recreate a new `vector_store` with the same files and reattach it to the thread. ```javascript const fileIds = []; for await (const file of openai.vectorStores.files.list( "vs_expired" )) { fileIds.push(file.id); } const vectorStore = await openai.vectorStores.create({ name: "rag-store", }); await openai.beta.threads.update("thread_abc123", { tool_resources: { file_search: { vector_store_ids: [vectorStore.id] } }, }); for (const fileBatch of _.chunk(fileIds, 100)) { await openai.vectorStores.fileBatches.create(vectorStore.id, { file_ids: fileBatch, }); } ``` ```python all_files = list(client.vector_stores.files.list("vs_expired")) vector_store = client.vector_stores.create(name="rag-store") client.beta.threads.update( "thread_abc123", tool_resources={"file_search": {"vector_store_ids": [vector_store.id]}}, ) for file_batch in chunked(all_files, 100): client.vector_stores.file_batches.create_and_poll( vector_store_id=vector_store.id, file_ids=[file.id for file in file_batch], ) ``` ```go pager := client.VectorStores.Files.ListAutoPaging(context.Background(), "vs_expired", openai.VectorStoreFileListParams{}) fileIDs := make([]string, 0) for pager.Next() { fileIDs = append(fileIDs, pager.Current().ID) } if err := pager.Err(); err != nil { panic(err) } vectorStore, err := client.VectorStores.New(context.Background(), openai.VectorStoreNewParams{ Name: openai.String("rag-store"), }) if err != nil { panic(err) } _, err = client.Beta.Threads.Update(context.Background(), "thread_abc123", openai.BetaThreadUpdateParams{ ToolResources: openai.BetaThreadUpdateParamsToolResources{ FileSearch: openai.BetaThreadUpdateParamsToolResourcesFileSearch{VectorStoreIDs: []string{vectorStore.ID}}, }, }) if err != nil { panic(err) } for start := 0; start < len(fileIDs); start += 100 { end := min(start+100, len(fileIDs)) if _, err := client.VectorStores.FileBatches.NewAndPoll(context.Background(), vectorStore.ID, openai.VectorStoreFileBatchNewParams{ FileIDs: fileIDs[start:end], }, 1000); err != nil { panic(err) } } ``` ## Supported files _For `text/` MIME types, the encoding must be one of `utf-8`, `utf-16`, or `ascii`._ {/* Keep this table in sync with RETRIEVAL_SUPPORTED_EXTENSIONS in the agentapi service */} | File format | MIME type | | ----------- | --------------------------------------------------------------------------- | | `.c` | `text/x-c` | | `.cpp` | `text/x-c++` | | `.cs` | `text/x-csharp` | | `.css` | `text/css` | | `.doc` | `application/msword` | | `.docx` | `application/vnd.openxmlformats-officedocument.wordprocessingml.document` | | `.go` | `text/x-golang` | | `.html` | `text/html` | | `.java` | `text/x-java` | | `.js` | `text/javascript` | | `.json` | `application/json` | | `.md` | `text/markdown` | | `.pdf` | `application/pdf` | | `.php` | `text/x-php` | | `.pptx` | `application/vnd.openxmlformats-officedocument.presentationml.presentation` | | `.py` | `text/x-python` | | `.py` | `text/x-script.python` | | `.rb` | `text/x-ruby` | | `.sh` | `application/x-sh` | | `.tex` | `text/x-tex` | | `.ts` | `application/typescript` | | `.txt` | `text/plain` | --- # Assistants Function Calling After achieving feature parity in the Responses API, we've deprecated the Assistants API. It will shut down on August 26, 2026. Follow the [migration guide](https://developers.openai.com/platform/assistants/migration) to update your integration. [Learn more](https://platform.openai.com/docs/guides/migrate-to-responses). ## Overview Similar to the Chat Completions API, the Assistants API supports function calling. Function calling allows you to describe functions to the Assistants API and have it intelligently return the functions that need to be called along with their arguments. ## Quickstart In this example, we'll create a weather assistant and define two functions, `get_current_temperature` and `get_rain_probability`, as tools that the Assistant can call. Depending on the user query, the model will invoke parallel function calling if using our latest models released on or after Nov 6, 2023. In our example that uses parallel function calling, we will ask the Assistant what the weather in San Francisco is like today and the chances of rain. We also show how to output the Assistant's response with streaming. With the launch of Structured Outputs, you can now use the parameter `strict: true` when using function calling with the Assistants API. For more information, refer to the [Function calling guide](https://developers.openai.com/api/docs/guides/function-calling#strict-mode). Please note that Structured Outputs are not supported in the Assistants API when using vision. ### Step 1: Define functions When creating your assistant, you will first define the functions under the `tools` param of the assistant. ```javascript const assistant = await client.beta.assistants.create({ model: "gpt-4o", instructions: "You are a weather bot. Use the provided functions to answer questions.", tools: [ { type: "function", function: { name: "getCurrentTemperature", description: "Get the current temperature for a specific location", parameters: { type: "object", properties: { location: { type: "string", description: "The city and state, e.g., San Francisco, CA", }, unit: { type: "string", enum: ["Celsius", "Fahrenheit"], description: "The temperature unit to use. Infer this from the user's location.", }, }, required: ["location", "unit"], }, }, }, { type: "function", function: { name: "getRainProbability", description: "Get the probability of rain for a specific location", parameters: { type: "object", properties: { location: { type: "string", description: "The city and state, e.g., San Francisco, CA", }, }, required: ["location"], }, }, }, ], }); ``` ```python from openai import OpenAI client = OpenAI() assistant = client.beta.assistants.create( instructions="You are a weather bot. Use the provided functions to answer questions.", model="gpt-4o", tools=[ { "type": "function", "function": { "name": "get_current_temperature", "description": "Get the current temperature for a specific location", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "The city and state, e.g., San Francisco, CA", }, "unit": { "type": "string", "enum": ["Celsius", "Fahrenheit"], "description": "The temperature unit to use. Infer this from the user's location.", }, }, "required": ["location", "unit"], }, }, }, { "type": "function", "function": { "name": "get_rain_probability", "description": "Get the probability of rain for a specific location", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "The city and state, e.g., San Francisco, CA", } }, "required": ["location"], }, }, }, ], ) ``` ```go assistant, err := client.Beta.Assistants.New(context.Background(), openai.BetaAssistantNewParams{ Model: shared.ChatModelGPT4o, Instructions: openai.String("You are a weather bot. Use the provided functions to answer questions."), Tools: weatherTools(false), }) if err != nil { panic(err) } func weatherTools(strict bool) []openai.AssistantToolUnionParam { return []openai.AssistantToolUnionParam{ openai.AssistantToolParamOfFunction(shared.FunctionDefinitionParam{ Name: "get_current_temperature", Description: openai.String("Get the current temperature for a specific location"), Parameters: map[string]any{ "type": "object", "properties": map[string]any{ "location": map[string]any{"type": "string", "description": "The city and state, e.g., San Francisco, CA"}, "unit": map[string]any{"type": "string", "enum": []string{"Celsius", "Fahrenheit"}, "description": "The temperature unit to use. Infer this from the user's location."}, }, "required": []string{"location", "unit"}, }, Strict: openai.Bool(strict), }), openai.AssistantToolParamOfFunction(shared.FunctionDefinitionParam{ Name: "get_rain_probability", Description: openai.String("Get the probability of rain for a specific location"), Parameters: map[string]any{ "type": "object", "properties": map[string]any{ "location": map[string]any{"type": "string", "description": "The city and state, e.g., San Francisco, CA"}, }, "required": []string{"location"}, }, Strict: openai.Bool(strict), }), } } ``` ### Step 2: Create a Thread and add Messages Create a Thread when a user starts a conversation and add Messages to the Thread as the user asks questions. ```javascript const thread = await client.beta.threads.create(); const message = client.beta.threads.messages.create(thread.id, { role: "user", content: "What's the weather in San Francisco today and the likelihood it'll rain?", }); ``` ```python thread = client.beta.threads.create() message = client.beta.threads.messages.create( thread_id=thread.id, role="user", content="What's the weather in San Francisco today and the likelihood it'll rain?", ) ``` ```go thread, err := client.Beta.Threads.New(context.Background(), openai.BetaThreadNewParams{}) if err != nil { panic(err) } _, err = client.Beta.Threads.Messages.New(context.Background(), thread.ID, openai.BetaThreadMessageNewParams{ Role: "user", Content: openai.BetaThreadMessageNewParamsContentUnion{ OfString: openai.String("What's the weather in San Francisco today and the likelihood it'll rain?"), }, }) if err != nil { panic(err) } ``` ### Step 3: Initiate a Run When you initiate a Run on a Thread containing a user Message that triggers one or more functions, the Run will enter a `pending` status. After it processes, the run will enter a `requires_action` state which you can verify by checking the Run’s `status`. This indicates that you need to run tools and submit their outputs to the Assistant to continue Run execution. In our case, we will see two `tool_calls`, which indicates that the user query resulted in parallel function calling. Note that a runs expire ten minutes after creation. Be sure to submit your tool outputs before the 10 min mark. You will see two `tool_calls` within `required_action`, which indicates the user query triggered parallel function calling. ```json { "id": "run_qJL1kI9xxWlfE0z1yfL0fGg9", ... "status": "requires_action", "required_action": { "submit_tool_outputs": { "tool_calls": [ { "id": "call_FthC9qRpsL5kBpwwyw6c7j4k", "function": { "arguments": "{"location": "San Francisco, CA"}", "name": "get_rain_probability" }, "type": "function" }, { "id": "call_RpEDoB8O0FTL9JoKTuCVFOyR", "function": { "arguments": "{"location": "San Francisco, CA", "unit": "Fahrenheit"}", "name": "get_current_temperature" }, "type": "function" } ] }, ... "type": "submit_tool_outputs" } } ```
Run object truncated here for readability
How you initiate a Run and submit `tool_calls` will differ depending on whether you are using streaming or not, although in both cases all `tool_calls` need to be submitted at the same time. You can then complete the Run by submitting the tool outputs from the functions you called. Pass each `tool_call_id` referenced in the `required_action` object to match outputs to each function call. With streaming For the streaming case, we create an EventHandler class to handle events in the response stream and submit all tool outputs at once with the “submit tool outputs stream” helper in the Python and Node SDKs. ```javascript class EventHandler extends EventEmitter { constructor(client) { super(); this.client = client; } async onEvent(event) { try { console.log(event); // Retrieve events that are denoted with 'requires_action' // since these will have our tool_calls if (event.event === "thread.run.requires_action") { await this.handleRequiresAction( event.data, event.data.id, event.data.thread_id ); } } catch (error) { console.error("Error handling event:", error); } } async handleRequiresAction(data, runId, threadId) { try { const toolOutputs = data.required_action.submit_tool_outputs.tool_calls.map((toolCall) => { if (toolCall.function.name === "getCurrentTemperature") { return { tool_call_id: toolCall.id, output: "57", }; } else if (toolCall.function.name === "getRainProbability") { return { tool_call_id: toolCall.id, output: "0.06", }; } }); // Submit all the tool outputs at the same time await this.submitToolOutputs(toolOutputs, runId, threadId); } catch (error) { console.error("Error processing required action:", error); } } async submitToolOutputs(toolOutputs, runId, threadId) { try { // Use the submitToolOutputsStream helper const stream = this.client.beta.threads.runs.submitToolOutputsStream( runId, { thread_id: threadId, tool_outputs: toolOutputs } ); for await (const event of stream) { this.emit("event", event); } } catch (error) { console.error("Error submitting tool outputs:", error); } } } const eventHandler = new EventHandler(client); eventHandler.on("event", eventHandler.onEvent.bind(eventHandler)); const stream = await client.beta.threads.runs.stream(threadId, { assistant_id: assistantId, }); for await (const event of stream) { eventHandler.emit("event", event); } ``` ```python from typing_extensions import override from openai import AssistantEventHandler class EventHandler(AssistantEventHandler): @override def on_event(self, event): # Retrieve events that are denoted with 'requires_action' # since these will have our tool_calls if event.event == "thread.run.requires_action": run_id = event.data.id # Retrieve the run ID from the event data self.handle_requires_action(event.data, run_id) def handle_requires_action(self, data, run_id): tool_outputs = [] for tool in data.required_action.submit_tool_outputs.tool_calls: if tool.function.name == "get_current_temperature": tool_outputs.append({"tool_call_id": tool.id, "output": "57"}) elif tool.function.name == "get_rain_probability": tool_outputs.append({"tool_call_id": tool.id, "output": "0.06"}) # Submit all tool_outputs at the same time self.submit_tool_outputs(tool_outputs, run_id) def submit_tool_outputs(self, tool_outputs, run_id): # Use the submit_tool_outputs_stream helper with client.beta.threads.runs.submit_tool_outputs_stream( thread_id=self.current_run.thread_id, run_id=self.current_run.id, tool_outputs=tool_outputs, event_handler=EventHandler(), ) as stream: for text in stream.text_deltas: print(text, end="", flush=True) print() with client.beta.threads.runs.stream( thread_id=thread.id, assistant_id=assistant.id, event_handler=EventHandler(), ) as stream: stream.until_done() ``` Without streaming Runs are asynchronous, which means you'll want to monitor their `status` by polling the Run object until a [terminal status](https://developers.openai.com/api/docs/assistants/deep-dive#runs-and-run-steps) is reached. For convenience, where available, the 'create and poll' SDK helpers assist both in creating the run and then polling for its completion. The Go tab shows the equivalent workflow with manual polling. Once the Run completes, you can list the Messages added to the Thread by the Assistant. Finally, you would retrieve all the `tool_outputs` from `required_action` and submit them at the same time to the 'submit tool outputs and poll' helper. ```javascript const handleRequiresAction = async (run) => { // Check if there are tools that require outputs if ( run.required_action && run.required_action.submit_tool_outputs && run.required_action.submit_tool_outputs.tool_calls ) { // Loop through each tool in the required action section const toolOutputs = run.required_action.submit_tool_outputs.tool_calls.map( (tool) => { if (tool.function.name === "getCurrentTemperature") { return { tool_call_id: tool.id, output: "57", }; } else if (tool.function.name === "getRainProbability") { return { tool_call_id: tool.id, output: "0.06", }; } } ); // Submit all tool outputs at once after collecting them in a list if (toolOutputs.length > 0) { run = await client.beta.threads.runs.submitToolOutputsAndPoll(run.id, { thread_id: thread.id, tool_outputs: toolOutputs, }); console.log("Tool outputs submitted successfully."); } else { console.log("No tool outputs to submit."); } // Check status after submitting tool outputs return handleRunStatus(run); } }; const handleRunStatus = async (run) => { // Check if the run is completed if (run.status === "completed") { let messages = await client.beta.threads.messages.list(thread.id); console.log(messages.data); return messages.data; } else if (run.status === "requires_action") { console.log(run.status); return await handleRequiresAction(run); } else { console.error("Run did not complete:", run); } }; // Create and poll run let run = await client.beta.threads.runs.createAndPoll(thread.id, { assistant_id: assistant.id, }); handleRunStatus(run); ``` ```python run = client.beta.threads.runs.create_and_poll( thread_id=thread.id, assistant_id=assistant.id, ) if run.status == "completed": messages = client.beta.threads.messages.list(thread_id=thread.id) print(messages) # Define the list to store tool outputs tool_outputs = [] # Loop through each tool in the required action section if run.required_action: for tool in run.required_action.submit_tool_outputs.tool_calls: if tool.function.name == "get_current_temperature": tool_outputs.append({"tool_call_id": tool.id, "output": "57"}) elif tool.function.name == "get_rain_probability": tool_outputs.append({"tool_call_id": tool.id, "output": "0.06"}) # Submit all tool outputs at once after collecting them in a list if tool_outputs: try: run = client.beta.threads.runs.submit_tool_outputs_and_poll( thread_id=thread.id, run_id=run.id, tool_outputs=tool_outputs, ) print("Tool outputs submitted successfully.") except Exception as e: print("Failed to submit tool outputs:", e) else: print("No tool outputs to submit.") if run.status == "completed": messages = client.beta.threads.messages.list(thread_id=thread.id) print(messages) else: print(run.status) ``` ```go run, err := client.Beta.Threads.Runs.New(context.Background(), thread.ID, openai.BetaThreadRunNewParams{ AssistantID: assistant.ID, }) if err != nil { panic(err) } run = pollRun(client, thread.ID, run) if run.Status == openai.RunStatusRequiresAction { outputs := make([]openai.BetaThreadRunSubmitToolOutputsParamsToolOutput, 0) for _, toolCall := range run.RequiredAction.SubmitToolOutputs.ToolCalls { switch toolCall.Function.Name { case "get_current_temperature": outputs = append(outputs, openai.BetaThreadRunSubmitToolOutputsParamsToolOutput{ ToolCallID: openai.String(toolCall.ID), Output: openai.String("57"), }) case "get_rain_probability": outputs = append(outputs, openai.BetaThreadRunSubmitToolOutputsParamsToolOutput{ ToolCallID: openai.String(toolCall.ID), Output: openai.String("0.06"), }) } } if len(outputs) > 0 { run, err = client.Beta.Threads.Runs.SubmitToolOutputs( context.Background(), thread.ID, run.ID, openai.BetaThreadRunSubmitToolOutputsParams{ToolOutputs: outputs}, ) if err != nil { panic(err) } run = pollRun(client, thread.ID, run) } } if run.Status == openai.RunStatusCompleted { messages, err := client.Beta.Threads.Messages.List(context.Background(), thread.ID, openai.BetaThreadMessageListParams{}) if err != nil { panic(err) } fmt.Println(messages.Data) } else { fmt.Println(run.Status) } func pollRun(client openai.Client, threadID string, run *openai.Run) *openai.Run { for run.Status == openai.RunStatusQueued || run.Status == openai.RunStatusInProgress { time.Sleep(time.Second) next, err := client.Beta.Threads.Runs.Get(context.Background(), threadID, run.ID) if err != nil { panic(err) } run = next } return run } ``` ### Using Structured Outputs When you enable [Structured Outputs](https://developers.openai.com/api/docs/guides/structured-outputs) by supplying `strict: true`, the OpenAI API will pre-process your supplied schema on your first request, and then use this artifact to constrain the model to your schema. ```javascript const assistant = await client.beta.assistants.create({ model: "gpt-4o-2024-08-06", instructions: "You are a weather bot. Use the provided functions to answer questions.", tools: [ { type: "function", function: { name: "getCurrentTemperature", description: "Get the current temperature for a specific location", parameters: { type: "object", properties: { location: { type: "string", description: "The city and state, e.g., San Francisco, CA", }, unit: { type: "string", enum: ["Celsius", "Fahrenheit"], description: "The temperature unit to use. Infer this from the user's location.", }, }, required: ["location", "unit"], // highlight-start additionalProperties: false, // highlight-end }, // highlight-start strict: true, // highlight-end }, }, { type: "function", function: { name: "getRainProbability", description: "Get the probability of rain for a specific location", parameters: { type: "object", properties: { location: { type: "string", description: "The city and state, e.g., San Francisco, CA", }, }, required: ["location"], // highlight-start additionalProperties: false, // highlight-end }, // highlight-start strict: true, // highlight-end }, }, ], }); ``` ```python from openai import OpenAI client = OpenAI() assistant = client.beta.assistants.create( instructions="You are a weather bot. Use the provided functions to answer questions.", model="gpt-4o-2024-08-06", tools=[ { "type": "function", "function": { "name": "get_current_temperature", "description": "Get the current temperature for a specific location", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "The city and state, e.g., San Francisco, CA", }, "unit": { "type": "string", "enum": ["Celsius", "Fahrenheit"], "description": "The temperature unit to use. Infer this from the user's location.", }, }, "required": ["location", "unit"], # highlight-start "additionalProperties": False, # highlight-end }, # highlight-start "strict": True, # highlight-end }, }, { "type": "function", "function": { "name": "get_rain_probability", "description": "Get the probability of rain for a specific location", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "The city and state, e.g., San Francisco, CA", } }, "required": ["location"], # highlight-start "additionalProperties": False, # highlight-end }, # highlight-start "strict": True, # highlight-end }, }, ], ) ``` ```go assistant, err := client.Beta.Assistants.New(context.Background(), openai.BetaAssistantNewParams{ Model: shared.ChatModelGPT4o2024_08_06, Instructions: openai.String("You are a weather bot. Use the provided functions to answer questions."), Tools: weatherTools(), }) if err != nil { panic(err) } func weatherTools() []openai.AssistantToolUnionParam { return []openai.AssistantToolUnionParam{ openai.AssistantToolParamOfFunction(shared.FunctionDefinitionParam{ Name: "get_current_temperature", Description: openai.String("Get the current temperature for a specific location"), Parameters: map[string]any{ "type": "object", "properties": map[string]any{ "location": map[string]any{"type": "string", "description": "The city and state, e.g., San Francisco, CA"}, "unit": map[string]any{"type": "string", "enum": []string{"Celsius", "Fahrenheit"}, "description": "The temperature unit to use. Infer this from the user's location."}, }, "required": []string{"location", "unit"}, "additionalProperties": false, }, Strict: openai.Bool(true), }), openai.AssistantToolParamOfFunction(shared.FunctionDefinitionParam{ Name: "get_rain_probability", Description: openai.String("Get the probability of rain for a specific location"), Parameters: map[string]any{ "type": "object", "properties": map[string]any{ "location": map[string]any{"type": "string", "description": "The city and state, e.g., San Francisco, CA"}, }, "required": []string{"location"}, "additionalProperties": false, }, Strict: openai.Bool(true), }), } } ``` --- # Assistants migration guide After achieving feature parity in the Responses API, we've deprecated the Assistants API. It will shut down on August 26, 2026. Follow the [migration guide](https://developers.openai.com/platform/assistants/migration) to update your integration. [Learn more](https://platform.openai.com/docs/guides/migrate-to-responses). We're moving from the Assistants API to the new [Responses API](https://developers.openai.com/api/docs/guides/migrate-to-responses) for a simpler and more flexible mental model. Responses are simpler—send input items and get output items back. With the Responses API, you also get better performance and new features like [deep research](https://developers.openai.com/api/docs/guides/deep-research), [MCP](https://developers.openai.com/api/docs/guides/tools-connectors-mcp), and [computer use](https://developers.openai.com/api/docs/guides/tools-computer-use). This change also lets you manage conversations instead of passing back `previous_response_id`. ### What's changed?
Before Now Why?
`Assistants` `Prompts` Prompts hold configuration (model, tools, instructions) and are easier to version and update
`Threads` `Conversations` Streams of items instead of just messages
`Runs` `Responses` Responses send input items or use a conversation object and receive output items; tool call loops are explicitly managed
`Run steps` `Items` Generalized objects—can be messages, tool calls, outputs, and more
## From assistants to prompts Assistants were persistent API objects that bundled model choice, instructions, and tool declarations—created and managed entirely through the API. Their replacement, prompts, can only be created in the dashboard, where you can version them as you develop your product. ### Why this is helpful - **Portability and versioning**: You can snapshot, review, diff, and roll back prompt specs. You can also version a prompt, so your code can just point the latest version. - **Separation of concerns**: Your application code now handles orchestration (history pruning, tool loop, retries) while your prompt focuses on high‑level behavior and constraints (system guidance, tool availability, structured output schema, temperature defaults). - **Realtime compatibility**: The same prompt configuration can be reused when you connect through the Realtime API, giving you a single definition of behavior across chat, streaming, and low‑latency interactive sessions. - **Tool and output consistency**: Using prompts, every Responses or Realtime session you start inherits a consistent contract because prompts encapsulate tool schemas and structured output expectations. ### Practical migration steps 1. Identify each existing Assistant’s _instruction + tool_ bundle. 2. In the dashboard, recreate that bundle as a named prompt. 3. Store the prompt ID (or its exported spec) in source control so application code can refer to a stable identifier. 4. During rollout, run A/B tests by swapping prompt IDs—no need to create or delete assistant objects programmatically. Think of a prompt as a **versioned behavioral profile** to plug into either Responses or Realtime API. --- ## From threads to conversations A thread was a collection of messages stored server-side. Threads could _only_ store messages. Conversations store items, which can include messages, tool calls, tool outputs, and other data. ### Request example #### Python #### Go ### Response example #### Thread object ```json { "id": "thread_CrXtCzcyEQbkAcXuNmVSKFs1", "object": "thread", "created_at": 1752855924, "metadata": { "user_id": "peter_le_fleur" }, "tool_resources": {} } ``` #### Conversation object ```json { "id": "conv_68542dc602388199a30af27d040cefd4087a04b576bfeb24", "object": "conversation", "created_at": 1752855924, "metadata": { "user_id": "peter_le_fleur" } } ``` --- ## From runs to responses Runs were asynchronous processes that executed against threads. See the example below. Responses are simpler: provide a set of input items to execute, and get a list of output items back. Responses are designed to be used alone, but you can also use them with prompt and conversation objects for storing context and configuration. ### Request example #### Python #### Go ### Response example #### Run object ```json { "id": "run_FKIpcs5ECSwuCmehBqsqkORj", "assistant_id": "asst_8fVY45hU3IM6creFkVi5MBKB", "cancelled_at": null, "completed_at": 1752857327, "created_at": 1752857322, "expires_at": null, "failed_at": null, "incomplete_details": null, "instructions": null, "last_error": null, "max_completion_tokens": null, "max_prompt_tokens": null, "metadata": {}, "model": "gpt-4.1", "object": "thread.run", "parallel_tool_calls": true, "required_action": null, "response_format": "auto", "started_at": 1752857324, "status": "completed", "thread_id": "thread_CrXtCzcyEQbkAcXuNmVSKFs1", "tool_choice": "auto", "tools": [], "truncation_strategy": { "type": "auto", "last_messages": null }, "usage": { "completion_tokens": 130, "prompt_tokens": 34, "total_tokens": 164, "prompt_token_details": { "cached_tokens": 0 }, "completion_tokens_details": { "reasoning_tokens": 0 } }, "temperature": 1.0, "top_p": 1.0, "tool_resources": {}, "reasoning_effort": null } ``` #### Response object ```json { "id": "resp_687a7b53036c819baad6012d58b39bcb074adcd9e24850fc", "created_at": 1752857427, "conversation": { "id": "conv_689667905b048191b4740501625afd940c7533ace33a2dab" }, "error": null, "incomplete_details": null, "instructions": null, "metadata": {}, "model": "gpt-5.5", "object": "response", "output": [ { "id": "msg_687a7b542948819ba79e77e14791ef83074adcd9e24850fc", "content": [ { "annotations": [], "text": "The \"5 Ds of Dodgeball\" are a humorous set of rules made famous by the 2004 comedy film **\"Dodgeball: A True Underdog Story.\"** In the movie, dodgeball coach Patches O’Houlihan teaches these basics to his team. The **5 Ds** are:\n\n1. **Dodge**\n2. **Duck**\n3. **Dip**\n4. **Dive**\n5. **Dodge** (yes, dodge is listed twice for emphasis!)\n\nIn summary: \n> **“If you can dodge a wrench, you can dodge a ball!”**\n\nThese 5 Ds are not official competitive rules, but have become a fun and memorable pop culture reference for the sport of dodgeball.", "type": "output_text", "logprobs": [] } ], "role": "assistant", "status": "completed", "type": "message" } ], "parallel_tool_calls": true, "temperature": 1.0, "tool_choice": "auto", "tools": [], "top_p": 1.0, "background": false, "max_output_tokens": null, "previous_response_id": null, "reasoning": { "effort": null, "generate_summary": null, "summary": null }, "service_tier": "scale", "status": "completed", "text": { "format": { "type": "text" } }, "truncation": "disabled", "usage": { "input_tokens": 17, "input_tokens_details": { "cached_tokens": 0 }, "output_tokens": 150, "output_tokens_details": { "reasoning_tokens": 0 }, "total_tokens": 167 }, "user": null, "max_tool_calls": null, "store": true, "top_logprobs": 0 } ``` --- ## Migrating your integration Follow the migration steps below to move from the Assistants API to the Responses API, without losing any feature support. ### 1. Create prompts from your assistants 1. Identify the most important assistant objects in your application. 1. Find these in the dashboard and click `Create prompt`. This will create a prompt object out of each existing assistant object. Reusable prompt objects are also being deprecated. If you use this migration path, review the [prompts deprecation timeline](https://developers.openai.com/api/docs/deprecations#2026-06-03-reusable-prompts) before adopting prompt objects in a long-lived integration. ### 2. Move new user chats over to conversations and responses We will not provide an automated tool for migrating Threads to Conversations. Instead, we recommend migrating new user threads onto conversations and migrating older ones as necessary. Here's an example for how you might backfill a thread: ```python import os from openai import OpenAI openai = OpenAI() messages = [] thread_id = os.environ["OPENAI_THREAD_ID"] for page in openai.beta.threads.messages.list( thread_id=thread_id, order="asc" ).iter_pages(): messages += page.data items = [] for m in messages: item = {"role": m.role} item_content = [] for content in m.content: match content.type: case "text": item_content_type = "input_text" if m.role == "user" else "output_text" item_content += [ {"type": item_content_type, "text": content.text.value} ] case "image_url": item_content += [ { "type": "input_image", "image_url": content.image_url.url, "detail": content.image_url.detail, } ] item |= {"content": item_content} items.append(item) # create a conversation with your converted items conversation = openai.conversations.create(items=items) ``` ## Comparing full examples Here are a few examples of integrations using both the Assistants API and the Responses API so you can see how they compare. ### User chat app Assistants API ```python threads_by_session: dict[str, str] = {} @app.post("/messages") async def message(message: Message): thread_id = threads_by_session.get(message.session_id) if thread_id is None: thread_id = openai.beta.threads.create().id threads_by_session[message.session_id] = thread_id openai.beta.threads.messages.create( thread_id=thread_id, role="user", content=message.content, ) run = openai.beta.threads.runs.create( assistant_id=os.environ["OPENAI_ASSISTANT_ID"], thread_id=thread_id, ) while run.status in ("queued", "in_progress"): await asyncio.sleep(1) run = openai.beta.threads.runs.retrieve( thread_id=thread_id, run_id=run.id, ) messages = openai.beta.threads.messages.list( order="desc", limit=1, thread_id=thread_id, ) return {"content": messages.data[0].content} ``` Responses API ```python conversations_by_session: dict[str, str] = {} @app.post("/messages") async def message(message: Message): conversation_id = conversations_by_session.get(message.session_id) if conversation_id is None: conversation_id = openai.conversations.create().id conversations_by_session[message.session_id] = conversation_id response = openai.responses.create( prompt={"id": os.environ["OPENAI_PROMPT_ID"]}, input=[{"role": "user", "content": message.content}], conversation=conversation_id, ) return {"content": response.output_text} ``` --- # Overview of OpenAI Crawlers OpenAI uses web crawlers (“robots”) and user agents to perform actions for its products, either automatically or triggered by user request. OpenAI uses OAI-SearchBot and GPTBot robots.txt tags to enable webmasters to manage how their sites and content work with AI. Each setting is independent of the others – for example, a webmaster can allow OAI-SearchBot in order to appear in search results while disallowing GPTBot to indicate that crawled content should not be used for training OpenAI’s generative AI foundation models. If your site has allowed both bots, we may use the results from just one crawl for both use cases to avoid duplicative crawling. For search results, please note it can take ~24 hours from a site’s robots.txt update for our systems to adjust. | User agent | Description & details | | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | OAI-SearchBot | OAI-SearchBot is for search. OAI-SearchBot is used to surface websites in search results in ChatGPT's search features. Sites that are opted out of OAI-SearchBot will not be shown in ChatGPT search answers, though can still appear as navigational links. To help ensure your site appears in search results, we recommend allowing OAI-SearchBot in your site’s robots.txt file and allowing requests from our published IP ranges below. Example user-agent string (the version number may change): `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36; compatible; OAI-SearchBot/1.4; +https://openai.com/searchbot` When fetching robots.txt files, we may use a user-agent string with an additional `robots.txt` marker. The marker helps site owners more easily distinguish requests for the robots.txt file from requests for other resources, especially when their logs do not include paths: `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36; compatible; OAI-SearchBot/1.4; robots.txt; +https://openai.com/searchbot` Published IP addresses: https://openai.com/searchbot.json | OAI-AdsBot | OAI-AdsBot is used to validate the safety of web pages submitted as ads on ChatGPT. When you submit an ad, OpenAI may visit the landing page to ensure it complies with our policies. We may also use content from the landing page to determine when it's most relevant to show the ad to users. OAI-AdsBot only visits pages submitted as ads, and the data collected by OAI-AdsBot is not used to train generative AI foundation models. Full user-agent string: `Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; OAI-AdsBot/1.0; +https://openai.com/adsbot` Published IP addresses: https://openai.com/adsbot.json | GPTBot | GPTBot is used to make our generative AI foundation models more useful and safe. It is used to crawl content that may be used in training our generative AI foundation models. Disallowing GPTBot indicates a site’s content should not be used in training generative AI foundation models. Example user-agent string (the version number may change): `Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.4; +https://openai.com/gptbot` When fetching robots.txt files, we may add a `robots.txt` marker to the user-agent string to help site owners distinguish those requests from requests for other resources, especially when logs do not include paths. Published IP addresses: https://openai.com/gptbot.json | ChatGPT-User | OpenAI also uses ChatGPT-User for certain user actions in ChatGPT and [Custom GPTs](https://openai.com/index/introducing-gpts/). When users ask ChatGPT or a CustomGPT a question, it may visit a web page with a ChatGPT-User agent. ChatGPT users may also interact with external applications via [GPT Actions](https://developers.openai.com/api/docs/actions/introduction). ChatGPT-User is not used for crawling the web in an automatic fashion. Because these actions are initiated by a user, robots.txt rules may not apply. ChatGPT-User is not used to determine whether content may appear in Search. Please use OAI-SearchBot in robots.txt for managing Search opt outs and automatic crawl. Full user-agent string: `Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; ChatGPT-User/1.0; +https://openai.com/bot` Published IP addresses: https://openai.com/chatgpt-user.json --- # Key concepts At OpenAI, protecting user data is fundamental to our mission. We do not train our models on inputs and outputs through our API. Learn more on our [API data privacy page](https://openai.com/api-data-privacy). ## Text generation models OpenAI's text generation models (often referred to as generative pre-trained transformers or "GPT" models for short), like [`gpt-5.6`](https://developers.openai.com/api/docs/models/gpt-5.6-sol) and [`gpt-5.6-terra`](https://developers.openai.com/api/docs/models/gpt-5.6-terra), have been trained to understand natural and formal language. These models allow text outputs in response to their inputs. The inputs to these models are also referred to as "prompts." Designing a prompt is essentially how you "program" a model, usually by providing instructions or some examples of how to successfully complete a task. GPT models can be used across a great variety of tasks including content or code generation, summarization, conversation, creative writing, and more. Read more in our introductory [text generation guide](https://developers.openai.com/api/docs/guides/text) and in our [prompt engineering guide](https://developers.openai.com/api/docs/guides/prompt-engineering). ## Embeddings An embedding is a vector representation of a piece of data (e.g. some text) that is meant to preserve aspects of its content and/or its meaning. Chunks of data that are similar in some way will tend to have embeddings that are closer together than unrelated data. OpenAI offers text embedding models that take as input a text string and produce as output an embedding vector. Embeddings are useful for search, clustering, recommendations, anomaly detection, classification, and more. Read more about embeddings in our [embeddings guide](https://developers.openai.com/api/docs/guides/embeddings). ## Tokens Text generation and embeddings models process text in chunks called tokens. Tokens represent commonly occurring sequences of characters. For example, the string " tokenization" is decomposed as " token" and "ization", while a short and common word like " the" is represented as a single token. Note that in a sentence, the first token of each word typically starts with a space character. Check out our [tokenizer tool](https://platform.openai.com/tokenizer) to test specific strings and see how they are translated into tokens. As a rough rule of thumb, 1 token is approximately 4 characters or 0.75 words for English text. One limitation to keep in mind is that for a text generation model the prompt and the generated output combined must be no more than the model's maximum context length. For embeddings models (which do not output tokens), the input must be shorter than the model's maximum context length. The maximum context lengths for each text generation and embeddings model can be found in the [model index](https://developers.openai.com/api/docs/models). --- # Deprecations ## Overview As we launch safer and more capable models, we regularly retire older models. Software relying on OpenAI models may need occasional updates to keep working. Impacted customers will always be notified by email and in our documentation along with [blog posts](https://openai.com/blog) for larger changes. This page lists all API deprecations, along with recommended replacements. ## Model deprecation notice periods We provide advance notice before retiring models so customers have time to plan and migrate. When we announce a model deprecation, we notify customers who are actively using the model by email and document the deprecation on this page. Unless safety or compliance concerns require a faster timeline, we provide the following minimum notice periods before model retirement: - **Generally available models:** At least 6 months. - **Specialized variants of generally available models:** At least 3 months. Examples include chat variants such as `gpt-5.1-chat-latest`, Codex variants such as `gpt-5.3-codex`, and deep research variants such as `o3-deep-research`. - **Preview models:** Preview models, identified by `preview` in the model name, may be retired with much shorter notice, such as 2 weeks. Examples include `computer-use-preview` and `gpt-4o-audio-preview`. We don't recommend using preview models for business-critical production workloads unless you can migrate on short notice. If safety or compliance concerns require us to retire a model sooner, we will provide as much notice as reasonably possible. These notice periods give customers time to evaluate recommended replacement models, test application behavior, and complete migrations before a model is no longer available. In some cases, developers may be able to provision dedicated capacity for continued access after a model's shutdown date. To explore this option, [contact our sales team](https://openai.com/contact-sales/). ## Deprecation vs. legacy We use the term "deprecation" to refer to the process of retiring a model or endpoint. When we announce that a model or endpoint is being deprecated, it immediately becomes deprecated. All deprecated models and endpoints will also have a shut down date. At the time of the shut down, the model or endpoint will no longer be accessible. We use the terms "sunset" and "shut down" interchangeably to mean a model or endpoint is no longer accessible. We use the term "legacy" to refer to models and endpoints that no longer receive updates. We tag endpoints and models as legacy to signal to developers where we're moving as a platform and that they should likely migrate to newer models or endpoints. You can expect that a legacy model or endpoint will be deprecated at some point in the future. ## Upcoming deprecations Upcoming deprecations are listed below, with the most recent announcements at the top. ### 2026-07-20: Legacy audio, realtime, and transcription models On July 20, 2026, we notified developers using legacy audio, realtime, and transcription model families and snapshots of their deprecation and removal from the API on January 20, 2027. | Shutdown date | Model family / snapshot | Recommended replacement | | ------------- | ----------------------------------- | ----------------------------------- | | Jan 20, 2027 | `gpt-realtime` | `gpt-realtime-2.1` | | Jan 20, 2027 | `gpt-audio` | `gpt-audio-1.5` | | Jan 20, 2027 | `gpt-4o-audio` | `gpt-audio-1.5` | | Jan 20, 2027 | `gpt-4o-realtime` | `gpt-realtime-2.1` | | Jan 20, 2027 | `gpt-realtime-mini` | `gpt-realtime-2.1-mini` | | Jan 20, 2027 | `gpt-audio-mini` | `gpt-audio-1.5` | | Jan 20, 2027 | `gpt-4o-mini-realtime` | `gpt-realtime-2.1-mini` | | Jan 20, 2027 | `gpt-4o-mini-audio` | `gpt-audio-1.5` | | Jan 20, 2027 | `gpt-4o-mini-transcribe-2025-03-20` | `gpt-4o-mini-transcribe-2025-12-15` | ### 2026-06-11: GPT-5 and o3 model deprecations On June 11, 2026, we notified developers using older GPT-5 and o3 model snapshots of their deprecation and removal from the API on December 11, 2026. | Shutdown date | Model / system | Recommended replacement | | ------------- | ----------------------- | ------------------------------------- | | Dec 11, 2026 | `gpt-5-2025-08-07` | `gpt-5.6-sol` | | Dec 11, 2026 | `gpt-5-mini-2025-08-07` | `gpt-5.6-terra` | | Dec 11, 2026 | `gpt-5-nano-2025-08-07` | `gpt-5.6-luna` | | Dec 11, 2026 | `gpt-5-pro-2025-10-06` | `gpt-5.6-sol` (`reasoning.mode: pro`) | | Dec 11, 2026 | `o3-2025-04-16` | `gpt-5.6-sol` | | Dec 11, 2026 | `o3-pro-2025-06-10` | `gpt-5.6-sol` (`reasoning.mode: pro`) | ### 2026-06-03: Reusable prompts On June 3, 2026, we notified developers using reusable prompts in the dashboard and API that reusable prompt objects are being deprecated. | Date | Update | | ------------ | ---------------------------------------------------------------------------- | | June 3, 2026 | Deprecation announced and prompt creation de-emphasized in the platform. | | Nov 30, 2026 | The `v1/prompts` API and reusable prompt objects are scheduled to shut down. | To migrate, move reusable prompt content into your application code. See [Migrate from prompt objects](https://developers.openai.com/api/docs/guides/prompting/migrate-from-prompt-object). ### 2026-06-03: Evals platform On June 3, 2026, we notified developers using the Evals platform that the product is being deprecated. | Date | Update | | ------------ | ------------------------------------------------------- | | June 3, 2026 | Deprecation announced for the Evals platform. | | Oct 31, 2026 | Existing evals become read-only. | | Nov 30, 2026 | The Evals dashboard and API are scheduled to shut down. | Graders documented for eval workflows are part of this transition. Fine-tuning-related timelines remain covered in the self-serve fine-tuning section below. See [Moving from OpenAI Evals to Promptfoo](https://developers.openai.com/cookbook/examples/evaluation/moving-from-openai-evals-to-promptfoo) for a migration path. ### 2026-06-03: Agent Builder On June 3, 2026, we notified developers using Agent Builder that the product is being deprecated. ChatKit remains available. | Date | Update | | ------------ | ---------------------------------------- | | June 3, 2026 | Deprecation announced for Agent Builder. | | Nov 30, 2026 | Agent Builder is scheduled to shut down. | See [Migrate from Agent Builder](https://developers.openai.com/api/docs/guides/agent-builder/migrate-from-agent-builder) to continue with the Agents SDK or ChatGPT Workspace Agents. ### 2026-06-02: GPT Image model deprecations On June 2, 2026, we notified developers using older GPT Image models of their deprecation and removal from the API on December 1, 2026. | Shutdown date | Model / system | Recommended replacement | | ------------- | ---------------------- | ----------------------- | | Dec 1, 2026 | `gpt-image-1-mini` | `gpt-image-2` | | Dec 1, 2026 | `gpt-image-1.5` | `gpt-image-2` | | Dec 1, 2026 | `chatgpt-image-latest` | `gpt-image-2` | ### 2026-05-08: gpt-5.2-chat-latest and gpt-5.3-chat-latest model snapshots On May 8th, 2026, we notified developers using `gpt-5.2-chat-latest` and `gpt-5.3-chat-latest` model snapshots of their deprecation and removal from the API. | Shutdown date | Model / system | Recommended replacement | | ------------- | --------------------- | ----------------------- | | Aug 10, 2026 | `gpt-5.2-chat-latest` | `gpt-5.6-sol` | | Aug 10, 2026 | `gpt-5.3-chat-latest` | `gpt-5.6-sol` | ### Update to OpenAI’s self-serve fine-tuning On May 7th, 2026, we notified developers using OpenAI’s self-serve fine-tuning platform of updates to availability. Inference on fine-tuned models will continue to be available until the base models are deprecated. | Date | Update | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | May 7, 2026 | Creating fine-tuning jobs or training is not available to organizations that have not previously run fine-tuning. | | July 2, 2026 | Creating fine-tuning jobs is no longer available to organizations that have not run inference on a fine-tuned model in the past 60 days. | | Jan 6, 2027 | Active existing customers will no longer be able to create new fine-tuning jobs on this date. Inference on fine-tuned models will be disabled only when the underlying base model is deprecated. | ### 2026-04-22: Legacy GPT model snapshots To improve reliability and make it easier for developers to choose the right models, we are deprecating a set of older OpenAI models. Access to these models will be shut down on the dates below. | Shutdown date | Model snapshot | Substitute model | | ---------------- | ---------------------------------------------------------------------- | ------------------------------------- | | October 23, 2026 | `gpt-3.5-turbo-0125` \| `gpt-3.5-turbo`, `gpt-3.5-turbo-completions` | `gpt-5.6-terra` | | October 23, 2026 | `gpt-4-0613` \| `gpt-4`, `gpt-4-0613-completions`, `gpt-4-completions` | `gpt-5.6-sol` | | October 23, 2026 | `gpt-4-1106-preview` | `gpt-5.6-sol` | | October 23, 2026 | `gpt-4-turbo` \| `gpt-4-turbo-2024-04-09`, `gpt-4-turbo-completions` | `gpt-5.6-sol` | | October 23, 2026 | `gpt-4.1-nano` \| `gpt-4.1-nano-2025-04-14` | `gpt-5.6-luna` | | October 23, 2026 | `gpt-4o-2024-05-13` | `gpt-5.6-sol` | | October 23, 2026 | `gpt-image-1` | `gpt-image-2` | | October 23, 2026 | `o1-2024-12-17` \| `o1` | `gpt-5.6-sol` | | October 23, 2026 | `o1-pro-2025-03-19` \| `o1-pro` | `gpt-5.6-sol` (`reasoning.mode: pro`) | | October 23, 2026 | `o3-mini-2025-01-31` \| `o3-mini` | `gpt-5.6-sol` | | October 23, 2026 | `ft-o4-mini-2025-04-16` | `gpt-5.6-terra` | | October 23, 2026 | `o4-mini-2025-04-16` \| `o4-mini` | `gpt-5.6-terra` | We are also removing fine-tuned versions as below: | Shutdown date | Model snapshot | Recommended replacement base model | | ---------------- | ---------------------------- | ---------------------------------- | | October 23, 2026 | `ft-gpt-3.5-turbo` | `gpt-5.6-terra` | | October 23, 2026 | `ft-gpt-4` | `gpt-5.6-sol` | | October 23, 2026 | `ft-gpt-4.1-nano-2025-04-14` | `gpt-5.6-luna` | | October 23, 2026 | `ft-babbage-002` | `gpt-5.6-terra` | | October 23, 2026 | `ft-davinci-002` | `gpt-5.6-terra` | ### 2026-03-24: Sora 2 video generation models and Videos API On March 24th, 2026, we notified developers using the Videos API and Sora 2 video generation model aliases and snapshots of their deprecation and removal from the API on September 24, 2026. | Shutdown date | Model / system | Recommended replacement | | ------------- | ----------------------- | ----------------------- | | 2026-09-24 | Videos API | --- | | 2026-09-24 | `sora-2` | --- | | 2026-09-24 | `sora-2-pro` | --- | | 2026-09-24 | `sora-2-2025-10-06` | --- | | 2026-09-24 | `sora-2-2025-12-08` | --- | | 2026-09-24 | `sora-2-pro-2025-10-06` | --- | ### 2025-09-26: Legacy GPT model snapshots To improve reliability and make it easier for developers to choose the right models, we are deprecating a set of older OpenAI models with declining usage over the next six to twelve months. Access to these models will be shut down on the dates below. | Shutdown date | Model / system | Recommended replacement | | ------------- | ------------------------ | ----------------------- | | 2026-09-28 | `gpt-3.5-turbo-instruct` | `gpt-5.6-terra` | | 2026-09-28 | `babbage-002` | `gpt-5.6-terra` | | 2026-09-28 | `davinci-002` | `gpt-5.6-terra` | | 2026-09-28 | `gpt-3.5-turbo-1106` | `gpt-5.6-terra` | ### 2025-08-20: Assistants API On August 26th, 2025, we notified developers using the Assistants API of its deprecation and removal from the API one year later, on August 26, 2026. When we released the [Responses API](https://developers.openai.com/api/reference/resources/responses/methods/create) in [March 2025](https://developers.openai.com/api/docs/changelog), we announced plans to bring all Assistants API features to the easier to use Responses API, with a sunset date in 2026. See the Assistants to Conversations [migration guide](https://developers.openai.com/api/docs/assistants/migration) to learn more about how to migrate your current integration to the Responses API and Conversations API. | Shutdown date | Model / system | Recommended replacement | | ------------- | -------------- | ----------------------------------- | | 2026‑08‑26 | Assistants API | Responses API and Conversations API | ## Past deprecations Past deprecations are listed below, with the most recent announcements at the top. ### 2026-04-22: Legacy GPT model snapshots (July 2026 shutdown) On April 22, 2026, we announced the deprecation of the following older OpenAI models. Access to these models was shut down on July 23, 2026. | Shutdown date | Model snapshot | Substitute model | | ------------- | ------------------------------------------------------------- | ----------------------- | | July 23, 2026 | `computer-use-preview-2025-03-11` \| `computer-use-preview` | `gpt-5.6-terra` | | July 23, 2026 | `gpt-4o-mini-search-preview-2025-03-11` | `gpt-5.6-terra` | | July 23, 2026 | `gpt-4o-search-preview-2025-03-11` | `gpt-5.6-terra` | | July 23, 2026 | `gpt-5-chat-latest` | `gpt-5.6-sol` | | July 23, 2026 | `gpt-5-codex` | `gpt-5.6-sol` | | July 23, 2026 | `gpt-5.1-chat-latest` | `gpt-5.6-sol` | | July 23, 2026 | `gpt-5.1-codex` | `gpt-5.6-sol` | | July 23, 2026 | `gpt-5.1-codex-max` | `gpt-5.6-sol` | | July 23, 2026 | `gpt-5.1-codex-mini` | `gpt-5.6-terra` | | July 23, 2026 | `gpt-audio-mini-2025-10-06` | `gpt-audio-1.5` | | July 23, 2026 | `gpt-realtime-mini-2025-10-06` | `gpt-realtime-2.1-mini` | | July 23, 2026 | `o3-deep-research-2025-06-26` \| `o3-deep-research` | `gpt-5.6-sol` | | July 23, 2026 | `o4-mini-deep-research-2025-06-26` \| `o4-mini-deep-research` | `gpt-5.6-sol` | | July 23, 2026 | `gpt-5.2-codex` | `gpt-5.6-sol` | ### 2025-11-18: chatgpt-4o-latest snapshot On November 18th, 2025, we notified developers using `chatgpt-4o-latest` model snapshot of its deprecation and removal from the API on February 17, 2026. | Shutdown date | Model / system | Recommended replacement | | ------------- | ------------------- | ----------------------- | | 2026-02-17 | `chatgpt-4o-latest` | `gpt-5.1-chat-latest` | ### 2025-11-17: codex-mini-latest model snapshot On November 17th, 2025, we notified developers using `codex-mini-latest` model of its deprecation and removal from the API on February 12, 2026. As part of this deprecation, we will no longer support our legacy local shell tool, which is only available for use with `codex-mini-latest`. For new use cases, please use our latest shell tool. | Shutdown date | Model / system | Recommended replacement | | ------------- | ------------------- | ----------------------- | | 2026-02-12 | `codex-mini-latest` | `gpt-5-codex-mini` | ### 2025-11-14: DALL·E model snapshots On November 14th, 2025, we notified developers using DALL·E model snapshots of their deprecation and removal from the API on May 12, 2026. | Shutdown date | Model / system | Recommended replacement | | ------------- | -------------- | --------------------------------------------------- | | 2026-05-12 | `dall-e-2` | `gpt-image-2`, `gpt-image-1`, or `gpt-image-1-mini` | | 2026-05-12 | `dall-e-3` | `gpt-image-2`, `gpt-image-1`, or `gpt-image-1-mini` | ### 2025-09-26: Legacy GPT model snapshots (March 2026 shutdown) To improve reliability and make it easier for developers to choose the right models, we deprecated a set of older OpenAI models with declining usage. Access to these models was shut down on March 26, 2026. | Shutdown date | Model / system | Recommended replacement | | ------------- | -------------------------------------------------------------------------------------------------------------------------- | ----------------------- | | 2026‑03‑26 | `gpt-4-0314` | `gpt-5` or `gpt-4.1*` | | 2026‑03‑26 | `gpt-4-1106-preview` | `gpt-5` or `gpt-4.1*` | | 2026‑03‑26 | `gpt-4-0125-preview` (including `gpt-4-turbo-preview` and `gpt-4-turbo-preview-completions`, which point to this snapshot) | `gpt-5` or `gpt-4.1*` | \*For tasks that are especially latency sensitive and don't require reasoning ### 2025-09-15: Realtime API Beta The Realtime API Beta was deprecated and removed from the API on May 12, 2026. There are a few key differences between the interfaces in the Realtime beta API and the released GA API. See [the migration guide](https://developers.openai.com/api/docs/guides/realtime#beta-to-ga-migration) for the current GA interface and related Realtime docs. | Shutdown date | Model / system | Recommended replacement | | ------------- | ------------------------ | ----------------------- | | 2026‑05‑12 | OpenAI-Beta: realtime=v1 | Realtime API | ### 2025-09-15: gpt-4o-realtime-preview models In September, 2025, we notified developers using gpt-4o-realtime-preview models of their deprecation and removal from the API in six months. | Shutdown date | Model / system | Recommended replacement | | ------------- | ---------------------------------- | ----------------------- | | 2026-05-07 | gpt-4o-realtime-preview | gpt-realtime-1.5 | | 2026-05-07 | gpt-4o-realtime-preview-2025-06-03 | gpt-realtime-1.5 | | 2026-05-07 | gpt-4o-realtime-preview-2024-12-17 | gpt-realtime-1.5 | | 2026-05-07 | gpt-4o-mini-realtime-preview | gpt-realtime-mini | | 2026-05-07 | gpt-4o-audio-preview | gpt-audio-1.5 | | 2026-05-07 | gpt-4o-mini-audio-preview | gpt-audio-mini | ### 2025-06-10: gpt-4o-realtime-preview-2024-10-01 On June 10th, 2025, we notified developers using gpt-4o-realtime-preview-2024-10-01 of its deprecation and removal from the API in three months. | Shutdown date | Model / system | Recommended replacement | | ------------- | ---------------------------------- | ----------------------- | | 2025-10-10 | gpt-4o-realtime-preview-2024-10-01 | gpt-realtime-1.5 | ### 2025-06-10: gpt-4o-audio-preview-2024-10-01 On June 10th, 2025, we notified developers using `gpt-4o-audio-preview-2024-10-01` of its deprecation and removal from the API in three months. | Shutdown date | Model / system | Recommended replacement | | ------------- | --------------------------------- | ----------------------- | | 2025-10-10 | `gpt-4o-audio-preview-2024-10-01` | `gpt-audio-1.5` | ### 2025-04-28: text-moderation On April 28th, 2025, we notified developers using `text-moderation` of its deprecation and removal from the API in six months. | Shutdown date | Model / system | Recommended replacement | | ------------- | ------------------------ | ----------------------- | | 2025-10-27 | `text-moderation-007` | `omni-moderation` | | 2025-10-27 | `text-moderation-stable` | `omni-moderation` | | 2025-10-27 | `text-moderation-latest` | `omni-moderation` | ### 2025-04-28: o1-preview and o1-mini On April 28th, 2025, we notified developers using `o1-preview` and `o1-mini` of their deprecations and removal from the API in three months and six months respectively. | Shutdown date | Model / system | Recommended replacement | | ------------- | -------------- | ----------------------- | | 2025-07-28 | `o1-preview` | `o3` | | 2025-10-27 | `o1-mini` | `o4-mini` | ### 2025-04-14: GPT-4.5-preview On April 14th, 2025, we notified developers that the `gpt-4.5-preview` model is deprecated and will be removed from the API in the coming months. | Shutdown date | Model / system | Recommended replacement | | ------------- | ----------------- | ----------------------- | | 2025-07-14 | `gpt-4.5-preview` | `gpt-4.1` | ### 2024-10-02: Assistants API beta v1 In [April 2024](https://developers.openai.com/api/docs/assistants/migration) when we released the v2 beta version of the Assistants API, we announced that access to the v1 beta would be shut off by the end of 2024. Access to the v1 beta will be discontinued on December 18, 2024. See the Assistants API v2 beta [migration guide](https://developers.openai.com/api/docs/assistants/migration) to learn more about how to migrate your tool usage to the latest version of the Assistants API. | Shutdown date | Model / system | Recommended replacement | | ------------- | -------------------------- | -------------------------- | | 2024-12-18 | OpenAI-Beta: assistants=v1 | OpenAI-Beta: assistants=v2 | ### 2024-08-29: Fine-tuning training on babbage-002 and davinci-002 models On August 29th, 2024, we notified developers fine-tuning `babbage-002` and `davinci-002` that new fine-tuning training runs on these models will no longer be supported starting October 28, 2024. Fine-tuned models created from these base models are not affected by this deprecation, but you will no longer be able to create new fine-tuned versions with these models. | Shutdown date | Model / system | Recommended replacement | | ------------- | ----------------------------------------- | ----------------------- | | 2024-10-28 | New fine-tuning training on `babbage-002` | `gpt-4o-mini` | | 2024-10-28 | New fine-tuning training on `davinci-002` | `gpt-4o-mini` | ### 2024-06-06: GPT-4-32K and Vision Preview models On June 6th, 2024, we notified developers using `gpt-4-32k` and `gpt-4-vision-preview` of their upcoming deprecations in one year and six months respectively. As of June 17, 2024, only existing users of these models will be able to continue using them. | Shutdown date | Deprecated model | Deprecated model price | Recommended replacement | | ------------- | --------------------------- | -------------------------------------------------- | ----------------------- | | 2025-06-06 | `gpt-4-32k` | $60.00 / 1M input tokens + $120 / 1M output tokens | `gpt-4o` | | 2025-06-06 | `gpt-4-32k-0613` | $60.00 / 1M input tokens + $120 / 1M output tokens | `gpt-4o` | | 2025-06-06 | `gpt-4-32k-0314` | $60.00 / 1M input tokens + $120 / 1M output tokens | `gpt-4o` | | 2024-12-06 | `gpt-4-vision-preview` | $10.00 / 1M input tokens + $30 / 1M output tokens | `gpt-4o` | | 2024-12-06 | `gpt-4-1106-vision-preview` | $10.00 / 1M input tokens + $30 / 1M output tokens | `gpt-4o` | ### 2023-11-06: Chat model updates On November 6th, 2023, we [announced](https://openai.com/blog/new-models-and-developer-products-announced-at-devday) the release of an updated GPT-3.5-Turbo model (which now comes by default with 16k context) along with deprecation of `gpt-3.5-turbo-0613` and ` gpt-3.5-turbo-16k-0613`. As of June 17, 2024, only existing users of these models will be able to continue using them. | Shutdown date | Deprecated model | Deprecated model price | Recommended replacement | | ------------- | ------------------------ | -------------------------------------------------- | ----------------------- | | 2024-09-13 | `gpt-3.5-turbo-0613` | $1.50 / 1M input tokens + $2.00 / 1M output tokens | `gpt-3.5-turbo` | | 2024-09-13 | `gpt-3.5-turbo-16k-0613` | $3.00 / 1M input tokens + $4.00 / 1M output tokens | `gpt-3.5-turbo` | Fine-tuned models created from these base models are not affected by this deprecation, but you will no longer be able to create new fine-tuned versions with these models. ### 2023-08-22: Fine-tunes endpoint On August 22nd, 2023, we [announced](https://openai.com/blog/gpt-3-5-turbo-fine-tuning-and-api-updates) the new fine-tuning API (`/v1/fine_tuning/jobs`) and that the original `/v1/fine-tunes` API along with legacy models (including those fine-tuned with the `/v1/fine-tunes` API) will be shut down on January 04, 2024. This means that models fine-tuned using the `/v1/fine-tunes` API will no longer be accessible and you would have to fine-tune new models with the updated endpoint and associated base models. #### Fine-tunes endpoint | Shutdown date | System | Recommended replacement | | ------------- | ---------------- | ----------------------- | | 2024-01-04 | `/v1/fine-tunes` | `/v1/fine_tuning/jobs` | ### 2023-07-06: GPT and embeddings On July 06, 2023, we [announced](https://openai.com/blog/gpt-4-api-general-availability) the upcoming retirements of older GPT-3 and GPT-3.5 models served via the completions endpoint. We also announced the upcoming retirement of our first-generation text embedding models. They will be shut down on January 04, 2024. #### InstructGPT models | Shutdown date | Deprecated model | Deprecated model price | Recommended replacement | | ------------- | ------------------ | ---------------------- | ------------------------ | | 2024-01-04 | `text-ada-001` | $0.40 / 1M tokens | `gpt-3.5-turbo-instruct` | | 2024-01-04 | `text-babbage-001` | $0.50 / 1M tokens | `gpt-3.5-turbo-instruct` | | 2024-01-04 | `text-curie-001` | $2.00 / 1M tokens | `gpt-3.5-turbo-instruct` | | 2024-01-04 | `text-davinci-001` | $20.00 / 1M tokens | `gpt-3.5-turbo-instruct` | | 2024-01-04 | `text-davinci-002` | $20.00 / 1M tokens | `gpt-3.5-turbo-instruct` | | 2024-01-04 | `text-davinci-003` | $20.00 / 1M tokens | `gpt-3.5-turbo-instruct` | Pricing for the replacement `gpt-3.5-turbo-instruct` model can be found on the [pricing page](https://openai.com/api/pricing). #### Base GPT models | Shutdown date | Deprecated model | Deprecated model price | Recommended replacement | | ------------- | ------------------ | ---------------------- | ------------------------ | | 2024-01-04 | `ada` | $0.40 / 1M tokens | `babbage-002` | | 2024-01-04 | `babbage` | $0.50 / 1M tokens | `babbage-002` | | 2024-01-04 | `curie` | $2.00 / 1M tokens | `davinci-002` | | 2024-01-04 | `davinci` | $20.00 / 1M tokens | `davinci-002` | | 2024-01-04 | `code-davinci-002` | --- | `gpt-3.5-turbo-instruct` | Pricing for the replacement `babbage-002` and `davinci-002` models can be found on the [pricing page](https://openai.com/api/pricing). #### Edit models & endpoint | Shutdown date | Model / system | Recommended replacement | | ------------- | ----------------------- | ----------------------- | | 2024-01-04 | `text-davinci-edit-001` | `gpt-4o` | | 2024-01-04 | `code-davinci-edit-001` | `gpt-4o` | | 2024-01-04 | `/v1/edits` | `/v1/chat/completions` | #### Fine-tuning GPT models | Shutdown date | Deprecated model | Training price | Usage price | Recommended replacement | | ------------- | ---------------- | ------------------ | ------------------- | ---------------------------------------- | | 2024-01-04 | `ada` | $0.40 / 1M tokens | $1.60 / 1M tokens | `babbage-002` | | 2024-01-04 | `babbage` | $0.60 / 1M tokens | $2.40 / 1M tokens | `babbage-002` | | 2024-01-04 | `curie` | $3.00 / 1M tokens | $12.00 / 1M tokens | `davinci-002` | | 2024-01-04 | `davinci` | $30.00 / 1M tokens | $120.00 / 1K tokens | `davinci-002`, `gpt-3.5-turbo`, `gpt-4o` | #### First-generation text embedding models | Shutdown date | Deprecated model | Deprecated model price | Recommended replacement | | ------------- | ------------------------------- | ---------------------- | ------------------------ | | 2024-01-04 | `text-similarity-ada-001` | $4.00 / 1M tokens | `text-embedding-3-small` | | 2024-01-04 | `text-search-ada-doc-001` | $4.00 / 1M tokens | `text-embedding-3-small` | | 2024-01-04 | `text-search-ada-query-001` | $4.00 / 1M tokens | `text-embedding-3-small` | | 2024-01-04 | `code-search-ada-code-001` | $4.00 / 1M tokens | `text-embedding-3-small` | | 2024-01-04 | `code-search-ada-text-001` | $4.00 / 1M tokens | `text-embedding-3-small` | | 2024-01-04 | `text-similarity-babbage-001` | $5.00 / 1M tokens | `text-embedding-3-small` | | 2024-01-04 | `text-search-babbage-doc-001` | $5.00 / 1M tokens | `text-embedding-3-small` | | 2024-01-04 | `text-search-babbage-query-001` | $5.00 / 1M tokens | `text-embedding-3-small` | | 2024-01-04 | `code-search-babbage-code-001` | $5.00 / 1M tokens | `text-embedding-3-small` | | 2024-01-04 | `code-search-babbage-text-001` | $5.00 / 1M tokens | `text-embedding-3-small` | | 2024-01-04 | `text-similarity-curie-001` | $20.00 / 1M tokens | `text-embedding-3-small` | | 2024-01-04 | `text-search-curie-doc-001` | $20.00 / 1M tokens | `text-embedding-3-small` | | 2024-01-04 | `text-search-curie-query-001` | $20.00 / 1M tokens | `text-embedding-3-small` | | 2024-01-04 | `text-similarity-davinci-001` | $200.00 / 1M tokens | `text-embedding-3-small` | | 2024-01-04 | `text-search-davinci-doc-001` | $200.00 / 1M tokens | `text-embedding-3-small` | | 2024-01-04 | `text-search-davinci-query-001` | $200.00 / 1M tokens | `text-embedding-3-small` | ### 2023-06-13: Updated chat models On June 13, 2023, we announced new chat model versions in the [Function calling and other API updates](https://openai.com/blog/function-calling-and-other-api-updates) blog post. The three original versions will be retired in June 2024 at the earliest. As of January 10, 2024, only existing users of these models will be able to continue using them. | Shutdown date | Legacy model | Legacy model price | Recommended replacement | | ---------------------- | ------------ | ---------------------------------------------------- | ----------------------- | | at earliest 2024-06-13 | `gpt-4-0314` | $30.00 / 1M input tokens + $60.00 / 1M output tokens | `gpt-4o` | | Shutdown date | Deprecated model | Deprecated model price | Recommended replacement | | ------------- | -------------------- | ----------------------------------------------------- | ----------------------- | | 2024-09-13 | `gpt-3.5-turbo-0301` | $15.00 / 1M input tokens + $20.00 / 1M output tokens | `gpt-3.5-turbo` | | 2025-06-06 | `gpt-4-32k-0314` | $60.00 / 1M input tokens + $120.00 / 1M output tokens | `gpt-4o` | ### 2023-03-20: Codex models | Shutdown date | Deprecated model | Recommended replacement | | ------------- | ------------------ | ----------------------- | | 2023-03-23 | `code-davinci-002` | `gpt-4o` | | 2023-03-23 | `code-davinci-001` | `gpt-4o` | | 2023-03-23 | `code-cushman-002` | `gpt-4o` | | 2023-03-23 | `code-cushman-001` | `gpt-4o` | ### 2022-06-03: Legacy endpoints | Shutdown date | System | Recommended replacement | | ------------- | --------------------- | ----------------------------------------------------------------------------------------------------- | | 2022-12-03 | `/v1/engines` | [/v1/models](https://platform.openai.com/docs/api-reference/models/list) | | 2022-12-03 | `/v1/search` | [View transition guide](https://help.openai.com/en/articles/6272952-search-transition-guide) | | 2022-12-03 | `/v1/classifications` | [View transition guide](https://help.openai.com/en/articles/6272941-classifications-transition-guide) | | 2022-12-03 | `/v1/answers` | [View transition guide](https://help.openai.com/en/articles/6233728-answers-transition-guide) | ### Plain-text aliases - gpt-3.5-turbo-0125 | gpt-3.5-turbo, gpt-3.5-turbo-completions - gpt-4-0613 | gpt-4, gpt-4-0613-completions, gpt-4-completions - gpt-4-turbo | gpt-4-turbo-2024-04-09, gpt-4-turbo-completions - gpt-4.1-nano | gpt-4.1-nano-2025-04-14 - o1-2024-12-17 | o1 - o1-pro-2025-03-19 | o1-pro - o3-mini-2025-01-31 | o3-mini - o4-mini-2025-04-16 | o4-mini - computer-use-preview-2025-03-11 | computer-use-preview - o3-deep-research-2025-06-26 | o3-deep-research - o4-mini-deep-research-2025-06-26 | o4-mini-deep-research --- # GPT Release Notes Keep track of updates to OpenAI GPTs. You can also view all of the broader [ChatGPT releases](https://help.openai.com/en/articles/6825453-chatgpt-release-notes) which is used to share new features and capabilities. This page is maintained in a best effort fashion and may not reflect all changes being made. ### May 13th, 2024 - Actions can [return](https://developers.openai.com/api/docs/actions/sending-files#returning-files) up to 10 files per request to be integrated into the conversation ### April 8th, 2024 - Files created by Code Interpreter can now be [included](https://developers.openai.com/api/docs/actions/sending-files#sending-files) in POST requests ### Mar 18th, 2024 - GPT Builders can view and restore previous versions of their GPTs ### Mar 15th, 2024 - POST requests can [include up to ten files](https://developers.openai.com/api/docs/actions/sending-files#sending-files) (including DALL-E generated images) from the conversation ### Feb 22nd, 2024 - Users can now rate GPTs, which provides feedback for builders and signal for otherusers in the Store - Users can now leave private feedback for Builders if/when they opt in - Every GPT now has an About page with information about the GPT including Rating, Category, Conversation Count, Starter Prompts, and more - Builders can now link their social profiles from Twitter, LinkedIn, and GitHub to their GPT ### Jan 10th, 2024 - The [GPT Store](https://openai.com/blog/introducing-gpts) launched publicly, with categories and various leaderboards ### Nov 6th, 2023 - [GPTs](https://openai.com/blog/introducing-gpts) allow users to customize ChatGPT for various use cases and share these with other users --- # Actions in ChatKit Actions are a way for the ChatKit SDK frontend to trigger a streaming response without the user submitting a message. They can also be used to trigger side-effects outside ChatKit SDK. ## Triggering actions ### In response to user interaction with widgets Actions can be triggered by attaching an `ActionConfig` to any widget node that supports it. For example, you can respond to click events on Buttons. When a user clicks on this button, the action will be sent to your server where you can update the widget, run inference, stream new thread items, etc. ```python button = Button( label="Example", onClickAction=ActionConfig( type="example", payload={"id": 123}, ), ) ``` Actions can also be sent imperatively by your frontend with `sendAction()`. This is probably most useful when you need ChatKit to respond to interaction happening outside ChatKit, but it can also be used to chain actions when you need to respond on both the client and the server (more on that below). ```typescript await chatKit.sendAction({ type: "example", payload: { id: 123 }, }); ``` ## Handling actions ### On the server By default, actions are sent to your server. You can handle actions on your server by implementing the `action` method on `ChatKitServer`. ```python class MyChatKitServer(ChatKitServer[RequestContext]): async def action( self, thread: ThreadMetadata, action: Action[str, Any], sender: WidgetItem | None, context: RequestContext, ) -> AsyncIterator[Event]: if action.type == "example": await do_thing(action.payload["id"]) # Often you'll want to add a HiddenContextItem so the model # can see that the user did something. await self.store.add_thread_item( thread.id, HiddenContextItem( id="item_123", created_at=datetime.now(), content="The user did a thing", ), context, ) # Then you might want to run inference to stream a response # back to the user. async for event in self.generate(context, thread): yield event ``` Treat actions and their payloads as untrusted data because the client sends them to your server. ### Client Sometimes you’ll want to handle actions in your client integration. To do that you need to specify that the action should be sent to your client-side action handler by adding `handler="client"` to the `ActionConfig`. ```python button = Button( label="Example", onClickAction=ActionConfig(type="example", payload={"id": 123}, handler="client"), ) ``` Then, when the action is triggered, it will then be passed to a callback that you provide when instantiating ChatKit. ```typescript async function handleWidgetAction(action: WidgetAction) { if (action.type === "example") { const res = await doSomething(action); // You can fire off actions to your server from here as well. // For example, stream new thread items or update a widget. await chatKit.sendAction({ type: "example_complete", payload: res, }); } } chatKit.setOptions({ // Other options... widgets: { onAction: handleWidgetAction }, }); ``` ## Strongly typed actions By default `Action` and `ActionConfig` are not strongly typed. However, we do expose a `create` helper on `Action` that generates `ActionConfig`s from a set of strongly-typed actions. ```python class ExamplePayload(BaseModel): id: int ExampleAction = Action[Literal["example"], ExamplePayload] OtherAction = Action[Literal["other"], None] AppAction = Annotated[ ExampleAction | OtherAction, Field(discriminator="type"), ] ActionAdapter: TypeAdapter[AppAction] = TypeAdapter(AppAction) def parse_app_action(action: Action[str, Any]) -> AppAction: return ActionAdapter.validate_python(action) # Usage in a widget # Action provides a create helper which makes it easy to generate # ActionConfigs from strongly typed actions. button = Button( label="Example", onClickAction=ExampleAction.create(ExamplePayload(id=123)), ) # usage in action handler class MyChatKitServer(ChatKitServer[RequestContext]): async def action( self, thread: ThreadMetadata, action: Action[str, Any], sender: WidgetItem | None, context: RequestContext, ) -> AsyncIterator[Event]: # add custom error handling if needed app_action = parse_app_action(action) if app_action.type == "example": await do_thing(app_action.payload.id) yield ThreadItemDoneEvent( item=AssistantMessageItem( id=self.store.generate_item_id("message", thread, context), thread_id=thread.id, created_at=datetime.now(), content=[AssistantMessageContent(text="Action complete.")], ) ) ``` ## Use widgets and actions to create custom forms When widget nodes that take user input are mounted inside a `Form`, the values from those fields will be included in the `payload` of all actions that originate from within the `Form`. Form values are keyed in the `payload` by their `name` e.g. - `Select(name="title")` → `action.payload.title` - `Select(name="todo.title")` → `action.payload.todo.title` ```python form = Form( direction="col", validation="native", onSubmitAction=ActionConfig( type="update_todo", payload={"id": todo.id}, ), children=[ Title(value="Edit Todo"), Text(value="Title", color="secondary", size="sm"), Text( value=todo.title, editable=EditableProps(name="title", required=True), ), Text(value="Description", color="secondary", size="sm"), Text( value=todo.description, editable=EditableProps(name="description"), ), Button(label="Save", submit=True), ], ) class MyChatKitServer(ChatKitServer[RequestContext]): async def action( self, thread: ThreadMetadata, action: Action[str, Any], sender: WidgetItem | None, context: RequestContext, ) -> AsyncIterator[Event]: if action.type == "update_todo": todo_id = action.payload["id"] # Any action that originates from within the Form will # include title and description. title = action.payload["title"] description = action.payload["description"] await update_todo(todo_id, title, description) yield ThreadItemDoneEvent( item=AssistantMessageItem( id=self.store.generate_item_id("message", thread, context), thread_id=thread.id, created_at=datetime.now(), content=[AssistantMessageContent(text="Todo updated.")], ) ) ``` ### Validation `Form` uses basic native form validation; enforcing `required` and `pattern` on fields where they are configured and blocking submission when the form has any invalid field. We may add new validation modes with better UX, more expressive validation, custom error display, etc in the future. Until then, widgets are not a great medium for complex forms with tricky validation. If you have this need, a better pattern would be to use client side action handling to trigger a modal, show a custom form there, then pass the result back into ChatKit with `sendAction`. ### Treating `Card` as a `Form` You can pass `asForm=True` to `Card` and it will behave as a `Form`, running validation and passing collected fields to the Card’s `confirm` action. ### Payload key collisions If there is a naming collision with some other existing pre-defined key on your payload, the form value will be ignored. This is probably a bug, so we’ll emit an `error` event when we see this. ## Control loading state interactions in widgets Use `ActionConfig.loadingBehavior` to control how actions trigger different loading states in a widget. ```python button = Button( label="This may take a while...", onClickAction=ActionConfig( type="long_running_action_that_should_block_other_ui_interactions", loadingBehavior="container", ), ) ``` | Value | Behavior | | ----------- | ------------------------------------------------------------------------------------------------------------------------------- | | `auto` | The action will adapt to how it’s being used. (_default_) | | `self` | The action triggers loading state on the widget node that the action was bound to. | | `container` | The action triggers loading state on the entire widget container. This causes the widget to fade out slightly and become inert. | | `none` | No loading state | ### Using `auto` behavior Generally, we recommend using `auto`, which is the default. `auto` triggers loading states based on where the action is bound, for example: - `Button.onClickAction` → `self` - `Select.onChangeAction` → `none` - `Card.confirm.action` → `container` --- # Admin APIs Admin APIs let you automate organization management workflows such as user invitations, audit log review, project administration, API key management, spend limits and alerts, data retention, and rate limit operations. Use them for back-office automation, security workflows, and operational tooling that should run outside the dashboard. For endpoint details, see the [Administration API reference](https://developers.openai.com/api/reference/administration/overview), including [Admin API keys](https://developers.openai.com/api/reference/resources/admin/subresources/organization/subresources/admin_api_keys), [Invites](https://developers.openai.com/api/reference/resources/admin/subresources/organization/subresources/invites), [Users](https://developers.openai.com/api/reference/resources/admin/subresources/organization/subresources/users), [Projects](https://developers.openai.com/api/reference/resources/admin/subresources/organization/subresources/projects), [Spend limits](https://developers.openai.com/api/reference/resources/admin/subresources/organization/subresources/spend_limit), and [Audit logs](https://developers.openai.com/api/reference/resources/admin/subresources/organization/subresources/audit_logs). ## Use an Admin API key with the SDK To access these endpoints, [create an Admin API key](https://platform.openai.com/settings/organization/admin-keys). Admin API keys cannot be used for non-administration endpoints. Support for Admin APIs was added in these SDK versions, which may require updating your SDK version: - Node: `6.36.0` - Python: `2.34.0` - Go: `3.34.0` - Ruby: `0.61.0` - Java: `4.34.0` Set `OPENAI_ADMIN_KEY`, then initialize the SDK for your language. Set up the SDK with an Admin API key ```javascript import OpenAI from "openai"; const client = new OpenAI({ adminAPIKey: process.env.OPENAI_ADMIN_KEY, }); ``` ```python import os from openai import OpenAI client = OpenAI( admin_api_key=os.environ["OPENAI_ADMIN_KEY"], ) ``` ```go package main import ( "os" "github.com/openai/openai-go/v3" "github.com/openai/openai-go/v3/option" ) func main() { client := openai.NewClient( option.WithAdminAPIKey(os.Getenv("OPENAI_ADMIN_KEY")), ) _ = client } ``` ```java import com.openai.client.OpenAIClient; import com.openai.client.okhttp.OpenAIOkHttpClient; OpenAIClient client = OpenAIOkHttpClient.builder().adminApiKey(System.getenv("OPENAI_ADMIN_KEY")).build(); ``` ```ruby require "openai" openai = OpenAI::Client.new( admin_api_key: ENV.fetch("OPENAI_ADMIN_KEY") ) ``` ## Restrict model access for projects Use project model permissions to set an allowlist or denylist for a project. Set `mode` to `allow_list` to allow only the listed models, or set `mode` to `deny_list` to block the listed models while allowing other available models. Model IDs must be visible to the organization, including visible fine-tuned model snapshots. Set a project model allowlist/denylist ```javascript const modelPermissions = await client.admin.organization.projects.modelPermissions.update("proj_abc", { mode: "allow_list", model_ids: ["gpt-4.1", "o3"], }); console.log(modelPermissions.mode); ``` ```python model_permissions = client.admin.organization.projects.model_permissions.update( "proj_abc", mode="allow_list", model_ids=["gpt-4.1", "o3"], ) print(model_permissions.mode) ``` ```go ctx := context.Background() modelPermissions, err := client.Admin.Organization.Projects.ModelPermissions.Update( ctx, "proj_abc", openai.AdminOrganizationProjectModelPermissionUpdateParams{ Mode: openai.AdminOrganizationProjectModelPermissionUpdateParamsModeAllowList, ModelIDs: []string{"gpt-4.1", "o3"}, }, ) if err != nil { panic(err) } println(modelPermissions.Mode) ``` ```java import com.openai.models.admin.organization.projects.modelpermissions.ModelPermissionUpdateParams; import com.openai.models.admin.organization.projects.modelpermissions.ProjectModelPermissions; import java.util.List; ProjectModelPermissions modelPermissions = client .admin() .organization() .projects() .modelPermissions() .update( "proj_abc", ModelPermissionUpdateParams.builder() .mode(ModelPermissionUpdateParams.Mode.ALLOW_LIST) .modelIds(List.of("gpt-4.1", "o3")) .build()); System.out.println(modelPermissions.mode()); ``` ```ruby model_permissions = openai.admin.organization.projects.model_permissions.update( "proj_abc", mode: :allow_list, model_ids: ["gpt-4.1", "o3"] ) puts(model_permissions.mode) ``` ## Set an organization spend limit Use the [Spend Limits endpoint](https://developers.openai.com/api/reference/resources/admin/subresources/organization/subresources/spend_limit) to create or replace your organization's monthly hard spend limit. Set `threshold_amount` in cents. The following example sets a $100 monthly limit: ```bash curl -X POST https://api.openai.com/v1/organization/spend_limit \ -H "Authorization: Bearer $OPENAI_ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{ "threshold_amount": 10000, "currency": "USD", "interval": "month" }' ``` When tracked spend reaches a hard limit, affected API requests return a `429` error. For details, see the [spend limits guide](https://developers.openai.com/api/docs/guides/spend-limits). ## Manage spend limit alerts Use project spend alerts to notify your team when project spend reaches a threshold. Threshold amounts are specified in cents. Create a project spend limit alert ```javascript const spendAlert = await client.admin.organization.projects.spendAlerts.create( "proj_abc", { currency: "USD", interval: "month", notification_channel: { recipients: ["billing@example.com"], type: "email", subject_prefix: "[OpenAI spend]", }, threshold_amount: 50000, } ); console.log(spendAlert.id); ``` ```python spend_alert = client.admin.organization.projects.spend_alerts.create( "proj_abc", currency="USD", interval="month", notification_channel={ "recipients": ["billing@example.com"], "type": "email", "subject_prefix": "[OpenAI spend]", }, threshold_amount=50000, ) print(spend_alert.id) ``` ```go ctx := context.Background() spendAlert, err := client.Admin.Organization.Projects.SpendAlerts.New( ctx, "proj_abc", openai.AdminOrganizationProjectSpendAlertNewParams{ Currency: openai.AdminOrganizationProjectSpendAlertNewParamsCurrencyUsd, Interval: openai.AdminOrganizationProjectSpendAlertNewParamsIntervalMonth, NotificationChannel: openai.AdminOrganizationProjectSpendAlertNewParamsNotificationChannel{ Recipients: []string{"billing@example.com"}, Type: "email", SubjectPrefix: openai.String("[OpenAI spend]"), }, ThresholdAmount: 50000, }, ) if err != nil { panic(err) } println(spendAlert.ID) ``` ```java import com.openai.models.admin.organization.projects.spendalerts.ProjectSpendAlert; import com.openai.models.admin.organization.projects.spendalerts.SpendAlertCreateParams; ProjectSpendAlert spendAlert = client .admin() .organization() .projects() .spendAlerts() .create( "proj_abc", SpendAlertCreateParams.builder() .currency(SpendAlertCreateParams.Currency.USD) .interval(SpendAlertCreateParams.Interval.MONTH) .notificationChannel( SpendAlertCreateParams.NotificationChannel.builder() .addRecipient("billing@example.com") .subjectPrefix("[OpenAI spend]") .build()) .thresholdAmount(50000L) .build()); System.out.println(spendAlert.id()); ``` ```ruby spend_alert = openai.admin.organization.projects.spend_alerts.create( "proj_abc", currency: :USD, interval: :month, notification_channel: { recipients: ["billing@example.com"], type: :email, subject_prefix: "[OpenAI spend]" }, threshold_amount: 50_000 ) puts(spend_alert.id) ``` ## Manage data retention Use project data retention controls to override or inherit the organization's retention policy for a project. Set `retention_type` to `organization_default` to inherit the organization setting. Set project data retention ```javascript const dataRetention = await client.admin.organization.projects.dataRetention.update("proj_abc", { retention_type: "organization_default", }); console.log(dataRetention.type); ``` ```python data_retention = client.admin.organization.projects.data_retention.update( "proj_abc", retention_type="organization_default", ) print(data_retention.type) ``` ```go ctx := context.Background() dataRetention, err := client.Admin.Organization.Projects.DataRetention.Update( ctx, "proj_abc", openai.AdminOrganizationProjectDataRetentionUpdateParams{ RetentionType: openai.AdminOrganizationProjectDataRetentionUpdateParamsRetentionTypeOrganizationDefault, }, ) if err != nil { panic(err) } println(dataRetention.Type) ``` ```java import com.openai.models.admin.organization.projects.dataretention.DataRetentionUpdateParams; import com.openai.models.admin.organization.projects.dataretention.ProjectDataRetention; ProjectDataRetention dataRetention = client .admin() .organization() .projects() .dataRetention() .update( "proj_abc", DataRetentionUpdateParams.builder() .retentionType(DataRetentionUpdateParams.RetentionType.ORGANIZATION_DEFAULT) .build()); System.out.println(dataRetention.type()); ``` ```ruby data_retention = openai.admin.organization.projects.data_retention.update( "proj_abc", retention_type: :organization_default ) puts(data_retention.type) ``` ## Invite a user by email Use the Invites endpoint to send an organization invitation to an email address. Invite a user by email ```javascript const invite = await client.admin.organization.invites.create({ email: "user@example.com", role: "reader", }); console.log(invite.id); ``` ```python invite = client.admin.organization.invites.create( email="user@example.com", role="reader", ) print(invite.id) ``` ```go ctx := context.Background() invite, err := client.Admin.Organization.Invites.New(ctx, openai.AdminOrganizationInviteNewParams{ Email: "user@example.com", Role: openai.AdminOrganizationInviteNewParamsRoleReader, }) if err != nil { panic(err) } println(invite.ID) ``` ```java import com.openai.models.admin.organization.invites.Invite; import com.openai.models.admin.organization.invites.InviteCreateParams; Invite invite = client .admin() .organization() .invites() .create( InviteCreateParams.builder() .email("user@example.com") .role(InviteCreateParams.Role.READER) .build()); System.out.println(invite.id()); ``` ```ruby invite = openai.admin.organization.invites.create( email: "user@example.com", role: :reader ) puts(invite.id) ``` ## Retrieve audit logs Use the Audit Logs endpoint to list recent user actions and configuration changes for the organization. Retrieve audit logs ```javascript const auditLogs = await client.admin.organization.auditLogs.list({ limit: 10, }); console.log(auditLogs.data); ``` ```python audit_logs = client.admin.organization.audit_logs.list(limit=10) for audit_log in audit_logs.data: print(audit_log.id) ``` ```go ctx := context.Background() auditLogs, err := client.Admin.Organization.AuditLogs.List(ctx, openai.AdminOrganizationAuditLogListParams{ Limit: openai.Int(10), }) if err != nil { panic(err) } for _, auditLog := range auditLogs.Data { println(auditLog.ID) } ``` ```java import com.openai.models.admin.organization.auditlogs.AuditLogListParams; var page = client .admin() .organization() .auditLogs() .list(AuditLogListParams.builder().limit(10L).build()); page.data().forEach(auditLog -> System.out.println(auditLog.id())); ``` ```ruby audit_logs = openai.admin.organization.audit_logs.list(limit: 10) audit_logs.data.each do |audit_log| puts(audit_log.id) end ``` --- # Advanced integrations with ChatKit When you need full control—custom authentication, data residency, on‑prem deployment, or bespoke agent orchestration—you can run ChatKit on your own infrastructure. Use OpenAI's advanced self‑hosted option to use your own server and customized ChatKit. Agent Builder-hosted ChatKit workflows are in a transition window. For new ChatKit apps, build on your own server-side agent implementation with the ChatKit SDKs and the Agents SDK. See [ChatKit transition guidance →](https://developers.openai.com/api/docs/guides/chatkit) ## Run ChatKit on your own infrastructure At a high level, an advanced ChatKit integration is a process of building your own ChatKit server and adding widgets to build out your chat surface. You'll use OpenAI APIs and your ChatKit server to build a custom chat powered by OpenAI models. ![OpenAI-hosted ChatKit](https://cdn.openai.com/API/docs/images/self-hosted.png) ## Set up your ChatKit server Follow the [server guide on GitHub](https://github.com/openai/chatkit-python/blob/main/docs/server.md) to learn how to handle incoming requests, run tools, and stream results back to the client. The snippets below highlight the main components. ### 1. Install the server package ```bash pip install openai-chatkit ``` ### 2. Implement a server class `ChatKitServer` drives the conversation. Override `respond` to stream events whenever a user message or client tool output arrives. Helpers like `stream_agent_response` connect the server to the Agents SDK. ```python class MyChatKitServer(ChatKitServer[RequestContext]): async def respond( self, thread: ThreadMetadata, input: UserMessageItem | ClientToolCallOutputItem | None, context: RequestContext, ) -> AsyncIterator[Event]: items_page = await self.store.load_thread_items( thread.id, after=None, limit=20, order="desc", context=context, ) input_items = await simple_to_agent_input(list(reversed(items_page.data))) agent_context = AgentContext( thread=thread, store=self.store, request_context=context, ) result = Runner.run_streamed( assistant_agent, input_items, context=agent_context, ) async for event in stream_agent_response(agent_context, result): yield event ``` ### 3. Expose the endpoint Use your framework of choice to forward HTTP requests to the server instance. For example, with FastAPI: ```python from fastapi import FastAPI, Request, Response from fastapi.responses import StreamingResponse app = FastAPI() data_store = MemoryStore() server = MyChatKitServer(data_store) @app.post("/chatkit") async def chatkit_endpoint(request: Request): result = await server.process(await request.body(), {}) if isinstance(result, StreamingResult): return StreamingResponse(result, media_type="text/event-stream") return Response(content=result.json, media_type="application/json") ``` ### 4. Establish data store contract Implement `chatkit.store.Store` to persist threads, messages, and files using your preferred database. For local development, you can use an in-memory `Store` implementation. For production, use durable storage and consider storing the models as JSON blobs so library updates can evolve the schema without migrations. ### 5. Provide file store contract Provide a `FileStore` implementation if you support uploads. ChatKit works with direct uploads (the client POSTs the file to your endpoint) or two-phase uploads (the client requests a signed URL, then uploads to cloud storage). Expose previews to support inline thumbnails and handle deletions when threads are removed. ### 6. Trigger client tools from the server Client tools must be registered both in the client options and on your agent. Use `ctx.context.client_tool_call` to enqueue a call from an Agents SDK tool. ```python @function_tool(description_override="Add an item to the user's todo list.") async def add_to_todo_list(ctx: RunContextWrapper[AgentContext], item: str) -> None: ctx.context.client_tool_call = ClientToolCall( name="add_to_todo_list", arguments={"item": item}, ) assistant_agent = Agent[AgentContext]( model="gpt-5.6", name="Assistant", instructions="You are a helpful assistant", tools=[add_to_todo_list], tool_use_behavior=StopAtTools(stop_at_tool_names=[add_to_todo_list.name]), ) ``` ### 7. Use thread metadata and state Use `thread.metadata` to store server-side state such as the previous Responses API run ID or custom labels. Metadata is not exposed to the client but is available in every `respond` call. ### 8. Get tool status updates Long-running tools can stream progress to the UI with `ProgressUpdateEvent`. ChatKit replaces the progress event with the next assistant message or widget output. ### 9. Using server context Pass a custom context object to `server.process(body, context)` to enforce permissions or propagate user identity through your store and file store implementations. ## Add inline interactive widgets Widgets let agents surface rich UI inside the chat surface. Use them for cards, forms, text blocks, lists, and other layouts. The helper `stream_widget` can render a widget immediately or stream updates as they arrive. ```python async def respond( self, thread: ThreadMetadata, input: UserMessageItem | ClientToolCallOutputItem | None, context: RequestContext, ) -> AsyncIterator[Event]: widget = Card( children=[ Text( id="description", value="Generated summary", ) ] ) async for event in stream_widget( thread, widget, generate_id=lambda item_type: self.store.generate_item_id( item_type, thread, context ), ): yield event ``` ChatKit ships with a wide set of widget nodes (cards, lists, forms, text, buttons, and more). See [widgets guide on GitHub](https://github.com/openai/chatkit-python/blob/main/docs/widgets.md) for all components, props, and streaming guidance. See the [Widget Builder](https://widgets.chatkit.studio/) to explore and create widgets in an interactive UI. ## Use actions Actions let the ChatKit UI trigger work without sending a user message. Attach an `ActionConfig` to any widget node that supports it—buttons, selects, and other controls can stream new thread items or update widgets in place. When a widget lives inside a `Form`, ChatKit includes the collected form values in the action payload. On the server, implement the `action` method on `ChatKitServer` to process the payload and optionally stream additional events. You can also handle actions on the client by setting `handler="client"` and responding in JavaScript before forwarding follow-up work to the server. See the [actions guide on GitHub](https://github.com/openai/chatkit-python/blob/main/docs/actions.md) for patterns like chaining actions, creating strongly typed payloads, and coordinating client/server handlers. ## Resources Use the following resources and reference to complete your integration. ### Design resources - Download [OpenAI Sans Variable](https://drive.google.com/file/d/10-dMu1Oknxg3cNPHZOda9a1nEkSwSXE1/view?usp=sharing). - Duplicate the file and customize components for your product. ### Events reference ChatKit emits `CustomEvent` instances from the Web Component. The payload shapes are: ```typescript type Events = { "chatkit.error": CustomEvent<{ error: Error }>; "chatkit.response.start": CustomEvent; "chatkit.response.end": CustomEvent; "chatkit.thread.change": CustomEvent<{ threadId: string | null }>; "chatkit.log": CustomEvent<{ name: string; data?: Record }>; }; ``` ### Options reference | Option | Type | Description | Default | | --------------- | -------------------------- | ---------------------------------------------------------- | -------------- | | `apiURL` | `string` | Endpoint that implements the ChatKit server protocol. | _required_ | | `fetch` | `typeof fetch` | Override fetch calls (for custom headers or auth). | `window.fetch` | | `theme` | `"light" \| "dark"` | UI theme. | `"light"` | | `initialThread` | `string \| null` | Thread to open on mount; `null` shows the new thread view. | `null` | | `clientTools` | `Record` | Client-executed tools exposed to the model. | | | `header` | `object \| boolean` | Header configuration or `false` to hide the header. | `true` | | `newThreadView` | `object` | Customize greeting text and starter prompts. | | | `messages` | `object` | Configure message features (feedback, annotations, etc.). | | | `composer` | `object` | Control attachments, entity tags, and placeholder text. | | | `entities` | `object` | Callbacks for entity lookup, click handling, and previews. | | ### Plain-text aliases - "light" | "dark" - string | null - object | boolean --- # Advanced usage OpenAI's text generation models (often called generative pre-trained transformers or large language models) have been trained to understand natural language, code, and images. The models provide text outputs in response to their inputs. The text inputs to these models are also referred to as "prompts." Designing a prompt is essentially how you “program” a large language model, usually by providing instructions or some examples of how to successfully complete a task. ## Reproducible outputs Chat Completions are non-deterministic by default (which means model outputs may differ from request to request). That being said, we offer some control towards deterministic outputs by giving you access to the [`seed`](https://developers.openai.com/api/reference/resources/chat#chat-create-seed) parameter and the [`system_fingerprint`](https://developers.openai.com/api/reference/resources/completions#completions/object-system_fingerprint) response field. To receive (mostly) deterministic outputs across API calls, you can: - Set the [seed](https://developers.openai.com/api/reference/resources/chat#chat-create-seed) parameter to any integer of your choice and use the same value across requests you'd like deterministic outputs for. - Ensure all other parameters (like `prompt` or `temperature`) are the exact same across requests. Sometimes, determinism may be impacted due to necessary changes OpenAI makes to model configurations on our end. To help you keep track of these changes, we expose the [`system_fingerprint`](https://developers.openai.com/api/reference/resources/chat#chat/object-system_fingerprint) field. If this value is different, you may see different outputs due to changes we've made on our systems. [Deterministic outputs Explore the new seed parameter in the OpenAI cookbook](https://developers.openai.com/cookbook/examples/reproducible_outputs_with_the_seed_parameter) ## Managing tokens Language models read and write text in chunks called tokens. In English, a token can be as short as one character or as long as one word (for example, `a` or ` apple`), and in some languages tokens can be even shorter than one character or even longer than one word. As a rough rule of thumb, 1 token is approximately 4 characters or 0.75 words for English text. Check out our [Tokenizer tool](https://platform.openai.com/tokenizer) to test specific strings and see how they are translated into tokens. For example, the string `"ChatGPT is great!"` is encoded into six tokens: `["Chat", "G", "PT", " is", " great", "!"]`. The total number of tokens in an API call affects: - How much your API call costs, as you pay per token - How long your API call takes, as writing more tokens takes more time - Whether your API call works at all, as total tokens must be below the model's maximum limit (4097 tokens for `gpt-3.5-turbo`) Both input and output tokens count toward these quantities. For example, if your API call used 10 tokens in the message input and you received 20 tokens in the message output, you would be billed for 30 tokens. Note however that for some models the price per token is different for tokens in the input vs. the output (see the [pricing](https://openai.com/api/pricing) page for more information). To see how many tokens are used by an API call, check the `usage` field in the API response (for example, `response['usage']['total_tokens']`). Chat models like `gpt-3.5-turbo` and `gpt-4-turbo-preview` use tokens in the same way as the models available in the completions API, but because of their message-based formatting, it's more difficult to count how many tokens will be used by a conversation. Below is an example function for counting tokens for messages passed to `gpt-3.5-turbo-0613`. The exact way that messages are converted into tokens may change from model to model. So when future model versions are released, the answers returned by this function may be only approximate. ```python def num_tokens_from_messages(messages, model="gpt-3.5-turbo-0613"): """Returns the number of tokens used by a list of messages.""" try: encoding = tiktoken.encoding_for_model(model) except KeyError: encoding = tiktoken.get_encoding("cl100k_base") if model == "gpt-3.5-turbo-0613": # note: future models may deviate from this num_tokens = 0 for message in messages: num_tokens += ( 4 # every message follows {role/name}\n{content}\n ) for key, value in message.items(): num_tokens += len(encoding.encode(value)) if key == "name": # if there's a name, the role is omitted num_tokens += -1 # role is always required and always 1 token num_tokens += 2 # every reply is primed with assistant return num_tokens raise ValueError( f"num_tokens_from_messages() only supports gpt-3.5-turbo-0613, not {model}." ) ``` Next, create a message and pass it to the function defined above to see the token count, this should match the value returned by the API usage parameter: ```python messages = [ { "role": "system", "content": "You are a helpful, pattern-following assistant that translates corporate jargon into plain English.", }, { "role": "system", "name": "example_user", "content": "New synergies will help drive top-line growth.", }, { "role": "system", "name": "example_assistant", "content": "Things working well together will increase revenue.", }, { "role": "system", "name": "example_user", "content": "Let's circle back when we have more bandwidth to touch base on opportunities for increased leverage.", }, { "role": "system", "name": "example_assistant", "content": "Let's talk later when we're less busy about how to do better.", }, { "role": "user", "content": "This late pivot means we don't have time to boil the ocean for the client deliverable.", }, ] model = "gpt-3.5-turbo-0613" print(f"{num_tokens_from_messages(messages, model)} prompt tokens counted.") # Should show ~126 total_tokens ``` To confirm the number generated by our function above is the same as what the API returns, create a new Chat Completion: ```python # example token count from the OpenAI API from openai import OpenAI client = OpenAI() response = client.chat.completions.create( model=model, messages=messages, temperature=0, ) print(f"{response.usage.prompt_tokens} prompt tokens used.") ``` To see how many tokens are in a text string without making an API call, use OpenAI’s [tiktoken](https://github.com/openai/tiktoken) Python library. Example code can be found in the OpenAI Cookbook’s guide on [how to count tokens with tiktoken](https://developers.openai.com/cookbook/examples/how_to_count_tokens_with_tiktoken). Each message passed to the API consumes the number of tokens in the content, role, and other fields, plus a few extra for behind-the-scenes formatting. This may change slightly in the future. If a conversation has too many tokens to fit within a model’s maximum limit (for example, more than 4097 tokens for `gpt-3.5-turbo` or more than 128k tokens for `gpt-4o`), you will have to truncate, omit, or otherwise shrink your text until it fits. Beware that if a message is removed from the messages input, the model will lose all knowledge of it. Note that long conversations are more likely to receive incomplete replies. For example, a `gpt-3.5-turbo` conversation that is 4090 tokens long will have its reply cut off after just 6 tokens. ## Parameter details ### Frequency and presence penalties The frequency and presence penalties found in the [Chat Completions API](https://developers.openai.com/api/reference/resources/chat) and [Legacy Completions API](https://developers.openai.com/api/reference/resources/completions) can be used to reduce the likelihood of sampling repetitive sequences of tokens. They work by directly modifying the logits (un-normalized log-probabilities) with an additive contribution. ```python mu[j] = mu[j] - c[j] * alpha_frequency - float(c[j] > 0) * alpha_presence ``` Where: - `mu[j]` is the logits of the j-th token - `c[j]` is how often that token was sampled prior to the current position - `float(c[j] > 0)` is 1 if `c[j] > 0` and 0 otherwise - `alpha_frequency` is the frequency penalty coefficient - `alpha_presence` is the presence penalty coefficient As we can see, the presence penalty is a one-off additive contribution that applies to all tokens that have been sampled at least once and the frequency penalty is a contribution that is proportional to how often a particular token has already been sampled. Reasonable values for the penalty coefficients are around 0.1 to 1 if the aim is to just reduce repetitive samples somewhat. If the aim is to strongly suppress repetition, then one can increase the coefficients up to 2, but this can noticeably degrade the quality of samples. Negative values can be used to increase the likelihood of repetition. ### Token log probabilities The [`logprobs`](https://developers.openai.com/api/reference/resources/chat#chat-create-logprobs) parameter found in the [Chat Completions API](https://developers.openai.com/api/reference/resources/chat) and [Legacy Completions API](https://developers.openai.com/api/reference/resources/completions), when requested, provides the log probabilities of each output token, and a limited number of the most likely tokens at each token position alongside their log probabilities. This can be useful in some cases to assess the confidence of the model in its output, or to examine alternative responses the model might have given. ### Other parameters See the full [API reference documentation](https://platform.openai.com/docs/api-reference/chat) to learn more. --- # Agent Builder **Agent Builder** is a visual canvas for building multi-step agent workflows. You can start from templates, drag and drop nodes for each step in your workflow, provide typed inputs and outputs, and preview runs using live data. When you're ready to deploy, embed the workflow into your site with ChatKit, or download the SDK code to run it yourself. OpenAI is deprecating Agent Builder. Existing users can continue using it during the transition window, and the product is scheduled to shut down on November 30, 2026. ChatKit remains available. See the [deprecations page](https://developers.openai.com/api/docs/deprecations#2026-06-03-agent-builder) for the current timeline. Use this guide to learn the process and parts of building agents. ## Agents and workflows To build useful agents, you create workflows for them. A **workflow** is a combination of agents, tools, and control-flow logic. A workflow encapsulates all steps and actions involved in handling your tasks or powering your chats, with working code you can deploy when you're ready. Open Agent Builder There are three main steps in building agents to handle tasks: 1. Design a workflow in [Agent Builder](https://platform.openai.com/agent-builder). This defines your agents and how they'll work. 1. Publish your workflow. It's an object with an ID and versioning. 1. Deploy your workflow. Pass the ID into your [ChatKit](https://developers.openai.com/api/docs/guides/chatkit) integration, or download the Agents SDK code to deploy your workflow yourself. ## Compose with nodes In Agent Builder, insert and connect nodes to create your workflow. Each connection between nodes becomes a typed edge. Click a node to configure its inputs and outputs, observe the data contract between steps, and ensure downstream nodes receive the properties they expect. ### Examples and templates Agent Builder provides templates for common workflow patterns. Start with a template to see how nodes work together, or start from scratch. Here's a homework helper workflow. It uses agents to take questions, reframe them for better answers, route them to other specialized agents, and return an answer. ![prompts chat](https://cdn.openai.com/API/docs/images/homework-helper2.png) ### Available nodes Nodes are the building blocks for agents. To see all available nodes and their configuration options, see the [node reference documentation](https://developers.openai.com/api/docs/guides/node-reference). ### Preview and debug As you build, you can test your workflow by using the **Preview** feature. Here, you can interactively run your workflow, attach sample files, and observe the execution of each node. ### Safety and risks Building agent workflows comes with risks, like prompt injection and data leakage. See [safety in building agents](https://developers.openai.com/api/docs/guides/agent-builder-safety) to learn about and help mitigate the risks of agent workflows. ### Evaluate your workflow Run [trace graders](https://developers.openai.com/api/docs/guides/trace-grading) inside of Agent Builder. In the top navigation, click **Evaluate**. Here, you can select a trace (or set of traces) and run custom graders to assess overall workflow performance. ## Publish your workflow Agent Builder autosaves your work as you go. When you're happy with your workflow, publish it to create a new major version that acts as a snapshot. You can then use your workflow in [ChatKit](https://developers.openai.com/api/docs/guides/chatkit), an OpenAI framework for embedding chat experiences. You can create new versions or specify an older version in your API calls. ## Deploy in your product When you're ready to implement the agent workflow you created, click **Code** in the top navigation. You have two options for implementing your workflow in production: **ChatKit**: Follow the [ChatKit quickstart](https://developers.openai.com/api/docs/guides/chatkit) and pass in your workflow ID to embed this workflow into your application. If you're not sure, we recommend this option. **Advanced integration**: Copy the workflow code and use it anywhere. You can run ChatKit on your own infrastructure and use the Agents SDK to build and customize agent chat experiences. ## Next steps Now that you've created an agent workflow, bring it into your product with ChatKit. - [ChatKit quickstart](https://developers.openai.com/api/docs/guides/chatkit) → - [Advanced integration](https://developers.openai.com/api/docs/guides/custom-chatkit) → --- # Agent definitions An agent is the core unit of an SDK-based workflow. It packages a model, instructions, and optional runtime behavior such as tools, guardrails, MCP servers, handoffs, and structured outputs. ## What belongs on an agent Use agent configuration for decisions that are intrinsic to that specialist: | Property | Use it for | Read next | | ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------- | | `name` | Human-readable identity in traces and tool/handoff surfaces | This page | | `instructions` | The job, constraints, and style for that agent | This page | | `prompt` | Stored prompt configuration for Responses-based runs | [Models and providers](https://developers.openai.com/api/docs/guides/agents/models) | | `model` and model settings | Choosing the model and tuning behavior | [Models and providers](https://developers.openai.com/api/docs/guides/agents/models) | | `tools` | Capabilities the agent can call directly | [Using tools](https://developers.openai.com/api/docs/guides/tools#usage-in-the-agents-sdk) | | `handoffDescription` in TypeScript or `handoff_description` in Python | Hinting when another agent should delegate here | [Orchestration and handoffs](https://developers.openai.com/api/docs/guides/agents/orchestration) | | `handoffs` | Delegating to another agent | [Orchestration and handoffs](https://developers.openai.com/api/docs/guides/agents/orchestration) | | `outputType` in TypeScript or `output_type` in Python | Returning structured output instead of plain text | This page | | Guardrails and approvals | Validation, blocking, and review flows | [Guardrails and human review](https://developers.openai.com/api/docs/guides/agents/guardrails-approvals) | | MCP servers and hosted MCP tools | Attaching MCP-backed capabilities | [Integrations and observability](https://developers.openai.com/api/docs/guides/agents/integrations-observability#mcp) | ## Start with one focused agent Define the smallest agent that can own a clear task. Add more agents only when you need separate ownership, different instructions, different tool surfaces, or different approval policies. Define a single agent ```typescript import { Agent, tool } from "@openai/agents"; import { z } from "zod"; const getWeather = tool({ name: "get_weather", description: "Return the weather for a given city.", parameters: z.object({ city: z.string() }), async execute({ city }) { return `The weather in ${city} is sunny.`; }, }); const agent = new Agent({ name: "Weather bot", instructions: "You are a helpful weather bot.", model: "gpt-5.6", tools: [getWeather], }); ``` ```python from agents import Agent, function_tool @function_tool def get_weather(city: str) -> str: """Return the weather for a given city.""" return f"The weather in {city} is sunny." agent = Agent( name="Weather bot", instructions="You are a helpful weather bot.", model="gpt-5.6", tools=[get_weather], ) ``` ## Shape instructions, handoffs, and outputs Three configuration choices deserve extra care: - Start with static `instructions`. When the guidance depends on the current user, tenant, or runtime context, switch to a dynamic instructions callback instead of stitching strings together at the call site. - Keep `handoffDescription` in TypeScript or `handoff_description` in Python short and concrete so routing agents know when to pick this specialist. - Use `outputType` in TypeScript or `output_type` in Python when downstream code needs typed data rather than free-form prose. Return structured output ```typescript import { Agent, run } from "@openai/agents"; import { z } from "zod"; const calendarEvent = z.object({ name: z.string(), date: z.string(), participants: z.array(z.string()), }); const agent = new Agent({ name: "Calendar extractor", instructions: "Extract calendar events from text.", outputType: calendarEvent, }); const result = await run(agent, "Dinner with Priya and Sam on Friday."); console.log(result.finalOutput); ``` ```python import asyncio from pydantic import BaseModel from agents import Agent, Runner class CalendarEvent(BaseModel): name: str date: str participants: list[str] agent = Agent( name="Calendar extractor", instructions="Extract calendar events from text.", output_type=CalendarEvent, ) async def main() -> None: result = await Runner.run( agent, "Dinner with Priya and Sam on Friday.", ) print(result.final_output) if __name__ == "__main__": asyncio.run(main()) ``` Use `prompt` when you want to reference a stored prompt configuration from the Responses API instead of embedding the entire system prompt in code. ## Keep local context separate from model context The SDK lets you pass application state and dependencies into a run without sending them to the model. Use this for data like authenticated user info, database clients, loggers, and helper functions. Pass local context to tools ```typescript import { Agent, RunContext, run, tool } from "@openai/agents"; import { z } from "zod"; interface UserInfo { name: string; uid: number; } const fetchUserAge = tool({ name: "fetch_user_age", description: "Return the age of the current user.", parameters: z.object({}), async execute(_args, runContext?: RunContext) { return `User ${runContext?.context.name} is 47 years old`; }, }); const agent = new Agent({ name: "Assistant", tools: [fetchUserAge], }); const result = await run(agent, "What is the age of the user?", { context: { name: "John", uid: 123 }, }); console.log(result.finalOutput); ``` ```python import asyncio from dataclasses import dataclass from agents import Agent, RunContextWrapper, Runner, function_tool @dataclass class UserInfo: name: str uid: int @function_tool async def fetch_user_age(wrapper: RunContextWrapper[UserInfo]) -> str: """Fetch the age of the current user.""" return f"The user {wrapper.context.name} is 47 years old." agent = Agent[UserInfo]( name="Assistant", tools=[fetch_user_age], ) async def main() -> None: result = await Runner.run( agent, "What is the age of the user?", context=UserInfo(name="John", uid=123), ) print(result.final_output) if __name__ == "__main__": asyncio.run(main()) ``` The important boundary is: - Conversation history is what the model sees. - Run context is what your code sees. If the model needs a fact, put it in instructions, input, retrieval, or a tool. If only your runtime needs it, keep it in local context. ## When to split one agent into several Split an agent when one specialist shouldn't own the full reply or when separate capabilities are materially different. Common reasons are: - A specialist needs a different tool or MCP surface. - A specialist needs a different approval policy or guardrail. - One branch of the workflow needs a different model or output style. - You want explicit routing in traces rather than a single large prompt. ## Next steps Once one specialist is defined cleanly, move to the guide that matches the next design question. [Models and providers Choose models, defaults, and transport strategy for this agent.](https://developers.openai.com/api/docs/guides/agents/models) [Using tools Add capabilities the agent can call directly.](https://developers.openai.com/api/docs/guides/tools#usage-in-the-agents-sdk) [Orchestration and handoffs Choose how specialists collaborate once one agent is no longer enough.](https://developers.openai.com/api/docs/guides/agents/orchestration) [Running agents Understand the runtime loop, state, and streaming behavior.](https://developers.openai.com/api/docs/guides/agents/running-agents) --- # Agents SDK Agents are applications that plan, call tools, collaborate across specialists, and keep enough state to complete multi-step work. ## Get your first agent running Start with the [Agents SDK quickstart](https://developers.openai.com/api/docs/guides/agents/quickstart) to install the SDK, define one agent, and run it. Once that works, return here to choose the next capability your application needs. ## Get the Agents SDK Use the GitHub repositories for more examples, issues, and language-specific reference details. [TypeScript SDK Open the TypeScript SDK repository on GitHub.](https://github.com/openai/openai-agents-js) [Python SDK Open the Python SDK repository on GitHub.](https://github.com/openai/openai-agents-python) ## Choose your starting point | If you want to | Start here | Why | | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------- | | Build a code-first agent app | [Quickstart](https://developers.openai.com/api/docs/guides/agents/quickstart) | This is the shortest path to a working SDK integration. | | Define one specialist cleanly | [Agent definitions](https://developers.openai.com/api/docs/guides/agents/define-agents) | Start here when you are still shaping the contract for a single agent. | | Choose models, defaults, and transport | [Models and providers](https://developers.openai.com/api/docs/guides/agents/models) | Use this when model choice, provider setup, or transport strategy affects the workflow. | | Understand the runtime loop and state | [Running agents](https://developers.openai.com/api/docs/guides/agents/running-agents) | This is where the agent loop, streaming, and continuation strategies live. | | Run work in a container-based environment | [Sandbox agents](https://developers.openai.com/api/docs/guides/agents/sandboxes) | Use this when the agent needs files, commands, packages, snapshots, mounts, or provider links. | | Design specialist ownership | [Orchestration and handoffs](https://developers.openai.com/api/docs/guides/agents/orchestration) | Use this when you need more than one agent and must decide who owns the reply. | | Add validation or human review | [Guardrails and human review](https://developers.openai.com/api/docs/guides/agents/guardrails-approvals) | Use this when the workflow should block or pause before risky work continues. | | Understand what a run returns | [Results and state](https://developers.openai.com/api/docs/guides/agents/results) | This page explains final output, resumable state, and next-turn surfaces. | | Add hosted tools, function tools, or MCP | [Using tools](https://developers.openai.com/api/docs/guides/tools#usage-in-the-agents-sdk) and [Integrations and observability](https://developers.openai.com/api/docs/guides/agents/integrations-observability) | Tool semantics live in the platform tools docs; SDK-specific MCP and tracing live here. | | Inspect and improve runs | [Integrations and observability](https://developers.openai.com/api/docs/guides/agents/integrations-observability) and [evaluate agent workflows](https://developers.openai.com/api/docs/guides/agent-evals) | Use traces for debugging first, then move into evaluation loops. | | Build a voice-first workflow | [Voice agents](https://developers.openai.com/api/docs/guides/voice-agents) | Use the SDK's voice pipeline and realtime agent patterns. | ## Build with the SDK Use the SDK track when your server owns deployment, tool implementations, state storage, and approval decisions, while the SDK runs the agent loop and invokes those tools. That path is the best fit when you want: - typed application code in TypeScript or Python - direct control over tools, MCP servers, and runtime behavior - custom storage or server-managed conversation strategies - tight integration with existing product logic or infrastructure A typical SDK reading order is: - Start with [Quickstart](https://developers.openai.com/api/docs/guides/agents/quickstart) to get one working run on screen. - Use [Agent definitions](https://developers.openai.com/api/docs/guides/agents/define-agents) and [Models and providers](https://developers.openai.com/api/docs/guides/agents/models) to shape one specialist cleanly. - Continue to [Running agents](https://developers.openai.com/api/docs/guides/agents/running-agents), [Orchestration and handoffs](https://developers.openai.com/api/docs/guides/agents/orchestration), and [Guardrails and human review](https://developers.openai.com/api/docs/guides/agents/guardrails-approvals) as the workflow grows more complex. - Use [Results and state](https://developers.openai.com/api/docs/guides/agents/results) and [Integrations and observability](https://developers.openai.com/api/docs/guides/agents/integrations-observability) when application logic depends on the run object or deeper visibility into behavior. ## Agents SDK vs. Responses API Use the Responses API when you want to own the loop. Use the Agents SDK when you want the SDK to run it. ### Choose the Responses API when - You want direct control over model interactions, output items, tools, state, and orchestration, whether the workflow takes one call or many. - You want to implement custom tool routing, loops, or branching directly in your application. In the [Responses function-calling flow](https://developers.openai.com/api/docs/guides/function-calling#the-tool-calling-flow), your application receives function calls, executes them, returns their output, and calls the model again. For example, a Responses API workflow might search a knowledge base and generate a cited answer. ### Choose the Agents SDK when - You want the SDK to manage the agent loop and recurring orchestration such as repeated tool calls or branching. - Different specialists need different instructions, tools, or policies. - You want built-in sessions, tracing, guardrails, or resumable approval flows. The [Agents SDK runner](https://developers.openai.com/api/docs/guides/agents/running-agents#the-agent-loop) performs the tool loop, switches agents after handoffs, and stops when the run finishes or pauses for approval. For example, an Agents SDK workflow might investigate a support request, hand it to the correct specialist, call internal systems, request approval for a refund, and record the result. ### Compare the Responses API and Agents SDK | | Responses API | Agents SDK | | -------------------------- | ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Best for** | Custom model-powered features and workflows | Bounded conversational or transactional workflows with defined tools and recurring orchestration patterns | | **Core abstraction** | A model response | An agent run | | **Tools** | Platform tools, function calling, and remote [Model Context Protocol (MCP)](https://developers.openai.com/api/docs/guides/tools-connectors-mcp) | Platform tools attached to reusable agents, plus tool wrappers, local MCP connections, and [agents as tools](https://developers.openai.com/api/docs/guides/agents/orchestration#use-agents-as-tools-for-manager-style-workflows) | | **Workflow orchestration** | You manage custom loops and branching | The SDK provides the agent loop and lifecycle | | **Multi-agent workflows** | Build routing and delegation yourself | Built-in agents-as-tools and [handoffs](https://developers.openai.com/api/docs/guides/agents/orchestration#use-handoffs-for-delegated-ownership) | | **State** | Manual history, response chaining, or [Conversations](https://developers.openai.com/api/docs/guides/conversation-state#using-the-conversations-api) | The same options, plus [SDK sessions and resumable run state](https://developers.openai.com/api/docs/guides/agents/running-agents#choose-one-conversation-strategy) | | **Safety and approvals** | Tool-specific approvals; you build broader controls | Input, output, and tool [guardrails plus resumable approval flows](https://developers.openai.com/api/docs/guides/agents/guardrails-approvals) | | **Debugging and tracing** | Response objects and API logs | [Built-in traces](https://developers.openai.com/api/docs/guides/agents/integrations-observability#tracing) across model calls, tools, agents, guardrails, and handoffs | --- # API deployment checklist | Contents | Expected impact | | ------------------------------------------------------------------------------- | ----------------------------------- | | [Use the Responses API](#use-the-responses-api) | Quality, cost, latency, reliability | | [Choose a GPT-5.6 model](#choose-a-gpt-56-model) | Quality, cost, latency | | [Set up `reasoning.effort`](#set-up-reasoningeffort) | Quality, cost, latency | | [Set up `text.verbosity`](#set-up-textverbosity) | Quality, cost, latency | | [Set up the assistant `phase` parameter](#set-up-the-assistant-phase-parameter) | Quality, cost | | [Use `tool_search`](#use-toolsearch) | Cost, latency | | [Use Programmatic Tool Calling](#use-programmatic-tool-calling) | Quality, cost, latency | | [Use Multi-agent for parallel work](#use-multi-agent-for-parallel-work) | Quality, cost, latency | | [Leverage built-in tools](#leverage-built-in-tools) | Quality | | [Leverage compaction](#leverage-compaction) | Cost | | [Use `prompt_cache_key`](#use-promptcachekey) | Latency, cost | | [Use `reasoning.encrypted_content`](#use-reasoningencryptedcontent) | Quality, latency | | [Set image detail intentionally](#set-image-detail-intentionally) | Quality, cost, latency | | [Send a safety identifier](#send-a-safety-identifier) | Safety, reliability | | [Use `background=True`](#use-backgroundtrue) | Resumability | | [Use WebSocket mode](#use-websocket-mode) | Latency | ## Use the Responses API **Always start** with the [Responses API](https://developers.openai.com/api/docs/guides/migrate-to-responses). It is OpenAI's flagship API and the best place to access the newest model behavior, built-in tools, stateful workflows, and agent features. ## Choose a GPT-5.6 model Choose a [GPT-5.6 model](https://developers.openai.com/api/docs/guides/latest-model) for the workload instead of routing every request to the most capable tier. Use `gpt-5.6` or `gpt-5.6-sol` for frontier capability, `gpt-5.6-terra` for strong performance at a lower price, and `gpt-5.6-luna` for efficient, high-volume workloads. When migrating, preserve the current model's workload role and effective reasoning effort for the first comparison. Run representative evals before changing prompts or adding new capabilities. Compare task success, latency, input, output, reasoning, and cache-write tokens, and cost per successful task. ## Set up `reasoning.effort` Use `reasoning.effort` to decide how much thinking the model should do before it answers. For GPT-5.6 models, the supported values are `none`, `low`, `medium`, `high`, `xhigh`, and `max`. The default is `medium`. Lower effort is faster and uses fewer reasoning tokens. Higher effort gives the model more time for planning, debugging, synthesis, and multi-step tradeoffs. Use `low` when the job is mostly extraction, routing, classification, or a simple rewrite. Use `medium` or `high` when the model needs to diagnose a problem, compare options, write a plan, or reason through code. Use `xhigh` or `max` only when representative evals show that the quality gain justifies the extra latency and cost. When migrating from GPT-5.5 or GPT-5.4, start with the current effort and compare the same setting with one level lower. GPT-5.6 can often maintain or improve quality with fewer reasoning tokens, so the lower setting may also reduce latency and cost. For the hardest quality-first workloads, also compare [`reasoning.mode: "pro"`](https://developers.openai.com/api/docs/guides/reasoning#reasoning-mode) with standard mode at the same effort. Reasoning mode and effort are independent. Pro mode can improve reliability by applying more model work before returning a single final answer, but it increases latency and token usage. Tune reasoning effort for the task ```javascript import OpenAI from "openai"; const openai = new OpenAI(); const prompt = [ "Our CI job started failing after a dependency bump.", "", "Error:", "TypeError: Timeout.__init__() got an unexpected keyword argument 'connect'", "", "Identify the likeliest root cause and the smallest safe fix.", ].join("\n"); const response = await openai.responses.create({ model: "gpt-5.6", reasoning: { effort: "xhigh", mode: "pro" }, input: prompt, }); console.log(response.output_text); ``` ```python from openai import OpenAI client = OpenAI() prompt = """ Our CI job started failing after a dependency bump. Error: TypeError: Timeout.__init__() got an unexpected keyword argument 'connect' Identify the likeliest root cause and the smallest safe fix. """ response = client.responses.create( model="gpt-5.6", reasoning={"effort": "xhigh", "mode": "pro"}, input=prompt, ) print(response.output_text) ``` ```go package main import ( "context" "fmt" "strings" "github.com/openai/openai-go/v3" "github.com/openai/openai-go/v3/responses" "github.com/openai/openai-go/v3/shared" ) func main() { client := openai.NewClient() prompt := strings.Join([]string{ "Our CI job started failing after a dependency bump.", "", "Error:", "TypeError: Timeout.__init__() got an unexpected keyword argument 'connect'", "", "Identify the likeliest root cause and the smallest safe fix.", }, "\n") reasoning := shared.ReasoningParam{Effort: shared.ReasoningEffortXhigh} reasoning.SetExtraFields(map[string]any{"mode": "pro"}) response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{ Model: "gpt-5.6", Reasoning: reasoning, Input: responses.ResponseNewParamsInputUnion{OfString: openai.String(prompt)}, }) if err != nil { panic(err) } fmt.Println(response.OutputText()) } ``` ## Set up `text.verbosity` `text.verbosity` is the main lever for balancing brevity against completeness. Use lower verbosity when the product needs a quick, compact answer, and higher verbosity when the response needs richer explanation, clearer structure, or complete context. Lower verbosity means fewer output tokens, so the model generates less and returns output faster. For coding, `medium` and `high` tend to produce longer, more organized output with clearer structure. `low` keeps the answer tighter and more minimal. GPT-5.6 tends to be more concise by default than GPT-5.5. When migrating, check whether broad instructions like "Be concise" still help. In some cases, they may make responses too brief. Keep them only when they still help, and prefer using `text.verbosity` to control the default level of detail; then use the prompt to specify required content, structure, and a more specific length, if applicable. Set lower verbosity for compact output ```javascript import OpenAI from "openai"; const openai = new OpenAI(); const incident = [ "Summarize this incident for the next on-call engineer.", "- checkout latency spiked from 220 ms to 4.8 s", "- only us-east-1 was affected", "- rollback is complete", "- likely trigger: cache stampede after deploy", ].join("\n"); const response = await openai.responses.create({ model: "gpt-5.6", text: { verbosity: "low" }, input: incident, }); console.log(response.output_text); ``` ```python from openai import OpenAI client = OpenAI() response = client.responses.create( model="gpt-5.6", text={"verbosity": "low"}, input=""" Summarize this incident for the next on-call engineer. - checkout latency spiked from 220 ms to 4.8 s - only us-east-1 was affected - rollback is complete - likely trigger: cache stampede after deploy """, ) print(response.output_text) ``` ```go package main import ( "context" "fmt" "strings" "github.com/openai/openai-go/v3" "github.com/openai/openai-go/v3/responses" ) func main() { client := openai.NewClient() incident := strings.Join([]string{ "Summarize this incident for the next on-call engineer.", "- checkout latency spiked from 220 ms to 4.8 s", "- only us-east-1 was affected", "- rollback is complete", "- likely trigger: cache stampede after deploy", }, "\n") response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{ Model: "gpt-5.6", Text: responses.ResponseTextConfigParam{Verbosity: "low"}, Input: responses.ResponseNewParamsInputUnion{OfString: openai.String(incident)}, }) if err != nil { panic(err) } fmt.Println(response.OutputText()) } ``` ## Set up the assistant `phase` parameter `phase` is a label on assistant messages in the conversation history. It indicates to the model whether a prior assistant message was an intermediate working commentary or the final answer. Use `phase: "commentary"` for progress updates, pre-tool-call notes, and other in-between messages. Use `phase: "final_answer"` for the completed response. The assistant might say something like: Assistant commentary message ```json { "role": "assistant", "phase": "commentary", "content": "I'm checking the logs and comparing them to the last successful deploy." } ``` That is not the answer. It is a progress note. Later, the assistant might say: Assistant final answer message ```json { "role": "assistant", "phase": "final_answer", "content": "The deploy failed because the migration referenced a column that does not exist in production." } ``` This is useful in long-running or tool-heavy workflows where the assistant may produce visible progress updates before it finishes. When you send that history back on follow-up requests for `gpt-5.3-codex` and later models, **preserve and resend `phase`** on assistant messages so the model can distinguish progress updates from the final result. This helps reduce early stopping, making the agent more likely to continue until it reaches the final answer. ## Use `tool_search` Instead of loading the full tool catalog into every request, use [tool search](https://developers.openai.com/api/docs/guides/tools-tool-search): add `{"type": "tool_search"}` and mark expensive tool definitions with `defer_loading: true`. The model can then load the subset it needs at runtime. At request start, the model only sees the search tool name and description. If the model decides it needs a deferred tool, it runs tool search, and only then are the deferred tool definitions loaded into context. Only then will the model call them. This saves tokens and preserves cache performance. There are two modes: - **Hosted tool search** is the simpler option. Use it when you already know which tools could be available for the request. - **Client-executed tool search** is for cases where your app has to decide what tools are available, like based on the user's tenant, project, permissions, or internal registry. **Start with hosted tool search** unless your app really needs to control discovery itself. Group your tools by user intent. Use namespaces or MCP servers when you can. It is easier for the model to choose between a few clear groups than a long flat list of functions. We recommend keeping each namespace under about 10 functions for optimal token efficiency and model performance. Keep namespace descriptions short and discriminative. Put the detailed instructions inside the deferred tool definitions. Avoid making one giant namespace for everything. Use hosted tool search with deferred tools ```javascript import OpenAI from "openai"; const openai = new OpenAI(); /** @type {OpenAI.Responses.Tool} */ const billingNamespace = { type: "namespace", name: "billing", description: "Billing tools for invoices, payments, taxes, and credits.", tools: [ { type: "function", name: "lookup_invoice", description: "Look up invoice state, taxes, credits, and payment attempts.", parameters: { type: "object", properties: { invoice_id: { type: "string" }, }, required: ["invoice_id"], additionalProperties: false, }, strict: true, defer_loading: true, }, ], }; /** @type {OpenAI.Responses.Tool} */ const crmNamespace = { type: "namespace", name: "crm", description: "CRM tools for account ownership, plans, health, and payment history.", tools: [ { type: "function", name: "get_account", description: "Fetch account owner, plan, health, and payment history.", parameters: { type: "object", properties: { account_id: { type: "string" }, }, required: ["account_id"], additionalProperties: false, }, strict: true, defer_loading: true, }, ], }; const response = await openai.responses.create({ model: "gpt-5.6", input: "Find the right billing tool and explain why invoice INV-1043 still " + "shows overdue after a payment yesterday.", tools: [billingNamespace, crmNamespace, { type: "tool_search" }], }); console.log(response.output); ``` ```python from openai import OpenAI client = OpenAI() billing_namespace = { "type": "namespace", "name": "billing", "description": "Billing tools for invoices, payments, taxes, and credits.", "tools": [ { "type": "function", "name": "lookup_invoice", "description": "Look up invoice state, taxes, credits, and payment attempts.", "parameters": { "type": "object", "properties": { "invoice_id": {"type": "string"}, }, "required": ["invoice_id"], "additionalProperties": False, }, "strict": True, "defer_loading": True, } ], } crm_namespace = { "type": "namespace", "name": "crm", "description": "CRM tools for account ownership, plans, health, and payment history.", "tools": [ { "type": "function", "name": "get_account", "description": "Fetch account owner, plan, health, and payment history.", "parameters": { "type": "object", "properties": { "account_id": {"type": "string"}, }, "required": ["account_id"], "additionalProperties": False, }, "strict": True, "defer_loading": True, } ], } response = client.responses.create( model="gpt-5.6", input=( "Find the right billing tool and explain why invoice INV-1043 still " "shows overdue after a payment yesterday." ), tools=[billing_namespace, crm_namespace, {"type": "tool_search"}], ) print(response.output) ``` ```go package main import ( "context" "fmt" "github.com/openai/openai-go/v3" "github.com/openai/openai-go/v3/responses" ) func main() { client := openai.NewClient() billing := namespaceTool( "billing", "Billing tools for invoices, payments, taxes, and credits.", "lookup_invoice", "Look up invoice state, taxes, credits, and payment attempts.", "invoice_id", ) crm := namespaceTool( "crm", "CRM tools for account ownership, plans, health, and payment history.", "get_account", "Fetch account owner, plan, health, and payment history.", "account_id", ) toolSearch := responses.ToolUnionParam{OfToolSearch: &responses.ToolSearchToolParam{}} response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{ Model: "gpt-5.6", Input: responses.ResponseNewParamsInputUnion{OfString: openai.String( "Find the right billing tool and explain why invoice INV-1043 still shows overdue after a payment yesterday.", )}, Tools: []responses.ToolUnionParam{billing, crm, toolSearch}, }) if err != nil { panic(err) } fmt.Println(response.Output) } func namespaceTool(namespace, namespaceDescription, name, description, argument string) responses.ToolUnionParam { parameters := map[string]any{ "type": "object", "properties": map[string]any{ argument: map[string]any{"type": "string"}, }, "required": []string{argument}, "additionalProperties": false, } function := responses.NamespaceToolToolFunctionParam{ Name: name, Description: openai.String(description), Parameters: parameters, Strict: openai.Bool(true), DeferLoading: openai.Bool(true), } return responses.ToolParamOfNamespace( namespaceDescription, namespace, []responses.NamespaceToolToolUnionParam{{OfFunction: &function}}, ) } ``` ## Use Programmatic Tool Calling [Programmatic Tool Calling](https://developers.openai.com/api/docs/guides/tools-programmatic-tool-calling) lets GPT-5.6 write JavaScript that calls eligible tools and reduces their intermediate results inside a hosted runtime. Use it for bounded stages where code can filter, join, rank, remove duplicates, combine, or check large tool results before returning a smaller structured result to the model. Add the `programmatic_tool_calling` tool and opt in each eligible tool. Use `allowed_callers: ["programmatic"]` for program-only tools, or use `allowed_callers: ["direct", "programmatic"]` when the model may also call the tool directly. Keep calls direct when each result may change the model's next decision, an action requires approval, or the final answer must preserve citations or native artifacts. Document tool return fields and error behavior so the model can write a correct program without first inspecting a result. Your tool loop must handle `program` and `program_output` items, as well as program-issued `function_call` items and their `function_call_output` items. Preserve each `call_id`, and copy the function call's `caller` into its output so the service can resume the correct program. Test both the `program_output` and the final assistant message. A correct program result can still become an incomplete final answer. Compare task success, required evidence, total tokens, latency, and cost against the same workflow using direct tool calls. ## Use Multi-agent for parallel work [Multi-agent](https://developers.openai.com/api/docs/guides/responses-multi-agent) is a GPT-5.6 feature that lets a root agent delegate independent workstreams to subagents and synthesize their results. Use it when you can split research, analysis, or implementation into concrete, bounded tasks that use separate context and run in parallel. Set `multi_agent.enabled` to `true` in the request. For HTTP, use the beta Responses SDK with `client.beta.responses` and pass `responses_multi_agent=v1` in `betas`. For raw HTTP or WebSocket connections, send `OpenAI-Beta: responses_multi_agent=v1`. Item schemas can change while Multi-agent is in beta. Prefer one agent for short tasks, ordered chains where each step depends on the last, or work that writes to the same mutable resource. Subagents can increase token usage, so start with the default `max_concurrent_subagents` value of `3` and measure end-to-end quality, latency, and cost. For tool-heavy or long-running Multi-agent workflows, WebSocket mode can reduce continuation overhead. Before enabling Multi-agent, account for its current limitations: `/responses/compact`, `reasoning.summary`, and `max_tool_calls` are not supported. The server automatically compacts the root context and every subagent context. ## Leverage built-in tools [Built-in tools](https://developers.openai.com/api/docs/guides/tools) are the API's native capabilities. Instead of building every tool yourself, you can give the model access to tools that already work inside the Responses API. The model can then decide when to use them. OpenAI keeps adding more native tools, so start with built-in tools when they fit your workflow. Build custom tools when native options do not cover the task. Current built-in tools and related tool options include: - **Web search**: Search the web for up-to-date information - **File search**: Search uploaded files or vector stores - **Code interpreter**: Run Python for analysis, math, charts, and file processing - **Shell**: Run shell commands in a hosted container or your own runtime - **Computer use**: Operate a UI through screenshots, clicks, typing, and scrolling - **Image generation**: Generate or edit images - **MCP/connectors**: Connect the model to external services and tools - **Skills**: Attach reusable instruction bundles and workflow files - **Apply patch**: Make structured code edits There is also a model-quality reason to prefer them. Built-in tools are in-distribution for our post-training, meaning that the models are trained and evaluated around these tool shapes, behaviors, and outputs. With built-in tools, OpenAI models support better tool selection, cleaner execution, and fewer failures than with new tools. ## Leverage compaction [Compaction](https://developers.openai.com/api/docs/guides/compaction) is a context engineering tool: it decides what information the model carries forward across many turns. In long-running agents, the problem is not just, "Will I hit the context limit?" It is that old messages, tool logs, retries, and stale details crowd out the state the model needs. Compaction gives you a controlled way to reduce context size while preserving state needed for subsequent turns. After a meaningful milestone, like finishing a debugging phase or narrowing a root cause, you can compact the prior window and continue from the compacted output. This keeps the model sharp because the next turn is built around the important state, not every intermediate reasoning, failed command, and obsolete branch of reasoning. There are two ways to leverage compaction: - **Let the server handle it**: if you use `previous_response_id`, turn on `context_management` with a `compact_threshold`. The server will automatically compact the conversation when it gets too large. You keep sending only the newest user message. - **Do it yourself**: if you manage the full input array yourself, call `client.responses.compact()`. It gives back a smaller context window. Use that returned output directly in the next `responses.create()` call. **Do not edit the compacted output.** It is not a human summary, but the machine state that helps the model continue. Pass it forward as-is, then add the next user message. Continue from compacted response state ```javascript import OpenAI from "openai"; const openai = new OpenAI(); // Full window collected from a long debugging session: // user messages, assistant outputs, tool calls, and tool outputs. const longWindow = sessionItems; const compacted = await openai.responses.compact({ model: "gpt-5.6", input: longWindow, }); const nextResponse = await openai.responses.create({ model: "gpt-5.6", store: false, input: [ ...compacted.output, // Use compact output as-is. { type: "message", role: "user", content: "We found the bad cache invalidation path. Write the fix plan " + "and the verification checklist.", }, ], }); console.log(nextResponse.output_text); ``` ```python from openai import OpenAI client = OpenAI() # Full window collected from a long debugging session: # user messages, assistant outputs, tool calls, and tool outputs. long_window = session_items compacted = client.responses.compact( model="gpt-5.6", input=long_window, ) next_response = client.responses.create( model="gpt-5.6", store=False, input=[ *compacted.output, # Use compact output as-is. { "type": "message", "role": "user", "content": ( "We found the bad cache invalidation path. Write the fix plan " "and the verification checklist." ), }, ], ) print(next_response.output_text) ``` ```go package main import ( "context" "encoding/json" "fmt" "github.com/openai/openai-go/v3" "github.com/openai/openai-go/v3/responses" ) func main() { client := openai.NewClient() longWindow := []responses.ResponseInputItemUnionParam{ responses.ResponseInputItemParamOfMessage("Find the cache invalidation bug in this debugging session.", responses.EasyInputMessageRoleUser), } compacted, err := client.Responses.Compact(context.Background(), responses.ResponseCompactParams{ Model: "gpt-5.6", Input: responses.ResponseCompactParamsInputUnion{OfResponseInputItemArray: longWindow}, }) if err != nil { panic(err) } input := append(outputAsInput(compacted.Output), responses.ResponseInputItemParamOfMessage( "We found the bad cache invalidation path. Write the fix plan and the verification checklist.", responses.EasyInputMessageRoleUser, ), ) nextResponse, err := client.Responses.New(context.Background(), responses.ResponseNewParams{ Model: "gpt-5.6", Store: openai.Bool(false), Input: responses.ResponseNewParamsInputUnion{OfInputItemList: input}, }) if err != nil { panic(err) } fmt.Println(nextResponse.OutputText()) } func outputAsInput(output []responses.ResponseOutputItemUnion) []responses.ResponseInputItemUnionParam { input := make([]responses.ResponseInputItemUnionParam, 0, len(output)) for _, item := range output { var converted responses.ResponseInputItemUnion if err := json.Unmarshal([]byte(item.RawJSON()), &converted); err != nil { panic(err) } input = append(input, converted.ToParam()) } return input } ``` ## Use `prompt_cache_key` [Prompt caching](https://developers.openai.com/api/docs/guides/prompt-caching) automatically reduces latency and cost when requests reuse the same long prefix. For high-volume workflows, set [`prompt_cache_key`](https://developers.openai.com/api/reference/resources/responses/methods/create#responses-create-prompt_cache_key) consistently for requests that share the same stable prefix. The service combines the key with the prompt prefix hash to help route similar requests to the same cache without changing the model input. Keep the key stable for genuinely shared prefixes, choose a granularity that avoids sending too much traffic to one key, and keep total traffic across the prefixes for each key to about 15 requests per minute. Partition higher-volume traffic across more keys with a stable mapping. GPT-5.6 introduced explicit prompt caching. Implicit caching remains the default, but GPT-5.6 models and later model families also support explicit cache breakpoints and request-wide cache policy. On those models, set `prompt_cache_key` to use the more reliable matching for both implicit caching and explicit breakpoints. If a changing suffix comes after a stable prefix, add an explicit `prompt_cache_breakpoint` at the reusable boundary. Set `prompt_cache_options.mode` to `explicit` only when the request should use only the breakpoints you provide and no implicit breakpoint. Earlier models continue to use automatic prompt caching only. On GPT-5.6 models and later model families, cache writes cost 1.25× the uncached input token rate. Log `cached_tokens` and `cache_write_tokens`, then compare write volume with later cache reads to measure net cost and tune key granularity and breakpoint placement. Route related requests to the same prompt cache ```javascript import OpenAI from "openai"; const openai = new OpenAI(); const instructions = [ "You are the support agent for Acme.", "Follow the Acme support policy and escalation rubric.", "Use the same tone, safety rules, and tool plan for each ticket.", ].join("\n"); const response = await openai.responses.create({ model: "gpt-5.6", prompt_cache_key: "tenant-acme-support-agent", instructions, input: "Summarize the current escalation for the on-call lead.", }); console.log(response.output_text); ``` ```python from openai import OpenAI client = OpenAI() instructions = """ You are the support agent for Acme. Follow the Acme support policy and escalation rubric. Use the same tone, safety rules, and tool plan for each ticket. """ response = client.responses.create( model="gpt-5.6", prompt_cache_key="tenant-acme-support-agent", instructions=instructions, input="Summarize the current escalation for the on-call lead.", ) print(response.output_text) ``` ```go package main import ( "context" "fmt" "strings" "github.com/openai/openai-go/v3" "github.com/openai/openai-go/v3/responses" ) func main() { client := openai.NewClient() instructions := strings.Join([]string{ "You are the support agent for Acme.", "Follow the Acme support policy and escalation rubric.", "Use the same tone, safety rules, and tool plan for each ticket.", }, "\n") response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{ Model: "gpt-5.6", PromptCacheKey: openai.String("tenant-acme-support-agent"), Instructions: openai.String(instructions), Input: responses.ResponseNewParamsInputUnion{OfString: openai.String("Summarize the current escalation for the on-call lead.")}, }) if err != nil { panic(err) } fmt.Println(response.OutputText()) } ``` ## Use `reasoning.encrypted_content` GPT-5.6 can [preserve reasoning across calls](https://developers.openai.com/api/docs/guides/reasoning#preserve-reasoning-across-calls). Use `reasoning.context: "all_turns"` when the task's goals, assumptions, and priorities remain stable. Use `current_turn` when earlier reasoning is no longer relevant and might anchor the model to an outdated approach. If you omit `reasoning.context` or set it to `auto`, inspect the response's `reasoning.context` field to confirm the effective mode. [Persisted reasoning](https://developers.openai.com/api/docs/guides/reasoning#keeping-reasoning-items-in-context) works only when earlier reasoning items are available. Use `previous_response_id` for stored responses. If your [Zero Data Retention (ZDR)](https://developers.openai.com/api/docs/guides/your-data#zero-data-retention) requirements do not allow storing response data, encrypted reasoning content enables a stateless handoff. Reasoning items in the response output include encrypted reasoning content by default. You can access the encrypted reasoning content from each reasoning item's `encrypted_content` property. Your app does not need to understand that value. It just keeps each reasoning item exactly as returned and sends it back during the next turn, so the model can use it to continue the workflow. Pass encrypted reasoning between stateless turns ```javascript import OpenAI from "openai"; const openai = new OpenAI(); /** @type {OpenAI.Responses.ResponseInput} */ const history = [ { role: "user", content: "Investigate why invoice INV-1043 has mismatched tax totals.", }, ]; const first = await openai.responses.create({ model: "gpt-5.6", store: false, reasoning: { effort: "medium", context: "current_turn" }, input: history, }); history.push(...first.output); history.push({ role: "user", content: "Now write the customer-facing explanation in plain English.", }); const second = await openai.responses.create({ model: "gpt-5.6", store: false, reasoning: { effort: "medium", context: "all_turns" }, input: history, }); console.log(second.output_text); ``` ```python from openai import OpenAI client = OpenAI() history = [ { "role": "user", "content": "Investigate why invoice INV-1043 has mismatched tax totals.", } ] first = client.responses.create( model="gpt-5.6", store=False, reasoning={"effort": "medium", "context": "current_turn"}, input=history, ) history.extend(item.model_dump(exclude={"status"}) for item in first.output) history.append( { "role": "user", "content": "Now write the customer-facing explanation in plain English.", } ) second = client.responses.create( model="gpt-5.6", store=False, reasoning={"effort": "medium", "context": "all_turns"}, input=history, ) print(second.output_text) ``` ```go package main import ( "context" "encoding/json" "fmt" "github.com/openai/openai-go/v3" "github.com/openai/openai-go/v3/responses" "github.com/openai/openai-go/v3/shared" ) func main() { client := openai.NewClient() history := []responses.ResponseInputItemUnionParam{ responses.ResponseInputItemParamOfMessage("Investigate why invoice INV-1043 has mismatched tax totals.", responses.EasyInputMessageRoleUser), } first, err := client.Responses.New(context.Background(), responses.ResponseNewParams{ Model: "gpt-5.6", Store: openai.Bool(false), Reasoning: shared.ReasoningParam{Effort: shared.ReasoningEffortMedium, Context: shared.ReasoningContextCurrentTurn}, Include: []responses.ResponseIncludable{responses.ResponseIncludableReasoningEncryptedContent}, Input: responses.ResponseNewParamsInputUnion{OfInputItemList: history}, }) if err != nil { panic(err) } history = append(history, outputAsInput(first.Output)...) history = append(history, responses.ResponseInputItemParamOfMessage( "Now write the customer-facing explanation in plain English.", responses.EasyInputMessageRoleUser, )) second, err := client.Responses.New(context.Background(), responses.ResponseNewParams{ Model: "gpt-5.6", Store: openai.Bool(false), Reasoning: shared.ReasoningParam{Effort: shared.ReasoningEffortMedium, Context: shared.ReasoningContextAllTurns}, Input: responses.ResponseNewParamsInputUnion{OfInputItemList: history}, }) if err != nil { panic(err) } fmt.Println(second.OutputText()) } func outputAsInput(output []responses.ResponseOutputItemUnion) []responses.ResponseInputItemUnionParam { input := make([]responses.ResponseInputItemUnionParam, 0, len(output)) for _, item := range output { var converted responses.ResponseInputItemUnion if err := json.Unmarshal([]byte(item.RawJSON()), &converted); err != nil { panic(err) } input = append(input, converted.ToParam()) } return input } ``` ## Set image detail intentionally On GPT-5.6 models, omitted image `detail` and `detail: "auto"` use the same sizing behavior as `original`. The service preserves the input dimensions instead of resizing the image to a patch budget or pixel-dimension limit. Large images can use more input tokens and add latency as a result. Choose [`detail`](https://developers.openai.com/api/docs/guides/images-vision#choose-an-image-detail-level) for the task. Resize the image, use `low` when fine visual detail is not important, or use `high` for standard high-fidelity image understanding. Keep `original` for large, dense, coordinate-sensitive, OCR, localization, or visual-inspection tasks where the extra detail improves quality. Measure worst-case image tokens and latency before deployment. ## Send a safety identifier If your application serves individual end users, send a stable, privacy-preserving [`safety_identifier`](https://developers.openai.com/api/docs/guides/safety-best-practices#implement-safety-identifiers) with each request. It helps OpenAI detect misuse and gives your team a stable way to trace policy violations. It also reduces the chance that one user's misuse disrupts access for your broader organization. Hash the user's username or email address instead of sending identifying information. For logged-out experiences, use a stable session ID. ## Use `background=True` Use [`background=True`](https://developers.openai.com/api/docs/guides/background) for requests that may take a long time. Instead of keeping the client connection open, the API starts a job and returns an ID. Your app can poll that job until it finishes, fails, or is canceled. Use it for large analyses, long tool runs, or work that needs status and retry behavior. Run and poll a background response ```javascript import OpenAI from "openai"; const openai = new OpenAI(); let job = await openai.responses.create({ model: "gpt-5.6", background: true, store: false, input: "Analyze this large log bundle and cluster the primary failure modes.", tools: [ { type: "code_interpreter", container: { type: "auto", file_ids: [logBundleFileId], }, }, ], }); while (["queued", "in_progress"].includes(job.status)) { await new Promise((resolve) => setTimeout(resolve, 2000)); job = await openai.responses.retrieve(job.id); } console.log(job.output_text); ``` ```python from openai import OpenAI import time client = OpenAI() job = client.responses.create( model="gpt-5.6", background=True, store=False, input="Analyze this large log bundle and cluster the primary failure modes.", tools=[ { "type": "code_interpreter", "container": { "type": "auto", "file_ids": [log_bundle_file_id], }, } ], ) while job.status in {"queued", "in_progress"}: time.sleep(2) job = client.responses.retrieve(job.id) print(job.output_text) ``` ```go package main import ( "context" "fmt" "time" "github.com/openai/openai-go/v3" "github.com/openai/openai-go/v3/responses" ) func main() { client := openai.NewClient() tool := responses.ToolParamOfCodeInterpreter(responses.ToolCodeInterpreterContainerCodeInterpreterContainerAutoParam{ FileIDs: []string{"file_abc123"}, }) job, err := client.Responses.New(context.Background(), responses.ResponseNewParams{ Model: "gpt-5.6", Background: openai.Bool(true), Store: openai.Bool(false), Input: responses.ResponseNewParamsInputUnion{OfString: openai.String("Analyze this large log bundle and cluster the primary failure modes.")}, Tools: []responses.ToolUnionParam{tool}, }) if err != nil { panic(err) } for job.Status == responses.ResponseStatusQueued || job.Status == responses.ResponseStatusInProgress { time.Sleep(2 * time.Second) job, err = client.Responses.Get(context.Background(), job.ID, responses.ResponseGetParams{}) if err != nil { panic(err) } } fmt.Println(job.OutputText()) } ``` You can combine it with `stream=True` for progress events, but the first event may take longer than a normal request. From the UI perspective, background mode indicates, "This is running; here is the status; the result will appear here when it's ready." ## Use WebSocket mode [WebSocket mode](https://developers.openai.com/api/docs/guides/websocket-mode) is built for long-running, tool-call-heavy workflows where you keep a persistent connection open and continue by sending only new input items plus `previous_response_id`. For rollouts with 20 or more tool calls, this approach is roughly 40% faster end-to-end. **How this works**: The first message will look like a normal Responses request: model, instructions, tools, and user input. The server streams events back. If the model asks for a tool, your app runs the tool. Then, instead of sending a new HTTP request, you send another `response.create` event on the same socket with the prior `previous_response_id` and the new item. That is where the latency win comes from. In plain HTTP, every follow-up is a fresh request. In WebSocket mode, the connection stays open and the most recent response state stays warm in memory on that connection. When the next turn continues from that response, the backend has to do less setup work. If your workflow is one request, one answer, then **keep HTTP**. If your workflow behaves like a long-running agent, try WebSocket mode. A single WebSocket connection handles one in-flight response at a time, so parallel work needs multiple connections. Connections currently top out at 60 minutes. Continuation uses the same `previous_response_id` semantics as HTTP mode, with a connection-local cache for the most recent response. Note: WebSocket mode works with ZDR because your data is not stored to disk, only stored in memory. The default Python sample uses `websocket-client` (`pip install websocket-client`). The JavaScript sample uses `ws` (`npm install ws`). Start a Responses API WebSocket session ```javascript import OpenAI from "openai"; import WebSocket from "ws"; const openai = new OpenAI(); const ws = new WebSocket("wss://api.openai.com/v1/responses", { headers: { Authorization: "Bearer " + openai.apiKey, }, }); ws.on("open", () => { ws.send( JSON.stringify({ type: "response.create", model: "gpt-5.6", store: false, input: [ { type: "message", role: "user", content: [ { type: "input_text", text: "Find the flaky test in this run, call the tools you need, " + "and keep going until you can explain the root cause.", }, ], }, ], tools: [testLogTool, codeSearchTool], }) ); }); ws.on("message", (data) => { const firstEvent = JSON.parse(data.toString()); console.log(firstEvent.type); }); ``` ```python from openai import OpenAI from websocket import create_connection import json client = OpenAI() ws = create_connection( "wss://api.openai.com/v1/responses", header=[f"Authorization: Bearer {client.api_key}"], ) # Same request body you would send to client.responses.create(...). ws.send( json.dumps( { "type": "response.create", "model": "gpt-5.6", "store": False, "input": [ { "type": "message", "role": "user", "content": [ { "type": "input_text", "text": ( "Find the flaky test in this run, call the tools " "you need, and keep going until you can explain " "the root cause." ), } ], } ], "tools": [test_log_tool, code_search_tool], } ) ) first_event = json.loads(ws.recv()) print(first_event["type"]) ``` ## Final takeaway Responses API is the foundation for building smarter, more capable OpenAI applications. The real advantage is that it lets developers move from one-off prompts to durable, tool-using, context-aware workflows that can adapt to the complexity of the task. Follow this guide to see higher performance in real deployments. --- # Apply Patch The `apply_patch` tool lets GPT-5.1 create, update, and delete files in your codebase using structured diffs. Instead of just suggesting edits, the model emits patch operations that your application applies and then reports back on, enabling iterative, multi-step code editing workflows. ## When to use Some common scenarios where you would use apply_patch: - **Multi-file refactors** – Rename symbols, extract helpers, or reorganize modules across many files at once. - **Bug fixes** – Have the model both diagnose issues and emit precise patches. - **Tests & docs generation** – Create new test files, fixtures, and documentation alongside code changes. - **Migrations & mechanical edits** – Apply repetitive, structured updates (API migrations, type annotations, formatting fixes, etc.). If you can describe your repo and desired change in text, apply_patch can usually generate the corresponding diffs. ## Use apply patch tool with Responses API At a high level, using `apply_patch` with the Responses API looks like this: 1. **Call the Responses API with the `apply_patch` tool** - Provide the model with context about available files (or a summary) in your `input`, or give the model tools for exploring your file system. - Enable the tool with `tools=[{"type": "apply_patch"}]`. 2. **Let the model return one or more patch operations** - The Response output includes one or more `apply_patch_call` objects. - Each call describes a single file operation: create, update, or delete. 3. **Apply patches in your environment** - Run a patch harness or script that: - Interprets the `operation` diff for each `apply_patch_call`. - Applies the patch to your working directory or repo. - Records whether each patch succeeded and any logs or error messages. 4. **Report patch results back to the model** - Call the Responses API again, either with `previous_response_id` or by passing back your conversation items into `input`. - Include an `apply_patch_call_output` event for each `call_id`, with a `status` and optional `output` string. - Keep `tools=[{"type": "apply_patch"}]` so the model can continue editing if needed. 5. **Let the model continue or explain changes** - The model may issue more `apply_patch_call` operations, or - Provide a human-facing explanation of what it changed and why. ## Example: Renaming a function with Apply Patch Tool **Step 1: Ask the model to plan and emit patches** Ask the model to plan and emit patches ```python from openai import OpenAI client = OpenAI() # For brevity, we are including file context in the example input. # Most agentic use cases should instead equip the model with tools # for exploring file system state. RESPONSE_INPUT = """ The user has the following files: ===== lib/fib.py def fib(n): if n <= 1: return n return fib(n-1) + fib(n-2) ===== run.py from lib.fib import fib def main(): print(fib(42)) You are a helpful coding assistant that should assist the user with whatever they ask. User query: Help me rename the fib() function to fibonacci() """ response = client.responses.create( model="gpt-5.6", input=RESPONSE_INPUT, tools=[{"type": "apply_patch"}], ) # response.output may contain multiple apply_patch_call entries, e.g.: # - update lib/fib.py # - update run.py patch_calls = [ item.model_dump() for item in response.output if item.type == "apply_patch_call" ] ``` ```go response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{ Model: "gpt-5.6", Input: responses.ResponseNewParamsInputUnion{OfString: openai.String(responseInput)}, Tools: []responses.ToolUnionParam{{OfApplyPatch: &responses.ApplyPatchToolParam{}}}, }) if err != nil { panic(err) } patchCalls := make([]responses.ResponseOutputItemUnion, 0) for _, item := range response.Output { if item.Type == "apply_patch_call" { patchCalls = append(patchCalls, item) } } ``` **Example `apply_patch_call` object** Example apply_patch_call object ```json { "id": "apc_08f3d96c87a585390069118b594f7481a088b16cda7d9415fe", "type": "apply_patch_call", "status": "completed", "call_id": "call_Rjsqzz96C5xzPb0jUWJFRTNW", "operation": { "type": "update_file", "diff": " @@ -def fib(n): +def fibonacci(n): if n <= 1: return n - return fib(n-1) + fib(n-2) + return fibonacci(n-1) + fibonacci(n-2), ", "path": "lib/fib.py" } } ``` **Step 2: Apply the patch and send results back** Apply the patch and return results ```python from apply_patch_harness import apply_operation # your implementation results = [] for call in patch_calls: op = call["operation"] success, maybe_log_output = apply_operation(op) results.append( { "type": "apply_patch_call_output", "call_id": call["call_id"], "status": "completed" if success else "failed", "output": maybe_log_output, } ) followup = client.responses.create( model="gpt-5.6", previous_response_id=response.id, input=results, tools=[{"type": "apply_patch"}], ) ``` ```go results := make(responses.ResponseInputParam, 0, len(patchCalls)) for _, call := range patchCalls { success, logOutput := applyOperation(call.Operation) status := "completed" if !success { status = "failed" } result := responses.ResponseInputItemParamOfApplyPatchCallOutput(call.CallID, status) result.OfApplyPatchCallOutput.Output = openai.String(logOutput) results = append(results, result) } _, err = client.Responses.New(context.Background(), responses.ResponseNewParams{ Model: "gpt-5.6", PreviousResponseID: openai.String(response.ID), Input: responses.ResponseNewParamsInputUnion{OfInputItemList: results}, Tools: []responses.ToolUnionParam{{OfApplyPatch: &responses.ApplyPatchToolParam{}}}, }) if err != nil { panic(err) } ``` If a patch fails (for example, file not found), set `status: "failed"` and include a helpful `output` string so the model can recover: Report a failed apply_patch call ```json { "type": "apply_patch_call_output", "call_id": "call_cNWm41dB3RyQcLNOVTIPBWZU", "status": "failed", "output": "Could not apply patch to lib/foo.py — file not found on disk" } ``` ## Apply patch operations | Operation Type | Purpose | Payload | | -------------- | ---------------------------------- | ---------------------------------------------------------------- | | `create_file` | Create a new file at `path`. | `diff` is a V4A diff representing the full file contents. | | `update_file` | Modify an existing file at `path`. | `diff` is a V4A diff with additions, deletions, or replacements. | | `delete_file` | Remove a file at `path`. | No `diff`; delete the file entirely. | Your patch harness is responsible for interpreting the V4A diff format and applying changes. For reference implementations, see the [Python Agents SDK](https://github.com/openai/openai-agents-python/blob/main/src/agents/apply_diff.py) or [TypeScript Agents SDK](https://github.com/openai/openai-agents-js/blob/main/packages/agents-core/src/utils/applyDiff.ts) code. ## Implementing the patch harness When using the `apply_patch` tool, you don’t provide an input schema; the model knows how to construct `operation` objects. Your job is to: 1. **Parse operations from the Response** - Scan the Response for items with `type: "apply_patch_call"`. - For each call, inspect `operation.type`, `operation.path`, and any potential `diff`. 2. **Apply file operations** - For `create_file` and `update_file`, apply the V4A diff to the file system or in-memory workspace. - For `delete_file`, remove the file at `path`. - Record whether each operation succeeded and any logs or error messages. 3. **Return `apply_patch_call_output` events** - For each `call_id`, emit exactly one `apply_patch_call_output` event with: - `status: "completed"` if the operation was applied successfully. - `status: "failed"` if you encountered an error (include a short human-readable `output` string). ### Safety and robustness - **Path validation**: Prevent directory traversal and restrict edits to allowed directories. - **Backups**: Consider backing up files (or working in a scratch copy) before applying patches. - **Error handling**: Always return a `failed` status with an informative `output` string when patches cannot be applied. - **Atomicity**: Decide whether you want “all-or-nothing” semantics (rollback if any patch fails) or per-file success/failure. ## Use the apply patch tool with the Agents SDK Alternatively, you can use the [Agents SDK](https://developers.openai.com/api/docs/guides/tools#usage-in-the-agents-sdk) to use the apply patch tool. You'll still have to implement the harness that handles the actual file operations but you can use the `applyDiff` function to handle the diff processing. Use the apply patch tool with the Agents SDK ```typescript import { applyDiff, Agent, run, applyPatchTool, type ApplyPatchOperation, type ApplyPatchResult, type Editor, } from "@openai/agents"; class WorkspaceEditor implements Editor { async createFile( operation: Extract ): Promise { // convert the diff to the file content const content = applyDiff("", operation.diff, "create"); // write the file content to the file system return { status: "completed", output: `Created ${operation.path}` }; } async updateFile( operation: Extract ): Promise { // read the file content from the file system const current = ""; // convert the diff to the new file content const newContent = applyDiff(current, operation.diff); // write the updated file content to the file system return { status: "completed", output: `Updated ${operation.path}` }; } async deleteFile( operation: Extract ): Promise { // delete the file from the file system return { status: "completed", output: `Deleted ${operation.path}` }; } } const editor = new WorkspaceEditor(); const agent = new Agent({ name: "Patch Assistant", model: "gpt-5.6", instructions: "You can edit files inside the /tmp directory using the apply_patch tool.", tools: [ applyPatchTool({ editor, // could also be a function for you to determine if approval is needed needsApproval: true, onApproval: async (_ctx, _approvalItem) => { // create your own approval logic return { approve: true }; }, }), ], }); const result = await run( agent, "Create tasks.md with a shopping checklist of 5 entries." ); console.log(`\nFinal response:\n${result.finalOutput}`); ``` ```python from agents import Agent, ApplyPatchTool, Runner, apply_diff class WorkspaceEditor: async def create_file(self, operation): # convert the diff to the file content content = apply_diff("", operation.diff, mode="create") # write the file content to the file system return {"status": "completed", "output": f"Created {operation.path}"} async def update_file(self, operation): # read the file content from the file system current = "" # convert the diff to the new file content new_content = apply_diff(current, operation.diff) # write the updated file content to the file system return {"status": "completed", "output": f"Updated {operation.path}"} async def delete_file(self, operation): # delete the file from the file system return {"status": "completed", "output": f"Deleted {operation.path}"} editor = WorkspaceEditor() agent = Agent( name="Patch Assistant", model="gpt-5.6", instructions="You can edit files inside the /tmp directory using the apply_patch tool.", tools=[ ApplyPatchTool( editor=editor, # could also be a function for you to determine if approval is needed needs_approval=True, # Implement your own approval logic on_approval=lambda _ctx, _approval_item: {"approve": True}, ), ], ) async def main(): result = await Runner.run( agent, input="Create tasks.md with a shopping checklist of 5 entries.", ) print(f"\nFinal response:\n{result.final_output}") if __name__ == "__main__": import asyncio asyncio.run(main()) ``` You can find full working examples on GitHub. [Apply patch tool example - TypeScript Example of how to use the apply patch tool with the Agents SDK in TypeScript](https://github.com/openai/openai-agents-js/blob/main/examples/tools/applyPatch.ts) [Apply patch tool example - Python Example of how to use the apply patch tool with the Agents SDK in Python](https://github.com/openai/openai-agents-python/blob/main/examples/tools/apply_patch.py) ## Handling common errors Use `status: "failed"` plus a clear `output` message to help the model recover. File not found File not found error ```json { "type": "apply_patch_call_output", "call_id": "call_abc", "status": "failed", "output": "Error: File not found at path 'lib/baz.py'" } ``` Patch conflict Patch conflict error ```json { "type": "apply_patch_call_output", "call_id": "call_abc", "status": "failed", "output": "Error: Invalid Context:\n@@ def fib(n):" } ``` The model can then adjust future diffs (for example, by re-reading a file in your prompt or simplifying a change) based on these error messages. ## Best practices - **Give clear file context** - When you call the Responses API, include either an inline snapshot of your files (as in the example), or give the model tools for exploring your filesystem (like the `shell` tool). - **Consider using with the `shell` tool** - When used in conjunction with the `shell` tool, the model can explore file system directories, read files, and grep for keywords, enabling agentic file discovery and editing. - **Encourage small, focused diffs** - In your system instructions, nudge the model toward minimal, targeted edits rather than huge rewrites. - **Make sure changes apply cleanly** - After a series of patches, run your tests or linters and share failures back in the next `input` so the model can fix them. ## Usage notes
API Availability Supported models
[Responses](https://developers.openai.com/api/reference/resources/responses) [Chat Completions](https://developers.openai.com/api/reference/resources/chat) [Assistants](https://developers.openai.com/api/reference/resources/beta/subresources/assistants) [GPT-5.5](https://developers.openai.com/api/docs/models/gpt-5.5) [GPT-5.4](https://developers.openai.com/api/docs/models/gpt-5.4) [GPT-5.2](https://developers.openai.com/api/docs/models/gpt-5.2) [GPT-5.1](https://developers.openai.com/api/docs/models/gpt-5.1)
--- # Audio and speech Audio models can understand spoken input, generate spoken output, or do both in the same interaction. This guide explains the vocabulary used across OpenAI's audio docs. When you're ready to choose an implementation path, start with the [Realtime and audio overview](https://developers.openai.com/api/docs/guides/realtime). ## Audio modalities An audio application combines one or more of these modalities: | Modality | Meaning | Common use cases | | --------------- | -------------------------------------------- | ------------------------------------------------- | | Audio input | The model receives sound from a user or app. | Voice agents, transcription, translation. | | Audio output | The model or API returns spoken audio. | Voice agents, text to speech, spoken responses. | | Text transcript | Speech becomes text. | Captions, call analysis, search, records. | | Text prompt | Text controls what the model says or does. | Speech generation, scripted voice flows, prompts. | ## Common speech tasks **Speech to text** converts speech into text. Use it for captions, notes, transcripts, analytics, search, and accessibility. Transcription can be request-based for files or streaming for live audio. Start with the [Transcription overview](https://developers.openai.com/api/docs/guides/transcription) to choose a workflow and model. **Text to speech** converts text into spoken audio. Use it for narration, assistants, accessibility, and generated voice responses. Speech generation can stream audio back as the model produces it. **Speech to speech** lets a model listen, reason, and speak in one low-latency session. Use it for conversational voice agents when the assistant needs to respond, call tools, or maintain session state. **Speech translation** listens to speech in one language and returns translated speech or transcript output in another language. Use a dedicated realtime translation session when translation should begin continuously as audio arrives. ## Streaming and latency Streaming means the client and service exchange partial input or output while the interaction is still active. Streaming is useful when users expect immediate feedback, such as live captions, calls, voice agents, and translation. Lower latency requires a realtime connection, more careful audio handling, and a session model that can emit partial events. Request-based APIs are simpler for file uploads and non-interactive work, but they don't support the same live interaction patterns. ## Request-based APIs and realtime sessions OpenAI supports two broad audio architectures: | Architecture | Use when | Examples | | --------------------------- | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Request-based audio APIs | You have a file, a text input, or a bounded request. | [File transcription](https://developers.openai.com/api/docs/guides/speech-to-text), [text to speech](https://developers.openai.com/api/docs/guides/text-to-speech). | | Realtime sessions | Audio is live and the app needs low-latency events. | [Voice agents](https://developers.openai.com/api/docs/guides/voice-agents), [translation](https://developers.openai.com/api/docs/guides/realtime-translation), [transcription](https://developers.openai.com/api/docs/guides/realtime-transcription). | | Multimodal Chat Completions | You are extending an existing chat flow with audio. | [Audio input or output](#add-audio-to-your-existing-application). | For build-path guidance, see the [Realtime and audio overview](https://developers.openai.com/api/docs/guides/realtime). ## Add audio to your existing application Models such as [`gpt-realtime-2.1`](https://developers.openai.com/api/docs/models/gpt-realtime-2.1) and [`gpt-audio-1.5`](https://developers.openai.com/api/docs/models/gpt-audio-1.5) are natively multimodal, meaning they can understand and generate audio and text as input and output. For live browser speech-to-speech interactions, start with a realtime session in the TypeScript Agents SDK: Start a realtime voice session ```typescript import { RealtimeAgent, RealtimeSession } from "@openai/agents/realtime"; const agent = new RealtimeAgent({ name: "Assistant", instructions: "You are a helpful voice assistant.", }); const session = new RealtimeSession(agent, { model: "gpt-realtime-2.1", }); await session.connect({ apiKey: "ek_...(ephemeral key from your server)", }); ``` This TypeScript example uses the Agents SDK to connect browser voice agents with WebRTC from the client. For Python voice workflows, use the [Voice agents guide](https://developers.openai.com/api/docs/guides/voice-agents), which covers chained voice pipelines. If you already have a text-based LLM application with the [Chat Completions endpoint](https://developers.openai.com/api/reference/resources/chat), you may want to add audio capabilities. For example, if your chat application supports text input, you can add audio input and output: include `audio` in the `modalities` array and use an audio model, like [`gpt-audio-1.5`](https://developers.openai.com/api/docs/models/gpt-audio-1.5). The [Responses API](https://developers.openai.com/api/reference/resources/responses) docs currently describe text and image inputs with text outputs. For this audio-chat pattern, use Chat Completions with an audio-capable model. Audio output from model Create a human-like audio response to a prompt ```javascript import { writeFileSync } from "node:fs"; import OpenAI from "openai"; const openai = new OpenAI(); // Generate an audio response to the given prompt const response = await openai.chat.completions.create({ model: "gpt-audio-1.5", modalities: ["text", "audio"], audio: { voice: "alloy", format: "wav" }, messages: [ { role: "user", content: "Is a golden retriever a good family dog?", }, ], store: true, }); // Inspect returned data console.log(response.choices[0]); // Write audio data to a file writeFileSync( "dog.wav", Buffer.from(response.choices[0].message.audio.data, "base64"), { encoding: "utf-8" } ); ``` ```python import base64 from openai import OpenAI client = OpenAI() completion = client.chat.completions.create( model="gpt-audio-1.5", modalities=["text", "audio"], audio={"voice": "alloy", "format": "wav"}, messages=[{"role": "user", "content": "Is a golden retriever a good family dog?"}], ) print(completion.choices[0]) wav_bytes = base64.b64decode(completion.choices[0].message.audio.data) with open("dog.wav", "wb") as f: f.write(wav_bytes) ``` ```go package main import ( "context" "encoding/base64" "fmt" "os" "github.com/openai/openai-go/v3" ) func main() { client := openai.NewClient() response, err := client.Chat.Completions.New(context.Background(), openai.ChatCompletionNewParams{ Model: "gpt-audio-1.5", Modalities: []string{"text", "audio"}, Audio: openai.ChatCompletionAudioParam{ Voice: openai.ChatCompletionAudioParamVoiceUnion{OfString: openai.String("alloy")}, Format: openai.ChatCompletionAudioParamFormatWAV, }, Messages: []openai.ChatCompletionMessageParamUnion{openai.UserMessage("Is a golden retriever a good family dog?")}, }) if err != nil { panic(err) } fmt.Println(response.Choices[0]) audio, err := base64.StdEncoding.DecodeString(response.Choices[0].Message.Audio.Data) if err != nil { panic(err) } if err := os.WriteFile("dog.wav", audio, 0o600); err != nil { panic(err) } } ``` ```bash curl "https://api.openai.com/v1/chat/completions" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -d '{ "model": "gpt-audio-1.5", "modalities": ["text", "audio"], "audio": { "voice": "alloy", "format": "wav" }, "messages": [ { "role": "user", "content": "Is a golden retriever a good family dog?" } ] }' ``` Audio input to model Use audio inputs for prompting a model ```javascript import OpenAI from "openai"; const openai = new OpenAI(); // Fetch an audio file and convert it to a base64 string const url = "https://cdn.openai.com/API/docs/audio/alloy.wav"; const audioResponse = await fetch(url); const buffer = await audioResponse.arrayBuffer(); const base64str = Buffer.from(buffer).toString("base64"); const response = await openai.chat.completions.create({ model: "gpt-audio-1.5", modalities: ["text", "audio"], audio: { voice: "alloy", format: "wav" }, messages: [ { role: "user", content: [ { type: "text", text: "What is in this recording?" }, { type: "input_audio", input_audio: { data: base64str, format: "wav" }, }, ], }, ], store: true, }); console.log(response.choices[0]); ``` ```python import base64 import requests from openai import OpenAI client = OpenAI() # Fetch the audio file and convert it to a base64 encoded string url = "https://cdn.openai.com/API/docs/audio/alloy.wav" response = requests.get(url) response.raise_for_status() wav_data = response.content encoded_string = base64.b64encode(wav_data).decode("utf-8") completion = client.chat.completions.create( model="gpt-audio-1.5", modalities=["text", "audio"], audio={"voice": "alloy", "format": "wav"}, messages=[ { "role": "user", "content": [ {"type": "text", "text": "What is in this recording?"}, { "type": "input_audio", "input_audio": {"data": encoded_string, "format": "wav"}, }, ], }, ], ) print(completion.choices[0].message) ``` ```go package main import ( "context" "encoding/base64" "fmt" "os" "github.com/openai/openai-go/v3" ) func main() { audio, err := os.ReadFile("fixtures/audio.wav") if err != nil { panic(err) } client := openai.NewClient() response, err := client.Chat.Completions.New(context.Background(), openai.ChatCompletionNewParams{ Model: "gpt-audio-1.5", Modalities: []string{"text", "audio"}, Audio: openai.ChatCompletionAudioParam{ Voice: openai.ChatCompletionAudioParamVoiceUnion{OfString: openai.String("alloy")}, Format: openai.ChatCompletionAudioParamFormatWAV, }, Messages: []openai.ChatCompletionMessageParamUnion{openai.UserMessage([]openai.ChatCompletionContentPartUnionParam{ openai.TextContentPart("What is in this recording?"), openai.InputAudioContentPart(openai.ChatCompletionContentPartInputAudioInputAudioParam{ Data: base64.StdEncoding.EncodeToString(audio), Format: "wav", }), })}, }) if err != nil { panic(err) } fmt.Println(response.Choices[0]) } ``` ```bash curl "https://api.openai.com/v1/chat/completions" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -d '{ "model": "gpt-audio-1.5", "modalities": ["text", "audio"], "audio": { "voice": "alloy", "format": "wav" }, "messages": [ { "role": "user", "content": [ { "type": "text", "text": "What is in this recording?" }, { "type": "input_audio", "input_audio": { "data": "", "format": "wav" } } ] } ] }' ``` --- # Background mode Agents like [Codex](https://openai.com/index/introducing-codex/) and [Deep Research](https://openai.com/index/introducing-deep-research/) show that reasoning models can take several minutes to solve complex problems. Background mode enables you to execute long-running tasks on models like GPT-5.2 and GPT-5.2 Pro reliably, without having to worry about timeouts or other connectivity issues. Background mode kicks off these tasks asynchronously, and developers can poll response objects to check status over time. To start response generation in the background, make an API request with `background` set to `true`: Background requests from Zero Data Retention (ZDR) projects run with `store=false`. Response data is temporarily stored to disk for roughly 10 minutes to enable asynchronous execution and polling. Generate a response in the background ```bash curl https://api.openai.com/v1/responses \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -d '{ "model": "gpt-5.6", "input": "Write a very long novel about otters in space.", "background": true }' ``` ```javascript import OpenAI from "openai"; const client = new OpenAI(); const resp = await client.responses.create({ model: "gpt-5.6", input: "Write a very long novel about otters in space.", background: true, }); console.log(resp.status); ``` ```python from openai import OpenAI client = OpenAI() resp = client.responses.create( model="gpt-5.6", input="Write a very long novel about otters in space.", background=True, ) print(resp.status) ``` ```go package main import ( "context" "fmt" "github.com/openai/openai-go/v3" "github.com/openai/openai-go/v3/responses" ) func main() { client := openai.NewClient() response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{ Model: "gpt-5.6", Background: openai.Bool(true), Input: responses.ResponseNewParamsInputUnion{ OfString: openai.String("Write a very long novel about otters in space."), }, }) if err != nil { panic(err) } fmt.Println(response.Status) } ``` ## Polling background responses To check the status of background requests, use the GET endpoint for Responses. Keep polling while the request is in the queued or in_progress state. When it leaves these states, it has reached a final (terminal) state. Retrieve a response executing in the background ```bash curl https://api.openai.com/v1/responses/resp_123 \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $OPENAI_API_KEY" ``` ```javascript import OpenAI from "openai"; const client = new OpenAI(); let resp = await client.responses.create({ model: "gpt-5.6", input: "Write a very long novel about otters in space.", background: true, }); while (resp.status === "queued" || resp.status === "in_progress") { console.log("Current status: " + resp.status); await new Promise((resolve) => setTimeout(resolve, 2000)); // wait 2 seconds resp = await client.responses.retrieve(resp.id); } console.log("Final status: " + resp.status + "\nOutput:\n" + resp.output_text); ``` ```python from openai import OpenAI from time import sleep client = OpenAI() resp = client.responses.create( model="gpt-5.6", input="Write a very long novel about otters in space.", background=True, ) while resp.status in {"queued", "in_progress"}: print(f"Current status: {resp.status}") sleep(2) resp = client.responses.retrieve(resp.id) print(f"Final status: {resp.status}\nOutput:\n{resp.output_text}") ``` ```go package main import ( "context" "fmt" "time" "github.com/openai/openai-go/v3" "github.com/openai/openai-go/v3/responses" ) func main() { client := openai.NewClient() response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{ Model: "gpt-5.6", Background: openai.Bool(true), Input: responses.ResponseNewParamsInputUnion{ OfString: openai.String("Write a very long novel about otters in space."), }, }) if err != nil { panic(err) } for response.Status == "queued" || response.Status == "in_progress" { fmt.Println("Current status:", response.Status) time.Sleep(2 * time.Second) response, err = client.Responses.Get(context.Background(), response.ID, responses.ResponseGetParams{}) if err != nil { panic(err) } } fmt.Printf("Final status: %s\nOutput:\n%s\n", response.Status, response.OutputText()) } ``` ## Cancelling a background response You can also cancel an in-flight response like this: Cancel an ongoing response ```bash curl -X POST https://api.openai.com/v1/responses/resp_123/cancel \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $OPENAI_API_KEY" ``` ```javascript import OpenAI from "openai"; const client = new OpenAI(); const resp = await client.responses.cancel("resp_123"); console.log(resp.status); ``` ```python import os from openai import OpenAI response_id = os.environ["OPENAI_RESPONSE_ID"] client = OpenAI() resp = client.responses.cancel(response_id) print(resp.status) ``` ```go package main import ( "context" "fmt" "github.com/openai/openai-go/v3" ) func main() { client := openai.NewClient() canceled, err := client.Responses.Cancel(context.Background(), "resp_123") if err != nil { panic(err) } fmt.Println(canceled.Status) } ``` Cancelling twice is idempotent - subsequent calls simply return the final `Response` object. ## Streaming a background response You can create a background Response and start streaming events from it right away. This may be helpful if you expect the client to drop the stream and want the option of picking it back up later. To do this, create a Response with both `background` and `stream` set to `true`. You will want to keep track of a "cursor" corresponding to the `sequence_number` you receive in each streaming event. Currently, the time to first token you receive from a background response is higher than what you receive from a synchronous one. We are working to reduce this latency gap in the coming weeks. Generate and stream a background response ```bash curl https://api.openai.com/v1/responses \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -d '{ "model": "gpt-5.6", "input": "Write a very long novel about otters in space.", "background": true, "stream": true }' // To resume: curl "https://api.openai.com/v1/responses/resp_123?stream=true&starting_after=42" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $OPENAI_API_KEY" ``` ```javascript import OpenAI from "openai"; const client = new OpenAI(); const stream = await client.responses.create({ model: "gpt-5.6", input: "Write a very long novel about otters in space.", background: true, stream: true, }); let cursor = null; for await (const event of stream) { console.log(event); cursor = event.sequence_number; } // If the connection drops, you can resume streaming from the last cursor (SDK support coming soon): // const resumedStream = await client.responses.stream(resp.id, { starting_after: cursor }); // for await (const event of resumedStream) { ... } ``` ```python from openai import OpenAI client = OpenAI() # Fire off an async response but also start streaming immediately stream = client.responses.create( model="gpt-5.6", input="Write a very long novel about otters in space.", background=True, stream=True, ) cursor = None for event in stream: print(event) cursor = event.sequence_number # If your connection drops, the response continues running and you can reconnect: # SDK support for resuming the stream is coming soon. # for event in client.responses.stream(resp.id, starting_after=cursor): # print(event) ``` ```go package main import ( "context" "fmt" "github.com/openai/openai-go/v3" "github.com/openai/openai-go/v3/responses" ) func main() { client := openai.NewClient() stream := client.Responses.NewStreaming(context.Background(), responses.ResponseNewParams{ Model: "gpt-5.6", Background: openai.Bool(true), Input: responses.ResponseNewParamsInputUnion{ OfString: openai.String("Write a very long novel about otters in space."), }, }) var cursor int64 var responseID string for stream.Next() { event := stream.Current() fmt.Println(event.Type) cursor = event.SequenceNumber if event.Response.ID != "" { responseID = event.Response.ID } } if err := stream.Err(); err != nil { panic(err) } fmt.Printf("response %s last cursor %d\n", responseID, cursor) // If the connection drops, resume streaming from the last cursor: // resumed := client.Responses.GetStreaming( // context.Background(), // responseID, // responses.ResponseGetParams{StartingAfter: openai.Int(cursor)}, // ) // for resumed.Next() { // fmt.Println(resumed.Current().Type) // } } ``` ## Limits 1. Background requests can use `store=false`, but response data is temporarily stored to support asynchronous execution and polling. 2. To cancel a synchronous response, terminate the connection 3. You can only start a new stream from a background response if you created it with `stream=true`. --- # Batch API Learn how to use OpenAI's Batch API to send asynchronous groups of requests with 50% lower costs, a separate pool of significantly higher rate limits, and a clear 24-hour turnaround time. The service is ideal for processing jobs that don't require immediate responses. You can also [explore the API reference directly here](https://developers.openai.com/api/reference/resources/batches). ## Overview While some uses of the OpenAI Platform require you to send synchronous requests, there are many cases where requests do not need an immediate response or [rate limits](https://developers.openai.com/api/docs/guides/rate-limits) prevent you from executing a large number of queries quickly. Batch processing jobs are often helpful in use cases like: 1. Running evaluations 2. Classifying large datasets 3. Embedding content repositories 4. Queuing large offline video-render jobs The Batch API offers a straightforward set of endpoints that allow you to collect a set of requests into a single file, kick off a batch processing job to execute these requests, query for the status of that batch while the underlying requests execute, and eventually retrieve the collected results when the batch is complete. Compared to using standard endpoints directly, Batch API has: 1. **Better cost efficiency:** 50% cost discount compared to synchronous APIs 2. **Higher rate limits:** [Substantially more headroom](https://platform.openai.com/settings/organization/limits) compared to the synchronous APIs 3. **Fast completion times:** Each batch completes within 24 hours (and often more quickly) ## Getting started ### 1. Prepare your batch file Batches start with a `.jsonl` file where each line contains the details of an individual request to the API. For now, the available endpoints are: - `/v1/responses` ([Responses API](https://developers.openai.com/api/reference/resources/responses)) - `/v1/chat/completions` ([Chat Completions API](https://developers.openai.com/api/reference/resources/chat)) - `/v1/embeddings` ([Embeddings API](https://developers.openai.com/api/reference/resources/embeddings)) - `/v1/completions` ([Completions API](https://developers.openai.com/api/reference/resources/completions)) - `/v1/moderations` ([Moderation guide](https://developers.openai.com/api/docs/guides/moderation)) - `/v1/images/generations` ([Images API](https://developers.openai.com/api/reference/resources/images)) - `/v1/images/edits` ([Images API](https://developers.openai.com/api/reference/resources/images)) - `/v1/videos` ([Video generation guide](https://developers.openai.com/api/docs/guides/video-generation)) For a given input file, the parameters in each line's `body` field are the same as the parameters for the underlying endpoint. Each request must include a unique `custom_id` value, which you can use to reference results after completion. Here's an example of an input file with 2 requests. Note that each input file can only include requests to a single model. For video generation in Batch: - Batch currently supports `POST /v1/videos` only. - Batch requests for videos must use JSON, not multipart. - Upload assets ahead of time and pass supported asset references in the request body rather than using multipart uploads. - Use `input_reference` for image-guided generations in Batch. In JSON requests, pass `input_reference` as an object with either `file_id` or `image_url`. - Multipart `input_reference` uploads, including video reference inputs, aren't supported in Batch. - Batch-generated videos are available for download for up to `24` hours after the batch completes. When targeting `/v1/moderations`, include an `input` field in every request body. Batch accepts plain-text inputs and content arrays with text or image inputs using `omni-moderation-latest`. The Batch worker rejects requests that set `stream=true`, matching the synchronous moderation endpoint. ```jsonl {"custom_id": "request-1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-3.5-turbo-0125", "messages": [{"role": "system", "content": "You are a helpful assistant."},{"role": "user", "content": "Hello world!"}],"max_tokens": 1000}} {"custom_id": "request-2", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-3.5-turbo-0125", "messages": [{"role": "system", "content": "You are an unhelpful assistant."},{"role": "user", "content": "Hello world!"}],"max_tokens": 1000}} ``` #### Moderation input examples Text-only request: ```jsonl { "custom_id": "moderation-text-1", "method": "POST", "url": "/v1/moderations", "body": { "model": "omni-moderation-latest", "input": "This is a harmless test sentence." } } ``` Request with text and image input: ```jsonl { "custom_id": "moderation-mm-1", "method": "POST", "url": "/v1/moderations", "body": { "model": "omni-moderation-latest", "input": [ { "type": "text", "text": "Describe this image" }, { "type": "image_url", "image_url": { "url": "https://api.nga.gov/iiif/a2e6da57-3cd1-4235-b20e-95dcaefed6c8/full/!800,800/0/default.jpg" } } ] } } ``` Prefer referencing remote assets with `image_url` (instead of base64 blobs) to keep your `.jsonl` files well below the 200 MB Batch upload limit, especially for multimodal Moderations requests. ### 2. Upload your batch input file Similar to our [Fine-tuning API](https://developers.openai.com/api/docs/guides/model-optimization), you must first upload your input file so that you can reference it correctly when kicking off batches. Upload your `.jsonl` file using the [Files API](https://developers.openai.com/api/reference/resources/files). Upload files for Batch API ```javascript import fs from "fs"; import OpenAI from "openai"; const openai = new OpenAI(); const file = await openai.files.create({ file: fs.createReadStream("fixtures/batchinput.jsonl"), purpose: "batch", }); console.log(file); ``` ```python from openai import OpenAI client = OpenAI() batch_input_file = client.files.create( file=open("batchinput.jsonl", "rb"), purpose="batch" ) print(batch_input_file) ``` ```go package main import ( "context" "fmt" "os" "github.com/openai/openai-go/v3" ) func main() { client := openai.NewClient() file, err := os.Open("batchinput.jsonl") if err != nil { panic(err) } defer file.Close() uploaded, err := client.Files.New(context.Background(), openai.FileNewParams{ File: file, Purpose: openai.FilePurposeBatch, }) if err != nil { panic(err) } fmt.Println(uploaded.ID) } ``` ```bash curl https://api.openai.com/v1/files \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -F purpose="batch" \ -F file="@batchinput.jsonl" ``` ```bash openai files create \ --file batchinput.jsonl \ --purpose batch ``` ### 3. Create the batch Once you've successfully uploaded your input file, you can use the input File object's ID to create a batch. In this case, let's assume the file ID is `file-abc123`. For now, the completion window can only be set to `24h`. You can also provide custom metadata via an optional `metadata` parameter. Create the Batch ```javascript import OpenAI from "openai"; const openai = new OpenAI(); const batch = await openai.batches.create({ input_file_id: "file-abc123", endpoint: "/v1/chat/completions", completion_window: "24h", }); console.log(batch); ``` ```python batch = client.batches.create( input_file_id=batch_input_file.id, endpoint="/v1/chat/completions", completion_window="24h", metadata={"description": "nightly eval job"}, ) print(batch) ``` ```go package main import ( "context" "fmt" "github.com/openai/openai-go/v3" ) func main() { client := openai.NewClient() batch, err := client.Batches.New(context.Background(), openai.BatchNewParams{ InputFileID: "file-abc123", Endpoint: openai.BatchNewParamsEndpointV1ChatCompletions, CompletionWindow: openai.BatchNewParamsCompletionWindow24h, }) if err != nil { panic(err) } fmt.Println(batch.ID) } ``` ```bash curl https://api.openai.com/v1/batches \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input_file_id": "file-abc123", "endpoint": "/v1/chat/completions", "completion_window": "24h" }' ``` ```bash openai batches create \ --input-file-id file-abc123 \ --endpoint /v1/chat/completions \ --completion-window 24h ``` This request will return a [Batch object](https://developers.openai.com/api/reference/resources/batches) with metadata about your batch: ```json { "id": "batch_abc123", "object": "batch", "endpoint": "/v1/chat/completions", "errors": null, "input_file_id": "file-abc123", "completion_window": "24h", "status": "validating", "output_file_id": null, "error_file_id": null, "created_at": 1714508499, "in_progress_at": null, "expires_at": 1714536634, "completed_at": null, "failed_at": null, "expired_at": null, "request_counts": { "total": 0, "completed": 0, "failed": 0 }, "metadata": null } ``` ### 4. Check the status of a batch You can check the status of a batch at any time, which will also return a Batch object. Check the status of a batch ```javascript import OpenAI from "openai"; const openai = new OpenAI(); const batch = await openai.batches.retrieve("batch_abc123"); console.log(batch); ``` ```python batch = client.batches.retrieve(batch.id) print(batch) ``` ```go package main import ( "context" "fmt" "github.com/openai/openai-go/v3" ) func main() { client := openai.NewClient() batch, err := client.Batches.Get(context.Background(), "batch_abc123") if err != nil { panic(err) } fmt.Println(batch.Status) } ``` ```bash curl https://api.openai.com/v1/batches/batch_abc123 \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" ``` ```bash openai batches retrieve \ --batch-id batch_abc123 ``` The status of a given Batch object can be any of the following: | Status | Description | | ------------- | ------------------------------------------------------------------------------ | | `validating` | the input file is being validated before the batch can begin | | `failed` | the input file has failed the validation process | | `in_progress` | the input file was successfully validated and the batch is currently being run | | `finalizing` | the batch has completed and the results are being prepared | | `completed` | the batch has been completed and the results are ready | | `expired` | the batch was not able to be completed within the 24-hour time window | | `cancelling` | the batch is being cancelled (may take up to 10 minutes) | | `cancelled` | the batch was cancelled | ### 5. Retrieve the results Once the batch is complete, you can download the output by making a request against the [Files API](https://developers.openai.com/api/reference/resources/files) via the `output_file_id` field from the Batch object and writing it to a file on your machine, in this case `batch_output.jsonl` Retrieving the batch results ```javascript import OpenAI from "openai"; const openai = new OpenAI(); const fileResponse = await openai.files.content("file-xyz123"); const fileContents = await fileResponse.text(); console.log(fileContents); ``` ```python import os from openai import OpenAI output_file_id = os.environ["OPENAI_BATCH_OUTPUT_FILE_ID"] client = OpenAI() file_response = client.files.content(output_file_id) print(file_response.text) ``` ```go package main import ( "context" "fmt" "io" "github.com/openai/openai-go/v3" ) func main() { client := openai.NewClient() response, err := client.Files.Content(context.Background(), "file-xyz123") if err != nil { panic(err) } defer response.Body.Close() contents, err := io.ReadAll(response.Body) if err != nil { panic(err) } fmt.Println(string(contents)) } ``` ```bash curl https://api.openai.com/v1/files/file-xyz123/content \ -H "Authorization: Bearer $OPENAI_API_KEY" > batch_output.jsonl ``` ```bash openai files content \ --file-id file-xyz123 \ --output batch_output.jsonl ``` The output `.jsonl` file will have one response line for every successful request line in the input file. Any failed requests in the batch will have their error information written to an error file that can be found via the batch's `error_file_id`. For `/v1/videos`, a completed batch result contains video objects that have already reached a terminal state such as `completed`, `failed`, or `expired`. You can use the returned video IDs to download final assets immediately after the batch finishes. Note that the output line order **may not match** the input line order. Instead of relying on order to process your results, use the custom_id field which will be present in each line of your output file and allow you to map requests in your input to results in your output. ```jsonl {"id": "batch_req_123", "custom_id": "request-2", "response": {"status_code": 200, "request_id": "req_123", "body": {"id": "chatcmpl-123", "object": "chat.completion", "created": 1711652795, "model": "gpt-3.5-turbo-0125", "choices": [{"index": 0, "message": {"role": "assistant", "content": "Hello."}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 22, "completion_tokens": 2, "total_tokens": 24}, "system_fingerprint": "fp_123"}}, "error": null} {"id": "batch_req_456", "custom_id": "request-1", "response": {"status_code": 200, "request_id": "req_789", "body": {"id": "chatcmpl-abc", "object": "chat.completion", "created": 1711652789, "model": "gpt-3.5-turbo-0125", "choices": [{"index": 0, "message": {"role": "assistant", "content": "Hello! How can I assist you today?"}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 20, "completion_tokens": 9, "total_tokens": 29}, "system_fingerprint": "fp_3ba"}}, "error": null} ``` The output file will automatically be deleted 30 days after the batch is complete. ### 6. Cancel a batch If necessary, you can cancel an ongoing batch. The batch's status will change to `cancelling` until in-flight requests are complete (up to 10 minutes), after which the status will change to `cancelled`. Cancelling a batch ```javascript import OpenAI from "openai"; const openai = new OpenAI(); const batch = await openai.batches.cancel("batch_abc123"); console.log(batch); ``` ```python import os from openai import OpenAI batch_id = os.environ["OPENAI_BATCH_ID"] client = OpenAI() client.batches.cancel(batch_id) ``` ```go package main import ( "context" "fmt" "github.com/openai/openai-go/v3" ) func main() { client := openai.NewClient() batch, err := client.Batches.Cancel(context.Background(), "batch_abc123") if err != nil { panic(err) } fmt.Println(batch.Status) } ``` ```bash curl https://api.openai.com/v1/batches/batch_abc123/cancel \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" \ -X POST ``` ```bash openai batches cancel \ --batch-id batch_abc123 ``` ### 7. Get a list of all batches At any time, you can see all your batches. For users with many batches, you can use the `limit` and `after` parameters to paginate your results. Getting a list of all batches ```javascript import OpenAI from "openai"; const openai = new OpenAI(); const list = await openai.batches.list(); for await (const batch of list) { console.log(batch); } ``` ```python from openai import OpenAI client = OpenAI() client.batches.list(limit=10) ``` ```go package main import ( "context" "fmt" "github.com/openai/openai-go/v3" ) func main() { client := openai.NewClient() list := client.Batches.ListAutoPaging(context.Background(), openai.BatchListParams{Limit: openai.Int(10)}) for list.Next() { fmt.Println(list.Current().ID) } if err := list.Err(); err != nil { panic(err) } } ``` ```bash curl https://api.openai.com/v1/batches?limit=10 \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" ``` ```bash openai batches list \ --limit 10 ``` ## Model availability The Batch API is widely available across most of our models, but not all. Please refer to the [model reference docs](https://developers.openai.com/api/docs/models) to ensure the model you're using supports the Batch API. ## Rate limits Batch API rate limits are separate from existing per-model rate limits. The Batch API has three types of rate limits: 1. **Per-batch limits:** A single batch may include up to 50,000 requests, and a batch input file can be up to 200 MB in size. Note that `/v1/embeddings` batches are also restricted to a maximum of 50,000 embedding inputs across all requests in the batch. 2. **Queued prompt tokens per model:** Each model has a maximum number of prompt tokens that can be queued for batch processing. You can find these limits on the [Platform Settings page](https://platform.openai.com/settings/organization/limits). 3. **Batch creation rate limit:** You can create up to 2,000 batches per hour. If you need to submit more requests, increase the number of requests per batch. The Batch API currently has no output-token limit. Because Batch API rate limits are a new, separate pool, **using the Batch API will not consume tokens from your standard per-model rate limits**, thereby offering you a convenient way to increase the number of requests and processed tokens you can use when querying our API. ## Batch expiration Batches that do not complete in time eventually move to an `expired` state; unfinished requests within that batch are cancelled, and any responses to completed requests are made available via the batch's output file. You will be charged for tokens consumed from any completed requests. Expired requests will be written to your error file with the message as shown below. You can use the `custom_id` to retrieve the request data for expired requests. ```jsonl {"id": "batch_req_123", "custom_id": "request-3", "response": null, "error": {"code": "batch_expired", "message": "This request could not be executed before the completion window expired."}} {"id": "batch_req_123", "custom_id": "request-7", "response": null, "error": {"code": "batch_expired", "message": "This request could not be executed before the completion window expired."}} ``` --- # ChatGPT Developer mode [