Function Upload API

Upload a function as a ZIP or tar.gz archive; the API normalizes it to tar.gz and starts the cloud build.

Endpoint

POST /projects/{project_id}/functions

Upload a function as a ZIP or tar.gz source archive. The API accepts either format, injects build metadata, and stores a normalized tar.gz source archive before starting the cloud build.

Request Format

Content-Type: multipart/form-data

Form Fields

FieldTypeRequiredDescription
namestringYesFunction name (unique within project)
codefile (binary)YesZIP or tar.gz archive containing function source code plus dependency manifests/lockfiles
runtimestringYesRuntime environment
handlerstringNoFunction name to invoke (default: handler)

Supported Runtimes

  • nodejs22.x
  • nodejs24.x
  • python3.10
  • python3.11
  • python3.12
  • python3.13
  • python3.14
  • ruby3.3
  • ruby3.4
  • ruby4.0

python3.10 is deprecated: Python 3.10 reached end of life in October 2026. It still deploys, and Volcano will remove it before 2027-08-31. Move functions to a newer Python runtime.

Examples

cURL

curl -X POST https://api.volcano.dev/projects/PROJECT_ID/functions \
  -H "Authorization: Bearer $PLATFORM_TOKEN" \
  -F "name=my-api" \
  -F "code=@function.zip" \
  -F "runtime=nodejs24.x" \
  -F "handler=handler"

JavaScript (fetch)

const formData = new FormData();
formData.append('name', 'my-api');
formData.append('code', fileInput.files[0]); // File from <input type="file">
formData.append('runtime', 'nodejs24.x');
formData.append('handler', 'handler');

const response = await fetch(`https://api.volcano.dev/projects/${projectId}/functions`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${token}`,
  },
  body: formData,
});

const result = await response.json();
console.log('Function created:', result);

JavaScript (Node.js with form-data)

const FormData = require('form-data');
const fs = require('fs');

const form = new FormData();
form.append('name', 'my-api');
form.append('code', fs.createReadStream('./function.zip'));
form.append('runtime', 'nodejs24.x');
form.append('handler', 'handler');

const response = await fetch(`https://api.volcano.dev/projects/${projectId}/functions`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${token}`,
    ...form.getHeaders(),
  },
  body: form,
});

Python (requests)

import requests

files = {
    'name': (None, 'my-api'),
    'code': ('function.tar.gz', open('function.tar.gz', 'rb'), 'application/gzip'),
    'runtime': (None, 'python3.12'),
    'handler': (None, 'lambda_handler')
}

response = requests.post(
    f'https://api.volcano.dev/projects/{project_id}/functions',
    headers={'Authorization': f'Bearer {token}'},
    files=files
)

print(response.json())

Go

package main

import (
	"bytes"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
)

func uploadFunction(projectID, token string) error {
	// Create multipart form
	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)

	// Add form fields
	writer.WriteField("name", "my-api")
	writer.WriteField("runtime", "nodejs24.x")
	writer.WriteField("handler", "handler")

	// Add file
	file, _ := os.Open("function.zip")
	defer file.Close()

	part, _ := writer.CreateFormFile("code", "function.zip")
	io.Copy(part, file)
	writer.Close()

	// Make request
	url := fmt.Sprintf("https://api.volcano.dev/projects/%s/functions", projectID)
	req, _ := http.NewRequest("POST", url, body)
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", writer.FormDataContentType())

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	return nil
}

You may upload either ZIP or tar.gz source archives. The Volcano CLI always uploads tar.gz, but direct API clients can use either format.

Response

Success (201 Created)

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "project_id": "123e4567-e89b-12d3-a456-426614174000",
  "name": "my-api",
  "status": "provisioning",
  "runtime": "nodejs24.x",
  "handler": "handler",
  "created_at": "2024-01-05T10:30:00Z",
  "updated_at": "2024-01-05T10:30:00Z"
}

Error (400 Bad Request)

{
  "error": "function code too large (max 256 MB, got 312475648 bytes from file 'function.zip')"
}

Size Limits

Function source archives are limited to 256 MB. The API enforces this limit both before storage and after ZIP-to-tar.gz normalization. The CLI does not enforce it locally.

After upload, the cloud compile build installs dependencies from manifests such as package.json, requirements.txt, and Gemfile. The publish build then rejects a final function image larger than 4096 MB.

Recommendations:

Package SizeStatus
< 1MBExcellent
1-10MBGood
10-50MBLarge (consider optimization)
Above the configured upload limitRejected

File Requirements

  1. Must be a ZIP or tar.gz file - Other formats rejected
  2. Include the handler's module - usually index.js, index.mjs or index.cjs for Node.js, main.py for Python, or main.rb for Ruby, exporting the submitted handler function. The runtime can also load it from an installed dependency or a compiled module. If the runtime can't find it, the compile build fails, unless a project variable such as NODE_PATH, PYTHONPATH or RUBYLIB adds a directory it loads modules from. Upload compiled JavaScript: Volcano doesn't compile TypeScript.
  3. Include dependency manifests/lockfiles - Do not include installed dependency directories for cloud deploys
  4. No symlink entries - Uploaded source archives cannot contain symlinks. Safe symlinks created during the cloud build, such as package-manager workspace links, are materialized before publish.
  5. No top-level .volcano-dependencies path - Volcano reserves it for dependency staging. An upload containing it is accepted asynchronously, then fails during the compile build.
  6. Valid archive structure - The archive must be readable by Volcano
  7. No parent-directory entries - Archive paths such as .., ../, and src/../.. are rejected.

Direct Upload Layout Inference

When you upload directly to the API, Volcano generates .volcano/function-build.json automatically. You do not need to create this file.

Volcano infers build roots deterministically:

  • function_root is the directory containing the single runtime entry file when exactly one is found: index.js/index.mjs for Node.js, main.py for Python, or main.rb for Ruby.
  • If there is no root-level file and all files are under one top-level directory, that top-level directory becomes function_root.
  • Otherwise, function_root is the archive root (.).
  • install_root prefers the closest dependency manifest directory containing the runtime handler module, resolved relative to function_root.
  • When function_root is . and no manifest contains the handler module, the fallback selects the longest nested manifest directory path by character count; equal-length ties choose the lexicographically first directory. With no matching manifest or applicable fallback, install_root is function_root.

Volcano prefixes the supplied handler with index. for Node.js or main. for Python and Ruby. For Node.js and Ruby, the module runs up to the first dot after the last /; the rest names the export, or Ruby's Class.method. For Python, the final dot separates the module from the function. For example, the Node.js handler src/handler.main becomes index.src/handler.main, so its module file is index.src/handler.js, relative to function_root. Python module dots become path separators; Node.js and Ruby preserve module directory dots.

When a root-level dependency manifest exists, unrelated nested manifests do not select the install root. For a root-level index.js and package.json, Volcano installs from the archive root even when the bundle also contains examples/other-package/package.json.

Recommended direct-upload layouts:

# Simple function
index.js
package.json
package-lock.json

# Nested function
apps/api/index.js
apps/api/package.json

# Monorepo function with root install
package.json
package-lock.json
apps/api/index.js

Common Errors

"code file is required"

You forgot to include the code field:

# Bad - missing code
curl -F "name=my-func" ...

# Good - includes code
curl -F "name=my-func" -F "code=@function.zip" ...

"code must be a valid .tar.gz or .zip archive"

The file you uploaded isn't a supported archive:

# Check file type
file function.zip
# Should output: "Zip archive data"

"function code too large"

Your source archive exceeds the 256 MB limit. Common solutions:

  1. Remove unnecessary files
  2. Bundle and minify application code
  3. Exclude installed dependencies, dependency caches, and development-only files

Function image too large

A deployment fails after its build when the built function, with its installed dependencies, is larger than 4096 MB. Remove unnecessary runtime files, reduce dependency size, or split the function into smaller functions.

Updating a Function

Use the same endpoint with the same function name:

# First deployment
curl -F "name=my-api" -F "code=@v1.zip" ...

# Update (same name, new code)
curl -F "name=my-api" -F "code=@v2.zip" ...

Returns 200 OK for updates (not 201 Created).

Batch Deployments

POST /projects/{project_id}/functions/batch accepts multiple ZIP or tar.gz source archives in one multipart request. Each function still gets its own compile/publish workflow.

A single batch request can include up to 100 functions. Use multiple batch requests for larger projects.

If one function fails before its workflow starts, successful functions are left running. Failed new functions are deleted, and failed updates are rolled back to their previous metadata/status where possible. Partial failures are returned in the response failed array.

Benefits of Binary Upload

No base64 overhead - 33% more efficient Faster uploads - Direct binary transfer Simpler - Standard file upload Works with standard tooling - Any client that can send multipart form data can upload functions Browser compatible - Works with <input type="file">

On this page