Skip to content

Session files ​

People can attach documents and images to a session. The agent reads them in the turn — or, when they are long, page by page with read-only tools — and it can look at an image again whenever it needs to. Files belong to one session, live for a set number of days, and go when the session is deleted.

A session file is an ordinary file in a bucket of your project (the Document Store). You choose the bucket; nothing is stored anywhere you did not choose, and nothing is written to the server's disk unless the bucket you pick is on a local-disk provider on purpose.

Turn it on ​

Files are off until you switch them on and choose a bucket. Open Build → Agents → your agent → Build and scroll to Files.

  1. Switch on Let people attach files — it adds the paperclip to the session composer and accepts input_file / input_image over the API.
  2. Under Where files are stored, open the Bucket picker and choose a bucket. The picker lists the project's buckets with their provider, marks a bucket on the server's own disk with a server disk badge, and greys out any bucket that cannot take a file now, with the reason (The bucket is disabled, Its storage provider is not active, …). It has a search box; Manage buckets opens the Document Store. With no bucket yet it says No bucket in this project yet and offers Open the Document Store.
  3. Adjust the Folder and the limits if you need to, then Save Configuration. Saving with files on and no bucket is refused (Choose a bucket).
  4. Publish, if API or portal traffic should use it — see the note below.

Settings ​

SettingDefaultValid rangeWhat it does
Let people attach filesoff—Off: the agent takes no attachments. A connected (external) agent never does.
Bucketnone—Required when on. A bucket of the agent's project that is active and whose provider is active and assigned to the project. Saving is refused otherwise.
Folderagent-sessionsletters, digits and . _ - /; at most 200 characters; no . or .. segmentsRoot folder inside the bucket. A file is stored at <folder>/<agentKey>/<sessionId>/<unique name>. Slashes are trimmed and doubled slashes collapsed; the settings page shows Saved as … underneath.
Files per session101 – 100A session holds at most this many files (409 too_many_files).
Largest file (MB)20above 0, up to the platform's FILE_UPLOAD_MAX_MB (default 50)Larger uploads are refused (413). An image is also held to Image size per request: one bigger than a whole model request could never be shown, so it is refused when it arrives.
Allowed file typesanyMIME types; image/* and text/* match a familyEmpty = any file that can be read.
Put in the message up to (tokens)30000100 or moreSee What the agent sees. Estimated as characters / 4.
Page size when reading (characters)20000500 – 200000Size of a page of session_files_read.
Keep files for (days)301 – 3650The file is deleted this long after upload. Changing it affects new files only.
Send new uploads with the next messageon—Files uploaded to a session that no message has carried yet go with the next message.
Images per model request61 – 50How many images one request to the model carries. The newest win; an older one becomes a note that names it, and the agent can look again.
Image size per request (MB)120.1 – 200The same limit in bytes (before base64), oldest dropped first. Providers set their own per-image caps (Anthropic's is 5 MB), so set this to the smallest cap among the models the agent uses.
Show recent images again (messages)20 – 20When the agent's own context no longer holds the images of your last N messages that had one, they are shown again with the next message. 0 = never.

An agent saved before files had a bucket setting, or with files switched on but no bucket, takes no attachments: the paperclip is hidden and an upload is refused with 403 attachments_disabled (Files are not set up for this agent).

Where the files are ​

They are normal Document Store files (Data → Document Store): the same drivers (S3 and compatible, or local disk), the same markdown conversion, the same project file quotas. In the bucket's Files tab, Session files are told apart from the rest — the Show filter offers All files, Session files and Other files, and a session file shows the session it belongs to, when it expires and who uploaded it, with an Open session link. Open the bucket's folders (a breadcrumb starts at Bucket root) to see agent-sessions/<agent>/<session>/…. See File Storage.

A bucket an agent keeps session files in cannot be deleted or disabled (409 bucket_in_use, naming the agents) until you choose another bucket for that agent — whether its draft or its published version still names it (the version API callers and portals run keeps writing there even after you moved the draft). If the bucket or its provider stops working later, an upload is refused with 409 storage_unavailable, naming the bucket; the retention cleanup drops the record of an expired file whose bucket is gone for good (and logs session_file_orphaned for the object left behind) instead of retrying it every hour. Changing the agent's bucket affects new files only; earlier files stay readable in the bucket they were stored in.

Who can choose the bucket ​

Choosing or changing the bucket needs write access to Files (the Document Store); the list of buckets to pick from needs read access to Files — without it the Files section says so instead of showing an empty list. Saving the agent with the bucket it already has asks nothing of Files. An API token is held to the same rule, within its own scope.

Two things to know when you choose:

  • Portal users get the settings of the agent's published version, not the draft, so a new bucket reaches them with the next publish.
  • A bucket on a server-disk provider keeps files on one server's disk — fine for a single node, not for a cluster that scales out: with several replicas and no shared volume, a file can only be read by the replica that stored it.

What the agent sees ​

  • Documents. The file is converted to markdown (the original is kept). If the text of all the files attached to a message is at most the inline limit, it goes in the user turn as <document name="…" id="…">…</document> blocks. If not, the turn carries a short note listing the files and their page counts, and the agent reads them with session_files_read({ fileId, page }), which returns { text, page, pages }.
  • What was attached. The attachment block of the turn names every attachment, whatever the mix — "The person attached 4 files: 3 documents to read with the tools, 1 image shown below." — also for a message of images only, so the model knows what it was sent even when most of it is in the tools or below as pictures.
  • Images. Sent to the model as images when the model accepts image input; otherwise the turn says an image was attached that the model cannot read. Only PNG, JPEG, GIF and WebP are sent as pictures. Any other image/* file (TIFF, SVG, HEIC, BMP, …) is stored like a document with no text: the turn says it was attached and that its format is not supported by the model, and session_files_read / session_files_view_image say the same. It is never put in a request, where the provider would reject it — and, since pictures come back on later turns, reject every later one too.
  • Images travel by reference, not as bytes. In the agent's context, its saved state and its traces, a picture is a short reference (session-file://<fileId>) behind a text handle — [Attached image: "cat.png" (id …)] — that the model can call it by. The bytes are read from the bucket only at the moment the model is called, once per run, within Images per model request and Image size per request. Three things follow: a photo no longer makes a conversation look full to the agent (its size estimate counts an image as an image, about 850 tokens, not as the characters of its bytes), so it is not compacted away for nothing; the saved state stays small however many pictures a session holds; and a picture survives into later messages and into a replay of the conversation. A picture whose file is gone is replaced by [Image "…" is no longer available].
  • Looking again. While the session holds an image and the model accepts images, the agent also has session_files_view_image({ fileId }): it returns the picture in a message that follows the tool result — for an image the context no longer shows, or one that was left out of a crowded request. session_files_read on an image points at it.
  • Files nobody has sent yet. A file uploaded to the session before the first message, or since the last one, goes with the next message — you do not have to name it. That is how create → upload → ask works from the API. (Turning Send new uploads with the next message off leaves these files reachable only through the two tools below.)
  • Earlier uploads. session_files_list and session_files_read are offered whenever the session holds a file, so a later message can ask about an earlier upload. All the file tools only read, so tool approvals never ask about them.

Vision: does the model read images? ​

A chat model accepts images when its input types say so — Model Hub → the model → Input types. A model with nothing stored for it is taken to accept images when its id belongs to a known vision family (GPT-4o, 4.1 and 5 and later, the o-series except o1-mini and o3-mini, Claude 3 and later, Gemini, Llama 3.2 Vision and 4, Qwen-VL, Pixtral, Grok vision, Nova, …); a choice you store on the model — including "text only" — always wins over the id. See Model Hub → Input types and vision.

Studio tells you before you send: with a text-only model, an image chip in the composer carries the warning "{model} is set to text only; the image will not be sent." and a Change it in Models link.

In Studio ​

  • The paperclip in the session composer attaches files. Each file appears as a chip with its size; a chip in orange means no text could be extracted from it, so the agent can only see that it exists. Attached files are shown on the message they were sent with. The paperclip is only there when the agent has files switched on and a bucket chosen; if that bucket later stops working, the upload says which one. The composer enforces the limits up front ("{name}" is larger than the {max} MB this agent accepts, A session holds at most {max} files, "{name}" is a type this agent does not accept).
  • The Files tab in the session's side panel lists the files the session holds (5 of 10 files) with their type and size, a conversion badge (Readable, Converting, No text, Original only), where each is stored (bucket/path), when it expires, who uploaded it, and Download, Download as text and Delete actions. Deleting a file removes it from storage too. A file past its retention shows as Expired.
  • Everywhere a message appears — the session page, the session detail drawer's request inspector, and Observability traces — an attached file is the same component: a thumbnail for a picture, a chip with a type icon and size for anything else, and a muted expired chip for one that has expired. In a trace of an agent session, a picture the request names by reference is drawn as a thumbnail from the session's content route, under the session's own access check; if the route refuses you or the file is gone, the picture is shown as a chip with its name instead. A trace that is not an agent session's lists such pictures by name only.

API ​

Create a session, upload, ask ​

bash
# 1. A session with no message (nothing runs, nothing is spent).
SESSION=$(curl -s -X POST "$CONSOLE_URL/api/client/v1/agents/sessions" \
  -H "Authorization: Bearer $COGNIPEER_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "agent": "support-bot" }' | jq -r .id)

# 2. Upload — the raw bytes as the body, the type as Content-Type, the name in ?name=.
curl -X POST "$CONSOLE_URL/api/client/v1/agents/sessions/$SESSION/files?name=contract.pdf" \
  -H "Authorization: Bearer $COGNIPEER_API_TOKEN" \
  -H "Content-Type: application/pdf" \
  --data-binary @contract.pdf

# 3. Ask — naming the session, not the files. The file goes with this first message.
curl -X POST "$CONSOLE_URL/api/client/v1/agents/responses" \
  -H "Authorization: Bearer $COGNIPEER_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{ \"model\": \"support-bot\", \"session_id\": \"$SESSION\", \"input\": \"Summarise the contract\" }"
typescript
import { readFile } from 'node:fs/promises';

const session = await client.agents.sessions.create({ agent: 'support-bot' });

await client.agents.sessions.files.upload(session.id, await readFile('contract.pdf'), {
  name: 'contract.pdf',
  mimeType: 'application/pdf',
});

const reply = await client.agents.responses.create({
  model: 'support-bot',
  session_id: session.id,
  input: 'Summarise the contract',
});
console.log(reply.output[0].content[0].text);

The user message records the file in attachments. See Agent sessions for the session calls.

Upload ​

Upload the file as the raw request body, with its MIME type as Content-Type and its name in ?name=:

http
POST /api/client/v1/agents/sessions/{id}/files?name=contract.pdf
Authorization: Bearer <token>
Content-Type: application/pdf

<bytes>

A small file can go as JSON instead: { "name": "notes.txt", "content_type": "text/plain", "data": "<base64 or data URL>" } (contentType is accepted too). Because any JSON content type is read as that envelope, a .json file goes as application/octet-stream — its name still says what it is (the SDK does this for you).

Call
POST …/files?name=Upload. 201 with the file.
GET …/filesThe session's files: { "object": "list", "data": [...] }.
GET …/files/{fileId}/contentThe file's bytes (see below).
DELETE …/files/{fileId}Delete one: { "id": "…", "object": "agent.session.file", "deleted": true }.

A file, as the client API returns it (the dashboard's calls use camelCase):

json
{
  "id": "6710a3f2c1d4e5f60718293a",
  "object": "agent.session.file",
  "session_id": "6f1c0a2b9d3e4f5a6b7c8d9e",
  "name": "contract.pdf",
  "mime_type": "application/pdf",
  "size": 183422,
  "conversion_status": "succeeded",
  "bucket_key": "uploads",
  "path": "agent-sessions/support-bot/6f1c0a2b9d3e4f5a6b7c8d9e/contract-1727780400000-ab12cd34.pdf",
  "created_at": "2026-10-01T12:00:00.000Z",
  "expires_at": "2026-10-31T12:00:00.000Z",
  "content_url": "/api/client/v1/agents/sessions/6f1c0a2b9d3e4f5a6b7c8d9e/files/6710a3f2c1d4e5f60718293a/content"
}

bucket_key and path say where the file is stored: the bucket in the Document Store and the object's path inside it (the bucket's own prefix is added by the file service).

conversion_status is succeeded when the text could be read, pending while it converts, and failed or skipped when only the original exists (conversion_error says why); the agent then knows the file is there but cannot read it.

Upload errors ​

StatusCodeMeaning
400empty_fileThe body is empty.
400invalid_request?name= missing, or a JSON envelope without name or data.
403attachments_disabledFiles are off for the agent, or on with no bucket chosen.
404session_not_found / agent_not_foundNo such session or agent in the token's project.
409too_many_filesThe session already holds Files per session files.
409storage_unavailableThe agent's bucket cannot take a file now; the message names the bucket.
413file_too_largeOver Largest file (or Image size per request for a picture), or over the platform's FILE_UPLOAD_MAX_MB.
415unsupported_file_typeNot in Allowed file types.
429quota_exceededOver the project's file quotas, rate limits or storage bytes.

Read a file ​

http
GET /api/client/v1/agents/sessions/{id}/files/{fileId}/content
GET /api/client/v1/agents/sessions/{id}/files/{fileId}/content?variant=markdown

Returns the original's bytes with the file's own Content-Type, or — with variant=markdown — the text the platform converted it to (409 markdown_unavailable when there is none, as for a picture).

  • Pictures open in place (Content-Disposition: inline); everything else is offered as a download (attachment). ?disposition=inline|attachment asks for the other where it is safe. SVG, HTML and other types that could run code in a browser are always a download.
  • The filename in the header is sanitised, and the response is Cache-Control: private (max-age=60) with X-Content-Type-Options: nosniff.
  • A file of another session is a 404.
  • The route takes byte ranges: one Range: bytes=… (a-b, a- or -n) is answered 206 Partial Content with a Content-Range, a range that starts past the end 416, and several ranges (or another unit) are ignored and the whole file is sent. Every answer says Accept-Ranges: bytes.

Studio uses the same route to show thumbnails and open files from a session's messages. In the SDK: client.agents.sessions.files.content(id, fileId, { variant }) (buffered) and …contentStream(…) (streamed).

The dashboard has the same four calls under /api/agents/{agentId}/sessions/{sessionId}/files. Uploads count toward the project's file quotas like any other file.

Name the files a message uses in the chat body: attachments: ["<fileId>", …] (dashboard /chat, /chat/stream). The user message then records attachments: [{ fileId, name, mimeType, size }].

Responses API input parts ​

input_file and input_image parts of the last user message are used instead of dropped:

json
{
  "model": "support-bot",
  "session_id": "6f1c0a2b9d3e4f5a6b7c8d9e",
  "input": [{
    "role": "user",
    "content": [
      { "type": "input_text", "text": "Compare these" },
      { "type": "input_file", "file_id": "6710a3f2c1d4e5f60718293a" },
      { "type": "input_file", "file_data": "data:application/pdf;base64,JVBERi0x…", "filename": "a.pdf" },
      { "type": "input_image", "image_url": "data:image/png;base64,iVBORw0KGgo…" },
      { "type": "input_image", "image_url": "https://example.com/cat.png" }
    ]
  }]
}
  • file_id is a file already uploaded to the session you continue — with session_id, its alias conversation (a string or { "id": … }), or previous_response_id. A file_id with no session to continue is 400 attachment_not_found; a file that is not part of that session is 404 attachment_not_found.
  • file_data and a data: image are decoded and uploaded to the session first. An https image URL is fetched (private addresses are refused; 400 attachment_unreachable when it cannot be) within the agent's size limit and uploaded, provided the server answers with an image/* Content-Type — any other answer (JSON, HTML, no type) is refused with 415 unsupported_file_type and nothing is stored. To give the agent a document, send it as input_file instead.
  • A request carries at most 100 file parts. With background: true, the run carries the attachments. For Idempotency-Key, only the file ids you named identify the request: a file sent inline is stored under a new id each time.

This changes what earlier releases did

input_file and input_image parts used to be ignored and only the text ran. An agent that takes no attachments now answers a request that carries such a part with 403 attachments_disabled and runs nothing, rather than answering without the file. Clients that sent these parts by habit should stop sending them, or switch files on for the agent.

What a client can check first ​

GET /api/client/v1/agents/{agentKey} includes an attachments block so a screen can hide the attach button — or warn that an image will not be seen — instead of finding out from a refusal:

json
{
  "agent": {
    "key": "support-bot",
    "attachments": {
      "enabled": true,
      "max_files": 10,
      "max_file_size_mb": 20,
      "allowed_mime_types": ["application/pdf", "image/*"],
      "accepts_images": true
    }
  }
}

enabled is true only when files are on, a bucket is chosen and that bucket can take a file right now. accepts_images is whether the agent's model reads images.

Cleanup and retention ​

Files past their retention are removed by the hourly cleanup (object, markdown and record). Deleting a session removes its files at once; one that cannot be removed then is retried by the cleanup when it expires. A file that keeps failing does not hold the cleanup up: each sweep goes on to the files behind it. Changing Keep files for affects new files only.

Config reference ​

json
{
  "config": {
    "attachments": {
      "enabled": true,
      "bucketKey": "uploads",
      "folder": "agent-sessions",
      "maxFiles": 10,
      "maxFileSizeMb": 20,
      "allowedMimeTypes": ["application/pdf", "image/*"],
      "inlineMaxTokens": 30000,
      "readPageChars": 20000,
      "retentionDays": 30,
      "attachNewUploads": true,
      "maxImages": 6,
      "maxBytes": 12000000,
      "carryRecentImages": 2
    }
  }
}

Setting bucketKey through the API needs write access to Files for the token's owner (403 otherwise). PATCH replaces config wholesale — send it complete.

Security notes ​

  • Your storage, your choice. Files go only to the bucket the agent names, and nothing is written to the server's disk unless that bucket is on a local-disk provider. Choosing the bucket needs write access to Files, because it decides where uploaded content lands.
  • Session isolation. The content, list and delete routes resolve the session through the caller's tenant and project, and a file id from another session is a 404 — an id never confirms that a file exists elsewhere. Studio draws a file or thumbnail only under the session's own access check.
  • Uploaded content is not trusted. It is served from the Console's own origin with a sanitised file name, X-Content-Type-Options: nosniff and a private cache header; only pictures open inline, and SVG, HTML and any other type that could run code in a browser are always downloads.
  • Fetching an image URL is guarded. An https image URL in an input_image part is fetched by Console under the outbound address guard — private addresses are refused — within the agent's size limit, and only an image/* answer is stored.
  • Pictures are not copied around. An image is a reference in the agent's context, its saved state and its traces; the bytes are read from the bucket at the model call. Traces show a thumbnail through the same access check, or a name chip when you may not open the file.
  • Limits are hard limits. Per-file size, files per session, allowed types, the project's file quotas and the platform's FILE_UPLOAD_MAX_MB are all enforced when the file arrives, and files expire on a schedule you set.
  • Approvals never gate reading. The three file tools only read, so a tool approval policy does not interrupt them.

Troubleshooting ​

The paperclip is missing. Files are off, or on with no bucket, or the agent is a connected (external) agent. The single-agent GET shows attachments.enabled.

403 attachments_disabled ("Files are not set up for this agent"). The agent has files on but no bucket — open Build → Files and choose one. Agents saved before the bucket setting existed need this once.

409 storage_unavailable. The chosen bucket or its provider stopped working. The message names the bucket. Fix it in the Document Store, or choose another bucket (new files only).

409 bucket_in_use when deleting a bucket. An agent keeps session files there — including through its published version. Choose another bucket for that agent, publish, then delete.

The agent says it cannot see my image. Check, in order: the model's Input types include Images; the file is PNG, JPEG, GIF or WebP; the image fits Image size per request; and — for an older image — it has not been left out of a crowded request (Images per model request). A text-only model gets a note instead of the picture.

A long PDF is not in the message, the agent reads it page by page. That is the Put in the message up to (tokens) limit working as designed: if all the text attached to one message is over it, none of it is inlined. Raise the limit for models with a large context, or let the agent page.

A file I uploaded never reached the agent. Send new uploads with the next message may be off — then the agent reaches it only with session_files_list / session_files_read. Or the session is not the one you ask on: name it with session_id.

What's new ​

Session files on a bucket you choose, images by reference with a view_image tool, the sessions API (create → upload → ask), input_file / input_image on the Responses API, the attachments block on the agent resource, and model input types are new in the October 2026 agent runtime release.

Step by step: give an agent files and images ​

People attach contracts, spreadsheets and screenshots to a conversation, and expect the agent to read them. This guide switches that on for one agent, stores the files in a bucket you choose, and shows both ways in: the paperclip in Studio and a three-call flow over the API (create a session, upload, ask).

What you end up with ​

paperclip / API upload ──► Document Store bucket   <folder>/<agent>/<session>/<file>
                                  │
                                  ├─ document ─► converted to markdown ─► inlined in the turn, or read page by page
                                  └─ image ────► sent to the model by reference, only if the model reads images

Before you start ​

You needWhere
An agentBuild and publish an agent
A bucket in the agent's project, on an active file provider assigned to that projectData → Document Store → Create bucket — see File Storage
For images: a chat model that accepts themModel Hub — the model's Input types include Images
An API token, for the API flowSettings → API Tokens → Create Token

Use a shared bucket in a cluster

A bucket on a server-disk (local-filesystem) provider keeps files on one server. With several replicas and no shared volume, a file can only be read by the replica that stored it. Use an S3-compatible bucket for anything that scales out.


1. Check that the model takes images ​

Skip this step for documents only.

Open Model Hub, find the agent's model and look for the vision badge in the list. No badge? Open the model's Edit screen and look at Input types under Capabilities:

  • Detected from the model id — Console recognised a vision model family. Nothing to do.
  • Nothing ticked for Images — tick it, if the model really reads images. A choice you make here always beats the detection.

Studio warns you before it matters: with a text-only model, attaching an image shows "{model} is set to text only; the image will not be sent." See Input types and vision.

2. Switch files on for the agent ​

Build → Agents → your agent → Build, section Files:

  1. Switch on Let people attach files.
  2. Bucket — open the picker and choose your bucket. Buckets that cannot take a file now are greyed out with the reason (The bucket is disabled, Its storage provider is not active, …). Choosing or changing the bucket needs write access to Files; if you only have read access the section says so.
  3. Folder — where the agent's files go inside the bucket. The default agent-sessions is fine; the form shows Saved as agent-sessions/<agent key>/<sessionId>/….
  4. Limits — start with the defaults (10 files per session, 20 MB each, kept 30 days) and tighten later. Add Allowed file types (application/pdf, image/*) if the agent should refuse everything else.
  5. Save Configuration.

Then publish. Files settings travel with the agent version, and API calls and portals run the published one.

3. Try it in Studio ​

  1. Sessions → Start session. A paperclip is now in the composer.
  2. Attach a PDF and a screenshot. Each file appears as a chip with its size; an orange chip means no text could be extracted, so the agent can only see that the file exists.
  3. Ask "Summarise the contract and tell me what the screenshot shows."
  4. Open the Files tab in the side panel: each file with where it is stored (bucket/path), when it expires, and who uploaded it.
  5. In Data → Document Store → your bucket → Files, open the folders (agent-sessions → the agent → the session) and filter with Session files.

What the agent saw depends on the size of the text. If everything attached to a message is under Put in the message up to (tokens) (30,000 by default), the documents are placed in the turn as markdown. If not, the agent is told which files there are and how many pages, and reads them with session_files_read — one Page size when reading (20,000 characters) at a time. Images go to the model by reference and can be looked at again with session_files_view_image.

4. Do the same from code ​

The flow is create → upload → ask: start a session with no message, put the files in it, then ask naming the session — never the files.

bash
# 1. A session with no message. Nothing runs, nothing is spent.
SESSION=$(curl -s -X POST "$CONSOLE_URL/api/client/v1/agents/sessions" \
  -H "Authorization: Bearer $COGNIPEER_API_TOKEN" -H "Content-Type: application/json" \
  -d '{ "agent": "support-bot", "metadata": { "ticket": "4821" } }' | jq -r .id)

# 2. Upload — raw bytes as the body, the MIME type as Content-Type, the name in ?name=.
curl -X POST "$CONSOLE_URL/api/client/v1/agents/sessions/$SESSION/files?name=contract.pdf" \
  -H "Authorization: Bearer $COGNIPEER_API_TOKEN" -H "Content-Type: application/pdf" \
  --data-binary @contract.pdf

curl -X POST "$CONSOLE_URL/api/client/v1/agents/sessions/$SESSION/files?name=screenshot.png" \
  -H "Authorization: Bearer $COGNIPEER_API_TOKEN" -H "Content-Type: image/png" \
  --data-binary @screenshot.png

# 3. Ask. Files nobody has sent yet ride along with this first message.
curl -X POST "$CONSOLE_URL/api/client/v1/agents/responses" \
  -H "Authorization: Bearer $COGNIPEER_API_TOKEN" -H "Content-Type: application/json" \
  -d "{ \"model\": \"support-bot\", \"session_id\": \"$SESSION\", \"input\": \"Summarise the contract and describe the screenshot.\" }"
typescript
import { readFile } from 'node:fs/promises';

const session = await client.agents.sessions.create({ agent: 'support-bot', metadata: { ticket: '4821' } });

await client.agents.sessions.files.upload(session.id, await readFile('contract.pdf'), {
  name: 'contract.pdf',
  mimeType: 'application/pdf',
});
await client.agents.sessions.files.upload(session.id, await readFile('screenshot.png'), {
  name: 'screenshot.png',
  mimeType: 'image/png',
});

const reply = await client.agents.responses.create({
  model: 'support-bot',
  session_id: session.id,
  input: 'Summarise the contract and describe the screenshot.',
});
console.log(reply.output[0].content[0].text);

A few variations you will want:

  • One call, no pre-upload. Send the files inside the Responses request as input_file / input_image parts — { "type": "input_file", "file_data": "data:application/pdf;base64,…", "filename": "a.pdf" } or { "type": "input_image", "image_url": "https://example.com/cat.png" } (an https URL is fetched for you and must answer with an image/* type). They are stored in a new session, or in the one you name.
  • Ask about an earlier upload. A later message on the same session can refer to a file uploaded before — the agent has session_files_list and session_files_read whenever the session holds a file.
  • See what is attached. GET /agents/sessions/{id}/files lists the files with their bucket_key, path, expires_at and conversion_status.
  • Read one back. GET …/files/{fileId}/content returns the original; add ?variant=markdown for the text the agent saw.
  • Discover the limits first. GET /agents/{agentKey} returns attachments: { enabled, max_files, max_file_size_mb, allowed_mime_types, accepts_images } — enough to hide an attach button or warn about an image before the user hits an error.

5. Tune it ​

If you see…Change
Long documents are read page by page and you would rather have them inlineRaise Put in the message up to (tokens) — only for models with a large context
Old screenshots drop out of a long chatRaise Images per model request (default 6) and Image size per request (MB) (default 12) — but keep it under the smallest per-image cap of the providers you use (Anthropic's is 5 MB)
The agent forgets what the last screenshot looked like after a long contextShow recent images again (messages) (default 2) re-shows the images of your last N messages when the agent's own context no longer holds them
Files pile upLower Keep files for (days) — it affects new files only
Uploads are refused with 415Add the type to Allowed file types, or clear the list to accept any

6. Clean up ​

Files go away on their own: the hourly cleanup removes every file past its retention (object, markdown and record), and deleting a session removes its files at once. You can delete one file yourself with the trash icon in the Files tab or DELETE /agents/sessions/{id}/files/{fileId}. To move to a new bucket, choose it in Build → Files and publish — new files only go there; earlier files stay readable where they were. The old bucket cannot be deleted or disabled until no agent (draft or published version) names it.

What can go wrong ​

SymptomCause
No paperclipFiles are off, no bucket is chosen, or the agent is a connected (external) agent.
403 attachments_disabled — Files are not set up for this agentFiles are on but no bucket is chosen (older agents need this once).
409 storage_unavailableThe bucket or its provider stopped working; the message names the bucket. Fix it, or choose another.
413 file_too_largeOver Largest file — or, for an image, over Image size per request.
The agent "cannot see" the imageThe model is text-only, the format is not PNG / JPEG / GIF / WebP (TIFF, SVG, HEIC are stored as documents with no text), or an older image was left out of a crowded request.
A clean PDF is read page by page, not summarised at onceWorking as designed: the text is over the inline limit.

Where to go next ​

Studio · Pulse · Console · Agent SDK and more — the Cognipeer documentation hub