File uploads
Binary uploads use the dedicated endpoint /__rat__/upload, separate from the JSON call path. The browser POSTs a standard multipart/form-data body; the server packs every file part into request.files and invokes the named handler. Bytes ship as base64 so the same payload can cross Tail into Python without UTF-8 corruption.
Handler shape
main.rat
A top-level function takes one argument: the request Object. It exposes request.files (an array of file Objects), request.form (the non-file fields), request.path, and request.method. Files carry name, field, size, content_type, and base64 data.
> save_upload[request]
[file] << request.files[0]
[bytes] << base64_decode(file.data)
<< {name: file.name, size: file.size, ok: true} Wire up the form
Plain multipart — no Rat-specific markup
Use a standard HTML form with enctype="multipart/form-data". The endpoint URL carries the handler name and the page path so middleware can apply route-based gating around the upload.
> page
<form
method['POST']
enctype['multipart/form-data']
action['/__rat__/upload?fn=save_upload&path=/upload-form']
>
<input type['file'] name['photo']>
<button> upload Pass bytes through Tail
Python receives base64-ready bytes
Because request.files[0].data is base64, you can hand it straight to a Python Tail handler. The Python side decodes with base64.b64decode(data) and works with the raw bytes — image processing, OCR, audio transcoding.
# lang/python/vision.py
import base64
def describe(b64):
raw = base64.b64decode(b64)
# ...run image model on raw bytes...
return {"caption": "..."}
# main.rat
> save_upload[request]
[file] << request.files[0]
<< vision.describe(file.data) Next section: Realtime — broadcasting from the server, subscribing from the browser.