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

Terminal
# 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

Terminal
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

javascript
// 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

javascript
// 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

JSON
{
  "success": true,
  "data": {
    "path": "/opt/app/source.tar.gz",
    "size": 1048576,
    "extracted": true
  }
}

Error response example

403 — path forbidden for upload:

JSON
{
  "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, the detectArchiveExt() 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 using COPYFILE_DISABLE=1 tar -czf ... so that the sidecars never end up in the archive.
  • Windows PowerShell ZIP — works on agent ≥ 1.2.3. Compress-Archive on Windows writes a ZIP with literal \ in file names. Since version 1.2.3 the agent automatically normalizes these paths via the normalize_windows_paths step. For older agent versions use tar.gz (via WSL / Git Bash) or host a ready-made tar archive and upload it via url.
  • 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>/.
  • mode is applied to the archive, not to the unpacked files. With extract: true the permissions of the unpacked files are determined by the archive's contents, and mode is set only on the saved archive. If you need to change permissions inside — do it via /exec with chmod.
  • 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 + exec chain.
  • Run a command — after the upload you can unpack/install manually.
  • Service logs — check that the service picked up the new files.