---
title: "Upload bytes to the relay so they can be pushed to a machine"
method: POST
path: "/api/v1/files"
---

# Upload bytes to the relay so they can be pushed to a machine

`POST /api/v1/files`

## Request

- Base URL: `https://aicommander.dev`
- URL: `https://aicommander.dev/api/v1/files`
- Auth: HTTP bearer

PRO ONLY: Free and anonymous callers cannot upload. Stores the raw FILE body (`--data-binary @localfile`, not JSON) as a temporary blob for /api/v1/push. Limit 100 MiB; access ends 24 hours after upload even after downgrade, followed by retrying cleanup. Content-Length is mandatory: missing returns 411 before reading, a declared length over the cap returns 413, and a body whose actual length differs returns 400 with nothing stored. Allowed uploads are limited to 60/hour/account, 5000/hour relay-wide, and share a 5 GiB/account/rolling-day storage budget with pulls. Over quota returns 429 `rate_limited`.

## Response `200`

Stored.

- object
  - `ok` true, required
  - `blobId` string, required
  - `bytes` integer, required
  - `expiresAt` string, date-time, required
  - `maxBytes` integer: The relay's per-blob limit, in bytes.

## Other responses

- `400`: The body did not deliver the number of bytes its Content-Length declared. Nothing was stored — retry the transfer.
- `401`: A token was supplied but is not a valid API key/admin/OAuth token.
- `403`: Resolved but refused. Causes include expired anonymous access; Free/anonymous file transfer; `reason: plan_device_limit`; independent operator block/approval; or `error: reactivation_required`. For plan_device_limit, delete an older saved machine to promote the next or upgrade to Pro. For file transfer, use Pro. Do not conflate either with operator approval.
- `411`: The request carried no Content-Length. This endpoint checks the request itself for one and refuses without it, before any body is read: the relay stores a blob at exactly its declared length and will not take a stream of unknown size. Send the file itself (`--data-binary @file`) instead of piping it into a chunked request.
- `413`: The declared length exceeds the 100 MiB per-blob limit; refused before any bytes are read.
- `429`: The relay upload quota (`reason: "rate_limited"`). Keyed by ACCOUNT, not by IP: 60 uploads an hour per account, 5000 an hour across the whole relay, and 5 GiB of relay storage per account per rolling day — a budget shared with pulls, which park bytes in exactly the same place, but not with pushes, which only read bytes back out. The daily figure is a hard ceiling on any rolling 24-hour period, counted in hourly steps, so bytes stay counted for up to 25 hours rather than exactly 24 and a spent allowance frees hour by hour. Both windows roll forward, so a short pause clears the per-hour limit; for a genuinely large artifact have the job copy it to your own storage instead — the relay is a courier, not a drive.
- `503`: The session exists but the agent is offline or disconnected mid-command.

---

[API](https://skmtc.dev/aicommander/apis/ai-commander-remote-shell-and-long-running-jobs-api.md) · [All operations](https://skmtc.dev/aicommander/apis/ai-commander-remote-shell-and-long-running-jobs-api/llms.txt) · [OpenAPI document](https://skmtc.dev/aicommander/apis/ai-commander-remote-shell-and-long-running-jobs-api/revisions/a6bb89f779a1?raw)
