File transfers
Tool arguments and tool results are JSON that passes through your agent’s context, which makes them the wrong place for an 8 MB PDF. File transfers route the bytes around the model: you stage a file in Agent Handler and give the tool a reference to it, or a tool stages its output and gives you a download URL. The file content never enters the conversation, so it costs no tokens and is never at risk of being truncated to fit a context window.
Reach for this whenever a tool takes or returns real file content: attaching a signed contract to a draft email, uploading a scanned document to a library, pulling an invoice out of a mailbox.
Limits
Every request on this page authenticates with the same Access Key that authorizes your MCP calls:
Sending a file to a tool
Reserve a slot, send the bytes, then name the reference in the tool call. The three steps are separate requests because the middle one can take a while and you want the other two to be cheap.
The first two steps belong in your application code, not in the agent loop. Your backend already has the file on disk or in a bucket; the agent does not, and cannot make an HTTP PUT. By the time the model chooses a tool, all it needs is the file_reference string.
1. Reserve a slot
POST /api/v1/files/ records the file’s metadata and tells you where to send the bytes. Nothing moves yet.
cURL
Python
filename is the name the file carries into the connector, and it is the only required field. content_type defaults to application/octet-stream. size_bytes is optional and buys you one thing: a 400 on a file that is over the limit, before you spend bandwidth sending it. The upload enforces the limit either way.
The Authorization value under upload.headers is a placeholder, not a key. Agent Handler never echoes your Access Key back to you; send your own.
2. Send the bytes
PUT the raw file to upload.url. There is no multipart wrapper and no form encoding: the request body is the file itself, streamed.
cURL
Python
Passing an open file object as data streams it, so a 50 MB upload does not need 50 MB of memory. The response confirms the byte count that actually arrived:
One reservation accepts one successful upload. To replace a file, reserve a new slot.
3. Call the tool
Pass the file_reference value as the tool’s file_reference argument. Every upload tool also accepts inline content for small files, and the two are mutually exclusive: provide one or the other, never both.
Where the argument sits depends on the tool’s own input schema, which you can read from tools/list:
A tools/call uploading the reserved PDF into a SharePoint library:
Agent Handler validates the reference, streams the staged bytes into SharePoint, and returns the created item. Your agent sees the item’s ID and metadata, never the file content. The reference is spent once the call succeeds, and the staged copy is deleted.
A call that fails leaves the reference usable, so you can retry it without uploading again.
Getting a file back from a tool
File-returning tools answer inline by default, encoded as text or base64. Set return_download_url to true and you get a URL instead, which handles the full 50 MB and keeps the bytes out of the agent’s context.
Inline is not a uniform ceiling. Some tools reject an oversized file outright rather than truncating it: both SharePoint tools cap an inline download at 10 MB, and google_drive__export_file inherits Google’s own 10 MB export cap. Others stream whatever the provider returns. Where a cap exists, the tool’s error tells you to retry with return_download_url, so treat the URL as the default for anything you did not author yourself.
When you do take content inline, read the encoding field next to it instead of assuming a format. Both SharePoint tools decide per file rather than per file type: the connector tries to decode the bytes as UTF-8 and falls back to base64 only when that fails. A CSV comes back as utf-8, and a PDF fetched through the same call comes back as base64.
Branch on encoding, because the wrong guess fails quietly. Base64-decoding a utf-8 payload does not raise; it skips every character that is not valid base64 and hands you a shorter blob. A 9,438,000 byte CSV decodes that way into 6,259,500 bytes of noise, which reads like a truncated download rather than a decoding mistake.
1. Ask for a URL
Ten tools support this today. Each takes the argument at input.return_download_url, nested one level, because each declares a single input model. That is more consistent than the upload side, where the depth varies per tool.
Four of them set the flag and hand back a URL, with nothing else to watch for: docusign__get_document, docusign__download_all_documents, onedrive__download_item, and onedrive_gcc__download_item.
The other six change behavior in a way worth knowing before you call them:
Google Drive spells the argument returnDownloadUrl in its schema and accepts either spelling. The rest use return_download_url.
Where a Gmail attachment ID comes from
gmail__get_attachment takes an attachment_id that you read off the message part, and Gmail payloads return that field as attachmentId in camelCase while the fields beside it (part_id, mime_type, label_ids) stay snake_case. Reading attachment_id gives you nothing, and the call then fails on an ID you never picked up.
A draft is readable the same way a sent message is. Pass the message_id that list_drafts and get_draft return, not the draft_id.
Agent Handler fetches the file from the connector, stores it, and puts a download block in the tool result in place of the content:
As on the upload side, the Authorization header here is a placeholder. The URL alone is not enough to fetch the file: a request needs the token in the URL and your Access Key, so a leaked URL on its own is useless.
2. Fetch the file
cURL
Python
The response streams, and carries Content-Type, Content-Length, and Content-Disposition from the stored file.
Two extras are worth knowing about. The endpoint accepts a Range header, so you can resume an interrupted download or read part of a large file:
That returns 206 with a Content-Range header, or 416 if the range does not fit the file. And a HEAD request to the same URL returns the size, type and filename with no body, which is enough to decide whether you want the file at all.
Unlike a file reference, a download URL is reusable. Fetch it as many times as you like within the hour.
Errors worth handling
These are the failures your code can do something about. The three endpoints return standard HTTP statuses; a failure inside a tool call comes back as a tool error carrying error_reason and error_code.
A reserved slot whose bytes never arrive is not an error until you use it. It expires quietly after an hour and is cleaned up.
Next
Read the request and response schemas for both endpoints in the Files API reference.