For AI agents: markdown of this page — /docs-content-en/infra/deploy/upload.md documentation index — /llms.txt
Upload a file
POST /v1/infra/servers/:id/upload
Writes a file to a BLACKHOLE server through the tunnel agent. Two source options: inline base64 content in the request body (up to 500 MB), or a URL from which the agent downloads the file itself (up to 500 MB). Automatic unpacking of tar.gz / tar.bz2 / zip archives is supported — the format is detected by the file signature (magic bytes) or by the URL extension.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id |
path | string (UUID) | yes | BLACKHOLE server ID, status: running, blackholeStatus: CONNECTED |
Request fields (body)
| Field | Type | Required | Description |
|---|---|---|---|
path |
string | yes | Path on the server, 1–500 characters. For archives — where to place the uploaded archive before unpacking |
content |
string | yes (or url) |
Base64 content of the file. Maximum 500 MB per request body |
url |
string | yes (or content) |
HTTPS URL from which the agent downloads the file. Up to 500 MB |
mode |
string | no | File permissions in octal format: 0644, 0755. Applied to the file at path |
extract |
boolean | no | Unpack the archive after upload. Defaults to false. Requires content or url with an archive |
extractTo |
string | no | Directory for unpacking (when extract: true). Defaults to the directory from path |
You must pass exactly one of: content or url.
Examples
curl — personal key
# Uploading a file by URL with automatic unpacking
curl -X POST https://vibecode.bitrix24.com/v1/infra/servers/SERVER_ID/upload \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://github.com/user/repo/archive/main.tar.gz",
"path": "/opt/app/source.tar.gz",
"extract": true,
"extractTo": "/opt/app"
}'
# Inline base64 content — a small file (package.json)
CONTENT=$(echo '{"name":"my-app","version":"1.0.0"}' | base64)
curl -X POST https://vibecode.bitrix24.com/v1/infra/servers/SERVER_ID/upload \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"content\":\"$CONTENT\",\"path\":\"/opt/app/package.json\",\"mode\":\"0644\"}"
curl — OAuth application
curl -X POST https://vibecode.bitrix24.com/v1/infra/servers/SERVER_ID/upload \
-H "X-Api-Key: YOUR_APP_KEY" \
-H "Authorization: Bearer USER_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/config.json","path":"/etc/myapp/config.json"}'
JavaScript — personal key
// Uploading and unpacking an archive
const res = await fetch(
`https://vibecode.bitrix24.com/v1/infra/servers/${serverId}/upload`,
{
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: 'https://github.com/user/app/archive/main.tar.gz',
path: '/opt/app/source.tar.gz',
extract: true,
extractTo: '/opt/app',
}),
}
)
const { data } = await res.json()
console.log(`Wrote ${data.size} bytes at ${data.path}${data.extracted ? ', unpacked' : ''}`)
JavaScript — OAuth application
// Inline base64 content — a small config
const content = Buffer.from('NODE_ENV=production\n').toString('base64')
await fetch(
`https://vibecode.bitrix24.com/v1/infra/servers/${serverId}/upload`,
{
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_APP_KEY',
'Authorization': 'Bearer USER_SESSION_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({
content,
path: '/opt/app/.env',
mode: '0600',
}),
}
)
Response fields
| Field | Type | Description |
|---|---|---|
success |
boolean | true on a successful write |
data.path |
string | The final file path on the server |
data.size |
number | Size in bytes |
data.extracted |
boolean | true if the archive was unpacked after upload |
Response example
{
"success": true,
"data": {
"path": "/opt/app/source.tar.gz",
"size": 1048576,
"extracted": true
}
}
Error response example
403 — path forbidden for upload:
{
"success": false,
"error": {
"code": "UPLOAD_PATH_DENIED",
"message": "Upload to system path is not allowed"
}
}
Errors
| HTTP | Code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR |
Schema violation: both content and url at once, or neither; an invalid path; a body larger than 500 MB |
| 400 | NOT_BLACKHOLE |
Server in OPEN mode — the Deploy API is unavailable |
| 401 | MISSING_API_KEY |
The X-Api-Key header was not provided |
| 401 | INVALID_API_KEY |
Invalid or expired API key |
| 403 | UPLOAD_PATH_DENIED |
Uploading to system directories (/root/.ssh, /etc/passwd, /boot, …) is forbidden |
| 404 | NOT_FOUND |
The server does not exist, was deleted, or belongs to another API key |
| 409 | SERVER_NOT_READY |
The server is not ready for the operation: it is not running or the tunnel is not connected. The response carries a hint field with the reason and the next step. Wake the server or call /repair and repeat the request. The "listed as connected but the Gateway has no live tunnel" case on this route arrives as 502 TUNNEL_NOT_FOUND (see below) |
| 429 | RATE_LIMITED |
The limit of 10 operations per minute per server was exceeded |
| 429 | DEPLOY_BACKEND_BUSY |
Too many concurrent uploads that carry content in the request body. The platform caps the number of concurrent /upload and /deploy calls with inline content so that memory is not exhausted. The response carries the Retry-After: 30 header — repeat in 30 seconds. Or pass the archive by link (url): uploads by link have no concurrency cap |
| 502 | UNZIP_PREFLIGHT_FAILED |
Failed to install unzip on the server before extracting the zip archive. To avoid depending on this step, use a .tar.gz archive |
| 500 | agent code | A file write error on the server: out of disk space, permissions, etc. The specific code arrives from the agent in the error.code field |
| 502 | TUNNEL_NOT_FOUND / GATEWAY_UNREACHABLE: … |
No live tunnel to the server, or the Gateway is unreachable — call /repair and retry. GATEWAY_UNREACHABLE carries details after the colon — match error.code by prefix |
| 503 | GATEWAY_TIMEOUT: … |
The Gateway did not respond in time. The code carries details after the colon — compare by prefix |
Full list of common API errors — Errors.
Known specifics
- Automatic archive format detection with
extract: true. The agent detects the format by the file signature (magic bytes, thedetectArchiveExt()function): it supports.tar.gz,.zip,.tar.bz2. The format does not need to be specified explicitly. For a URL — the agent also checks the extension. - macOS archives are cleaned automatically. If the archive contains AppleDouble sidecars (
._*) or.DS_Store, the agent removes them after unpacking — this prevents false positives from scanners like Tailwind v4 oxide. When creating an archive on macOS we recommend usingCOPYFILE_DISABLE=1 tar -czf ...so that the sidecars never end up in the archive. - Windows PowerShell ZIP — works on agent ≥ 1.2.3.
Compress-Archiveon Windows writes a ZIP with literal\in file names. Since version 1.2.3 the agent automatically normalizes these paths via thenormalize_windows_pathsstep. For older agent versions usetar.gz(via WSL / Git Bash) or host a ready-made tar archive and upload it viaurl. - List of forbidden paths. The agent blocks uploads to:
/root/.ssh/(protecting SSH keys),/boot,/etc/shadow,/etc/passwd, system service files. For the application use/opt/<app>/or/var/lib/<app>/. modeis applied to the archive, not to the unpacked files. Withextract: truethe permissions of the unpacked files are determined by the archive's contents, andmodeis set only on the saved archive. If you need to change permissions inside — do it via/execwithchmod.- 10-minute timeout for uploading via
url. That should be enough for 500 MB. If the source is slow, preload the archive and host it on your own CDN.
See also
- Full deploy — in a single request instead of the
upload+execchain. - Run a command — after the upload you can unpack/install manually.
- Service logs — check that the service picked up the new files.