Retrieve OpenAI LLM Generated Documents Using Responses API

I’ve been unable to retrieve OpenAI LLM generated documents in my Responses API App. I’ve tried email, Dropbox, downloading (which places them in sandbox:/mnt/data) and file.io. I see this statement from ChatGPT:

  • Some official ChatGPT interfaces (like chat.openai.com) support file uploads and downloads directly within the conversation.
  • Other platforms or API-based environments often have sandbox restrictions or don’t support direct file transfer, so I can only provide links or coded content.

In this environment, file upload/download capabilities are limited to sandbox storage or external links, which is why I can’t attach files directly like in the official ChatGPT UI you might be used to.

Is there a way around this so that documents created with Responses API can be downloaded and saved? I’m using Edge on Ubuntu, and the gpt-4.1-mini model.

If you mean documents generated by code interpreter, they are stored inside a container.

You can download then using the containers endpoint:
https://platform.openai.com/docs/api-reference/container-files

When you run code interpreter it will mention on your annotations a container id and a container file id. Then, you use the API to access these files.

For the moment, there is no python sdk classes to retrieve, you must implement them using curl or http requests.

Not code interpreter. If I ask ChatGPT to create a PowerPoint doc for a product rollout plan, I can download the doc that ChatGPT includes in the response. If I ask the same thing using my Responses API Agent, the download links don’t work (e.g. sandbox:/mnt/data or file.io).

I’m not sure I’m following you.

Could you please share an example of how you are generating documents in responses API (not ChatGPT)?

ChatGPT is an end user product, it doesn’t always have a direct compatibility with the API.

I mentioned containers because depending on the circunstances, code interpreter can generate certain files.

Third app integrations is a ChatGPT thing, not the API.

I’ll show how it can be done in the Prompts Playground using the Responses endpoint.

Note that the AI isn’t given quite enough information or training about the code interpreter python sandbox environment. It also can only output to the user, unless you creatively use JSON to get some out-of-channel information.

Below: "store" true if you actually want a “chat” with a sustained code interpreter environment:

Prepare the AI beyond just adding the tool

The AI goes to work

…on my job that could only be satisfied by code-writing. Note that there is content here for you to stream also, as there can be along with any output with tool call, internally, or externally.

The AI is done

Note that the Playground neither presents the true output of the AI in plain text nor the API response, nor a non-stream that could allow a browser to capture objects. Display also has a broken link courtesy of the Prompts Playground.


Getting file events and file contents.

Enhancing observability: an input API parameter, undocumented in the API Reference:

"include":["code_interpreter_call.outputs"]

Thanks for the suggestions. I was able to replicate your playground session. I added very similar instructions to a Responses API session with my Agent and asked for a one-page PP presentation for a business plan. It still gave me a sandbox link. Here’s the API exchange from my Agent’s log file (the Python source that was provided by the model is clipped out). One thing; I don’t use streaming in the Agent. I have seen the model respond with a “here’s your document” message, and the document isn’t provided (I have to ask again). I’m assuming streaming isn’t required.

[2025-05-26T02:48:36.725Z] [INFO]     Responses Object: {"model":"gpt-4.1","input":"Make me a one-page PowerPoint document containing an organic dog food business plan brief.","stream":false,"include":["code_interpreter_call.outputs"],"instructions":"You are a helpful AI with expert computer programming skills.\n\nYour 'Python' tool has hundreds of useful preinstalled modules.\n\nEnsure that when creating mount points for a user that you always produce a download link in responses, or zip more than three files and offer that as download; the user cannot observe sandbox or file.io contents.  When creating documents use A4 paper size."}
[2025-05-26T02:48:48.215Z] [INFO]     {"id":"resp_6833d68514208191a7002fabdc3a549b04e59ab524fd8389","object":"response","created_at":1748227717,"status":"completed","background":false,"error":null,"incomplete_details":null,"instructions":"You are a helpful AI with expert computer programming skills.\n\nYour 'Python' tool has hundreds of useful preinstalled modules.\n\nEnsure that when creating mount points for a user that you always produce a download link in responses, or zip more than three files and offer that as download; the user cannot observe sandbox or file.io contents.  When creating documents use A4 paper size.","max_output_tokens":null,"model":"gpt-4.1-2025-04-14","output":[{"id":"msg_6833d68d8058819184c9dcebada8d29a04e59ab524fd8389","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"text":"Certainly! I’ll generate a concise, one-page PowerPoint (.pptx) slide containing a brief business plan for an organic dog food company.\n\n**Content to be included:**\n\n---\n\n**Slide Title:**  \n**OrganicDog: Business Plan Brief**\n\n---\n\n**Sections:**\n\n- **Business Overview:**  \n  Premium, all-natural dog food using locally sourced, organic ingredients. Focus on health, sustainability, and transparency.\n\n- **Market Opportunity:**  \n  Rapidly growing $10B+ pet food market, driven by health-conscious pet owners.\n\n- **Target Customers:**  \n  Dog owners aged 25-50, urban dwellers, eco-friendly consumers.\n\n- **Product Offerings:**  \n  Dry kibble, wet food, and treats – free from artificial additives.\n\n- **Competitive Advantage:**  \n  Certified organic, transparent sourcing, and meal plan subscriptions.\n\n- **Revenue Streams:**  \n  Direct-to-consumer eCommerce, retail partnerships, subscriptions.\n\n- **Marketing Channels:**  \n  Social media, influencer partnerships, vet clinics, events.\n\n- **Vision:**  \n  To become the most trusted organic dog food brand nationwide.\n\n---\n\nI will create the PowerPoint slide and then provide you with a download link for the file.\n\n<clip>**The PowerPoint file is ready. Please [download OrganicDog Business Plan Brief.pptx](sandbox:/OrganicDog_BusinessPlan_Brief.pptx).**"}],"role":"assistant"}],"parallel_tool_calls":true,"previous_response_id":null,"reasoning":{"effort":null,"summary":null},"service_tier":"default","store":true,"temperature":1,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[],"top_p":1,"truncation":"disabled","usage":{"input_tokens":103,"input_tokens_details":{"cached_tokens":0},"output_tokens":972,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":1075},"user":null,"metadata":{},<clip>

Let’s call for my PDF generation locally.

In a demo script for non-streaming, I have a parser that understands the response.output list items. I haven’t written it to fully offer up files translated to a storage download.

[0]: Content found to return
[1]: Output list item of type ‘code_interpreter_call’ skipped
[2]: Content found to return
To create a one-page PDF with 1 cm randomly-colored squares, I will:

  1. Define the page size (e.g., A4).
  2. Calculate how many 1 cm squares fit horizontally and vertically.
  3. Draw each square with a random color.

I’ll generate the PDF now.

I have created the one-page PDF with 1 cm randomly-colored squares. You can download it here:

[random_colored_squares.pdf](sandbox:/mnt/data/random_colored_squares.pdf)

So in the AI output, you have a link that should be captured and rewritten.

But with what?

Let’s look at that code interpreter output more closely.

  "output": [
    {
      "id": "msg_6833e",
      "content": [
        {
          "annotations": [],
          "text": "To create a one-page PDF with 1 cm randomly-colored squares, I will:\n\n1. Define the page size (e.g., A4).\n2. Calculate how many 1 cm squares fit horizontally and vertically.\n3. Draw each square with a random color.\n\nI'll generate the PDF now.",
          "type": "output_text"
        }
      ],
      "role": "assistant",
      "status": "completed",
      "type": "message"
    },

There was the AI writing about its intention to use the tool. AI can produce text as well as calling functions in a response, this isn’t just iteration done internally.

Then we get our next item returned. Responses abstracts away that this was the same “assistant” generation.

    {
      "id": "ci_6833e6",
      "code": "from reportlab.lib.pagesizes import A4\nfrom reportlab.pdfgen import canvas\nfrom reportlab.lib.units import cm\nimport random\n\n# Page size\nwidth, height = A4\n\n# Square size in points (1 cm)\nsquare_size = 1 * cm\n\n# Number of squares horizontally and vertically\nnum_squares_x = int(width // square_size)\nnum_squares_y = int(height // square_size)\n\n# Create PDF\npdf_path = \"/mnt/data/random_colored_squares.pdf\"\nc = canvas.Canvas(pdf_path, pagesize=A4)\n\n# Draw squares\nfor i in range(num_squares_x):\n    for j in range(num_squares_y):\n        # Random color\n        r = random.random()\n        g = random.random()\n        b = random.random()\n        c.setFillColorRGB(r, g, b)\n        \n        # Draw square\n        x = i * square_size\n        y = j * square_size\n        c.rect(x, y, square_size, square_size, fill=1, stroke=0)\n\nc.showPage()\nc.save()\n\npdf_path",
      "results": null,
      "status": "completed",
      "type": "code_interpreter_call",
      "container_id": "cntr_6833e",
      "outputs": [
        {
          "type": "logs",
          "logs": "'/mnt/data/random_colored_squares.pdf'"
        }
      ]
    },

This object is a result of include=["code_interpreter_call.outputs",] - along with four other strings that can go in that list to receive other stuff.

You can see the written code, the output produced and returned to the AI from that code. And more, you will get multiple if there are internal “steps”, such as the AI looping in writing bad code and trying again.

The display is useful for a UI, not needed for our file mapping to storage.

We now get to the AI output after it is finished with coding.

    {
      "id": "msg_6833e6",
      "content": [
        {
          "annotations": [
            {
              "file_id": "cfile_6833e6",
              "index": null,
              "type": "container_file_citation",
              "container_id": "cntr_6833e6",
              "end_index": 168,
              "filename": "random_colored_squares.pdf",
              "start_index": 124
            }
          ],
          "text": "I have created the one-page PDF with 1 cm randomly-colored squares. You can download it here:\n\n[random_colored_squares.pdf](sandbox:/mnt/data/random_colored_squares.pdf)",
          "type": "output_text"
        }
      ],
      "role": "assistant",
      "status": "completed",
      "type": "message"
    }
  ]

End of the “output” list from the API response object.

Beside the text response for display (or that might stream), we have an annotation. You can see it is type "container_file_citation" - and has both the autogenerated code container ID, a file ID, and also, a plain file name, the same as the mount point file.

Then you’ve just got go grabbing and downloading, and fast, and retain for the user beyond the quick expiry of the code interpreter “session” container.

You can see that the playground doesn’t look back at a stream and rewrite the annotation into it. If you are not streaming, it is easy for you to not need to hold back markdown URLs or to rewrite the UI: you can just regex the text, with where you want to serve the file from.

Here’s your API Reference link for retrieving out of “Containers”: https://platform.openai.com/docs/api-reference/container-files/retrieveContainerFile

1 Like

With my Responses API agent, accessing any OpenAI LLM generated document saved on /mnt/data/ or sandbox:/mnt/data/ is an issue with at least these browsers that I’ve tried on Ubuntu: Edge, Chrome and Firefox. I don’t know a way to access them. Accessing them on the Playground is fine, but the links are different.

As I wrote previously: the AI only understands the mount point. It cannot write working links, and there is no link that can resolve to a web-based file that you don’t provide yourself.

  • You need to look at annotations.
  • You need to address the container ID
  • You need to get the output file ID
  • You need to retrieve from the container
  • You need to host the file somewhere
  • You need to rewrite the user’s URL from a sandbox:/mnt/ type of link to the filename referenced to a URL where you are providing the file.
  • Or, you aren’t even expecting a web browser, and your application allows file downloads.
1 Like

I was able to get a container ID. I’ll come back and let you know after I’ve downloaded the file. Thanks for your patience with my questions.

1 Like

Okay, I can list containers, list container files, download files from a container and delete containers. But, I’ve a few questions; if a document generation is requested, it is guaranteed to be available in the container when the openai.responses.create returns? After requesting the generation of a document in openai.responses.create containing the following:

    [ {
        "type": "code_interpreter",
        "container": {"type": "auto"}
    }];

Sometimes don’t see a container returned from the openai.responses.create, but when I run a utility to return active containers I see one that has a state of “running”. Does that mean the document is still being generated (if it does that means there is no guarantee that the document is available when the openai.responses.create returns)? I don’t see a list of states in the container status API documentation, other than notes for “active” and “deleted”. I have seen “expired”, and “running”.

As long as the code interpreter actually created any file (not a model hallucination instead of using python), they should be in available as long as it is active.

Running means it is still active, and after about 20 minutes of inactivity, it will be automatically deleted and you will not be able to retrieve its files anymore.

If the container is in auto mode, it will create a new container whenever the code interpreter is needed.

So, when you list all containers, not all of them necessarily belong to your current request - it’s a list of all containers you’ve created across multiple requests.

2 Likes

Would there be a reason why I don’t always get documents in a running container? Sometimes when I request a document, it’s never provided in an annotation with “container_file_citation” in the response yet there are “running” containers with no associated files (possibly from other requests) and sometimes the model creates a sandbox:/mnt/data link in the response, and sometimes it doesn’t (when it doesn’t it typically asks if I want the document, and when I tell it “yes” it often just provides a sandbox:/mnt/data link. I’m also providing instructions like this for requests:

“When documents are generated, don’t provide download links (e.g. sandbox or /mnt/data links). Instead, provide annotations with container information for the specific document(s) file name(s) that I’ll download myself.”

There is a thing or two that you can try to enforce the usage of python:

While we call this tool Code Interpreter, the model knows it as the python tool. Models usually understand prompts that refer to the code interpreter tool. However, the most explicit way to invoke this tool is to ask for “the python tool” in your prompts.

Explicitly create a container beforehand and ask the model to use the 'python tool'

In this example in the docs, it uses for calculation but you could modify it to ask for a document generation using the python tool.

Since you create the container by yourself, it becomes easier to check for files created on it.

from openai import OpenAI
client = OpenAI()

container = client.containers.create(name="test-container")

response = client.responses.create(
    model="gpt-4.1",
    tools=[{
        "type": "code_interpreter",
        "container": container.id
    }],
    tool_choice="required",
    input="use the python tool to calculate what is 4 * 3.82. and then find its square root and then find the square root of that result"
)

print(response.output_text)
```

Since annotations are not working consistently for you and in the end they point to a file inside a container, perhaps it is better to ignore them and just look directly in the container. Eventually they might fix it but until then…

1 Like