Uploads
Task, session, and message create take a prompt plus an optional inputs array. Each input is a named piece of content the agent reads as a file in the sandbox. An input carries its content one of three ways:
text: UTF-8 text inline in the request.content_base64: the same content as base64, for callers that would rather not embed raw text in JSON.upload_id: a reference to a file you sent to storage ahead of time.
Small material travels inline as text or content_base64. Larger material cannot: an API request has a transport size limit well below what a single input may hold, and a request over it fails before the API can tell you why. An upload moves the bytes off that path. You declare the file, send it straight to storage over a URL Wallfacer signs for exactly those bytes, mark it ready, then name the upload where the content would have gone.
Uploads hold UTF-8 text, up to 33,554,432 bytes (32 MiB) per file. They are for material too large to send inline, such as a long log or a large document the agent should read.
The Upload Flow
Three steps, then reference the upload as an input.
1. Declare The File
POST /v1/accounts/{account}/uploads with the file's name, exact byte size, and SHA-256 in lowercase hex. The response reserves a location and returns a signed upload_url and the upload_headers you must send with it.
BYTES=$(wc -c < server-log.txt | tr -d ' ')
SHA=$(sha256sum server-log.txt | cut -d' ' -f1)
curl -s -X POST "$WALLFACER_API/accounts/$ACCOUNT_ID/uploads" \
-H "Authorization: Bearer $WALLFACER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "server-log.txt",
"byte_size": '$BYTES',
"sha256": "'$SHA'"
}' | jq ".data"name is the filename the agent reads; use letters, digits, dot, dash, and underscore, with no path separators. media_type is optional (any text/* type, or one of application/json, application/x-ndjson, application/xml, application/yaml; it defaults to text/plain). The response status is pending, and expires_at is 30 minutes out.
2. Send The Bytes
PUT the file to the returned upload_url, carrying every header from upload_headers exactly as returned. Those headers commit the request to the size and digest you declared, so storage refuses a body of a different length or different contents where it lands rather than accepting it and rejecting it later.
curl -s -X PUT "$UPLOAD_URL" \
-H "Content-Type: text/plain" \
-H "x-amz-...: <value from upload_headers>" \
--data-binary @server-log.txtupload_url and upload_headers are returned only when the upload is created. Reading the upload later does not repeat them.
3. Mark It Ready
PATCH /v1/accounts/{account}/uploads/{upload} with {"status": "ready"}. The stored file is checked against the size and digest you declared and confirmed to be valid UTF-8 text, then moved somewhere the upload URL can no longer reach. Only a ready upload can back an input.
curl -s -X PATCH "$WALLFACER_API/accounts/$ACCOUNT_ID/uploads/$UPLOAD_ID" \
-H "Authorization: Bearer $WALLFACER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"status": "ready"}' | jq ".data"The request is safe to repeat: an upload that is already ready returns its current state unchanged.
Attach An Upload To Work
Pass the upload as an input on task, session, or message create. The instruction stays ordinary prompt text; the upload is material the agent opens with its file tools, not the instruction.
curl -s -X POST "$WALLFACER_API/accounts/$ACCOUNT_ID/tasks/$TASK_ID/sessions/$SESSION_ID/messages" \
-H "Authorization: Bearer $WALLFACER_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: msg-attach-log-001" \
-d '{
"message": "Diagnose the crash using the attached log.",
"inputs": [{"upload_id": "'$UPLOAD_ID'"}]
}' | jq ".data"When an input carries an upload_id, its name, size, media type, and SHA-256 come from the upload as they were verified at upload time; omit name on that input. An input still provides exactly one of text, content_base64, or upload_id.
Upload States
| Status | Meaning |
|---|---|
pending | The location is reserved. The file has not arrived or has not been checked yet. |
finalizing | The file is being checked and moved right now. Transient, and never seen in a successful response. |
ready | The file arrived, matched the size and digest you declared, and can be used as an input. |
claimed | The file has been used as an input and this upload is finished. |
An upload backs exactly one input. Once used it becomes claimed and cannot be reused. Unused uploads expire at expires_at, 30 minutes after creation, and are removed along with their file.
Read And Remove Uploads
GET /v1/accounts/{account}/uploadslists the account's uploads, most recent first. Expired uploads drop off the list once their file is removed.GET /v1/accounts/{account}/uploads/{upload}returns one upload's current state.DELETE /v1/accounts/{account}/uploads/{upload}removes an upload and anything sent to it. Deleting an upload that is already gone also succeeds. An upload that has been used as an input cannot be deleted this way; the file belongs to that session now.
Errors
| Code | When |
|---|---|
uploads_unavailable | The deployment has no storage that accepts direct uploads. Send smaller material inline as text or content_base64 instead. |
upload_checksum_mismatch | The stored file does not match the SHA-256 you declared. The size and encoding checks report the same way when the file is the wrong length or is not valid UTF-8. |
upload_already_claimed | The upload has already backed an input. Each upload can be used once. |
upload_finalize_in_progress | Another request is checking the same upload right now. |
From The CLI
The wallfacer CLI mirrors these endpoints as an uploads command group (list, create, get, update, delete), where update marks an upload ready. To attach one, include inputs with an upload_id in the create request body for a task, session, or message.