This page is not an argument against base64 — data URIs are a real part of the web, and some APIs are built around them. It is an argument about what an agent should do when it has a real file and an API that needs it. That moment comes for every media-heavy workflow: the agent has written the caption and rendered the thumbnail, and now the bytes have to travel. How they travel decides whether the workflow scales past the demo.
It is the same pattern agencies hit with browser automation and with client file work generally: the workaround that made the demo effortless becomes the reason the workflow can’t run unattended.
Why agents end up base64-encoding files
Because it is the path of least resistance. The agent already speaks JSON, many APIs accept a
data:image/png;base64,… string wherever
they accept a URL, and no extra infrastructure is needed: read the file, encode it, inline it, send.
For a one-off task with a small image, that is a perfectly reasonable answer — which is exactly why it
spreads into workflows it was never sized for.
The measured cost of base64-in-JSON
In a real internal Outloop workflow test, the same media workflow ran both ways against the same social publishing API. The base64 path behaved exactly as the encoding math says it must:
- →Every upload is a third bigger. Base64 turns 3 bytes of file into 4 bytes of text. The two images uploaded this way produced payloads measured at 33.3% above the source file size — before the JSON around them.
- →The agent does the heavy lifting. It reads the whole file, holds it in memory, encodes it, and builds a multi-megabyte request body — CPU and memory cost paid inside the agent runtime, on every attempt.
- →The file content goes everywhere the request goes. The complete bytes sit inside the agent-built payload — which means agent memory, request logs, tool traces, and any debugging output that captures the body.
One honest boundary on that last point: this is a content
surface, not a credential leak. In the same test, both paths kept the API credential fully hidden —
every call returned secret_exposed:false.
The question is not whether base64 leaks your key (it doesn’t); it is how many places a client’s
file content gets copied into on the way to the API.
Where it breaks at scale
The failure mode is structural, not bad luck. A base64 upload puts the entire file — raw and encoded — inside one JSON body, so it inherits every limit that applies to request bodies: maximum payload sizes, memory that grows with the file, timeouts on long sends. There is no streaming and no resume; a retry means re-encoding and re-sending everything. Thumbnails survive this. Finished videos, long-form renders, and client deliverables do not — and those are precisely the files a media workflow exists to move.
The dedicated upload path: a reference in, a receipt out
Outloop’s media upload path inverts the shape. Instead of pushing bytes through the agent, the agent sends a file reference — path, file name, MIME type — for a file inside an approved workspace or media root:
- →Policy answers before bytes move. Upload is an explicit capability that can be checked up front: it comes included with full API access, is denied on read-only grants, and can be switched off without disconnecting the credential.
- →A local broker moves the bytes host-side. The file never gets re-serialized into the agent’s request, and the credential is used on the wire, host-side — the agent never sees it.
- →The result is an upload-shaped receipt, not a generic response. Redacted, tagged as an upload, and carrying the fields you actually want in an audit trail — upload protocol, provider host, duration — with
secret_exposed:falseon the record.
That last property matters most for unattended work. When a 3am scheduled run fails, a purpose-built receipt tells you which stage broke; a generic API response buried among every other write does not.
What happens when the agent requests a media upload
- 01
Agent request
The agent asks for an approved action or alias — not a raw key.
- 02
Policy & tenant check
Outloop checks project, tenant identity, and runtime policy before anything runs.
- 03
Local broker
On approval, the local broker uses the credential on the wire to perform the call.
- 04
Redacted result
The agent receives a sanitized, non-secret result. Raw values never enter its context.
- 05
Audit log
Every attempt is written to a redacted local audit — decision, tenant, service.
The agent never sees the credential. A wrong-tenant request is denied at the policy check, before any backend call.
Side by side
| Dimension | Base64 data URI | Dedicated media upload |
|---|---|---|
| Payload size | +33% on every file — measured on real image uploads | No inflation — request size is independent of file size |
| Encoding work | The agent encodes the full file into text on every attempt | None — the broker reads the approved file host-side |
| What crosses the agent boundary | The complete file content, inside the JSON body the agent builds | A file reference: path, name, and MIME type |
| Large files | Body-size limits, memory that grows with the file, retries that re-encode everything | Streamed host-side; memory does not grow with file size (product testing) |
| Capability preflight | None — it rides the generic API write | The upload capability can be checked before any bytes move |
| Result and audit | A generic API response, indistinguishable from any other write | An upload-tagged, redacted receipt: protocol, provider host, duration |
| Credential exposure | Hidden — secret_exposed:false | Hidden — secret_exposed:false |
What was verified — and what wasn’t
Same honesty rule as our Drive workflow rebuild: say what was tested, and how.
- ✓Real internal workflow test: the same images uploaded both ways to the same social publishing API. Base64 payloads measured at exactly one-third above source size; the dedicated upload completed with a redacted, upload-tagged receipt; the capability preflight answered before any bytes moved; both paths returned
secret_exposed:false. - ✓Product testing: a real upload of roughly 100 MB completed through the dedicated path, and streaming tests with multi-gigabyte test fixtures confirmed that memory use does not grow with file size. The streaming test is an engineering test of the transport — not a real multi-gigabyte provider upload — and provider-side size limits still apply independently.
- ○Not claimed: no controlled speed benchmark was run between the two paths, and one session is not a reliability statistic. The efficiency case rests on the measured payload math and the architecture, and this page keeps it there.
When base64 is still the right call
- →Small files — an icon or a tiny thumbnail, where a third more payload is noise.
- →APIs designed for data URIs — when inline content is the documented interface, use the documented interface.
- →One-off tasks — where the file is small, the run is attended, and nothing needs to scale.
What base64 should never be is a silent fallback. If the dedicated upload capability is denied or an upload fails, that denial is a policy answer about what this workspace may do — the agent’s job is to report it, not to route the same bytes around it through a generic write. That discipline is what keeps an audit trail meaning something.