Invoking functions
Call your deployed functions.
Call your deployed functions.
Choose an invocation mode
Every function has an invocation contract:
rpc(default) preserves the existing Volcano contract. The DNS endpoint and direct API endpoint acceptPOST, the request body is{ "payload": ... }, and Volcano authentication is required.httpturns the DNS endpoint into an HTTP handler. It acceptsGET,HEAD,POST,PUT,PATCH, andDELETEon nested paths and passes the HTTP request shape to your function.
HTTP mode has a separate http_auth_mode:
volcano(default) requires a service key, or an auth-user token or permitted anon key that the function's visibility admits.noneskips Volcano authentication so third-party webhooks can reach the function. It is valid only whenvisibilityispublic; your function must verify the provider's signature or application credential.
Configure an existing function with the management API:
curl -X PATCH "https://api.volcano.dev/projects/$PROJECT_ID/functions/$FUNC_ID" \
-H "Authorization: Bearer $PLATFORM_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"visibility": "public",
"invocation_mode": "http",
"http_auth_mode": "none",
"openapi_spec": {
"openapi": "3.1.0",
"info": {"title": "Payment webhook", "version": "1.0.0"},
"paths": {"/webhooks/payments": {"post": {}}}
}
}'openapi_spec is optional descriptive metadata. Volcano validates that it is
an OpenAPI 3.0 or 3.1 JSON document and limits it to 256 KiB; it does not use the
document to route or validate runtime requests.
Roll back HTTP mode
Invocation metadata is control-plane configuration, so switching back does not
redeploy or replace the function runtime. Patch the function back to rpc; Volcano
atomically restores http_auth_mode: volcano and clears openapi_spec. Moving
the function off public at the same time closes anon-key access as well.
Remove any Frontend Function routes attached to the function first. While a
route targets it, the function must stay public and in HTTP mode, and this
update answers 409. Then patch it:
curl -X PATCH "https://api.volcano.dev/projects/$PROJECT_ID/functions/$FUNC_ID" \
-H "Authorization: Bearer $PLATFORM_TOKEN" \
-H "Content-Type: application/json" \
-d '{"visibility":"authenticated","invocation_mode":"rpc"}'After the update, the DNS endpoint again accepts only POST / with the
{"payload": ...} RPC envelope and Volcano authentication. Existing RPC
functions require no migration action: a missing metadata row resolves to the
same RPC/Volcano defaults.
Mount an HTTP Function on a Frontend path
A frontend can send a path prefix, such as /api/session, to a public
HTTP-mode function, so your web app calls it on its own origin and can keep
sessions in cookies. See Frontend Function routes.
Two ways to consume an RPC function
You can invoke a function in either of these ways:
- DNS endpoint (recommended): the function's
invoke_url
Geo-routed, and the only way to reach the function on its own domain. - Invoke endpoint:
POST https://api.volcano.dev/functions/{functionId}/invoke
Useful when you want direct invocation on the API host.
Functions answer on their own domain, separate from the API. Never build the
invocation host yourself — the endpoint differs between deployments, and a
host derived from your API URL will not reach the function. Read invoke_url
from the function or from GET /functions/resolve:
INVOKE_URL=$(curl -s "https://api.volcano.dev/projects/$PROJECT_ID/functions/$FUNC_ID" \
-H "Authorization: Bearer $PLATFORM_TOKEN" | jq -r '.invoke_url')Every example below invokes $INVOKE_URL.
Example DNS call:
curl -X POST "$INVOKE_URL" \
-H "Authorization: Bearer $SERVICE_KEY" \
-H "Content-Type: application/json" \
-d '{"payload":{"action":"process","data":"value"}}'Invocation methods by token type
Which tokens a function accepts depends on its
visibility. New functions are
private: only service keys and schedulers can invoke them until you set
authenticated or public.
In local mode, a request with no Authorization header is treated as the local
service key, so it reaches every function. Send a user's access token or an
anon key to see the refusals your app will get in the cloud.
With service key (admin/background)
For admin operations, background jobs, cron, and webhooks:
curl -X POST "$INVOKE_URL" \
-H "Authorization: Bearer $SERVICE_KEY" \
-H "Content-Type: application/json" \
-d '{"payload":{"action":"process","data":"value"}}'Warning: Service keys bypass RLS. Use only in backend environments.
Function receives:
{
"action": "process",
"data": "value"
// No __volcano_auth (admin operation)
}HTTP mode request event
HTTP-mode functions receive this event instead of the RPC payload:
{
"version": "1.0",
"__volcano_http": true,
"method": "POST",
"path": "/webhooks/payments",
"raw_query_string": "attempt=one&attempt=two",
"headers": {
"Content-Type": ["application/json"],
"Provider-Signature": ["signature"]
},
"query": {
"attempt": ["one", "two"]
},
"body": "{\"event\":\"payment.succeeded\"}",
"is_base64_encoded": false,
"request_context": {
"host": "FUNCTION_ID.functions.volcano.run",
"scheme": "https",
"source_ip": "203.0.113.10",
"protocol": "HTTP/1.1"
}
}Header and query values are arrays so repeated values are preserved. For text
requests, body contains the exact UTF-8 request text. Binary requests put the
base64-encoded bytes in body and set is_base64_encoded: true. HTTP request
bodies are limited to 2 MiB. Volcano
also rejects a request if headers, query parameters, body encoding, and trusted
auth context make the final invocation event exceed 6 MiB.
__volcano_http: true is a server-owned marker that identifies this as an
HTTP-ingress event, so a function can distinguish trusted request_context
metadata from a caller-shaped RPC payload. Volcano removes a caller-supplied
__volcano_http field from synchronous and durable RPC payloads.
In http_auth_mode: volcano, platform credential headers are removed before
the event reaches the function; authenticated-user context remains available as
event.__volcano_auth. In http_auth_mode: none, Authorization and provider
signature headers are application input and are forwarded. Internal
X-Volcano-* headers are never forwarded.
Proxy-derived headers (Forwarded, X-Forwarded-For, X-Forwarded-Host,
X-Forwarded-Port, X-Forwarded-Proto, and X-Real-IP) are also removed. So
are lookalikes of these and of X-Volcano-* that put another character, such
as _ or ., in place of -: frameworks that map headers to CGI-style
variables read X_Forwarded_For as X-Forwarded-For. Other names with _ or
., such as X_Api_Key, are forwarded. Use
request_context.source_ip, request_context.host,
request_context.scheme, and request_context.protocol for Volcano's trusted
connection metadata. A request that reaches the function through a frontend's
Function route carries the visitor's address in source_ip too.
Webhook example:
import crypto from 'node:crypto';
exports.handler = async (event) => {
const signature = event.headers['Provider-Signature']?.[0] || '';
const rawBody = Buffer.from(event.body || '', event.is_base64_encoded ? 'base64' : 'utf8');
const expected = crypto.createHmac('sha256', process.env.WEBHOOK_SECRET).update(rawBody).digest('hex');
const supplied = Buffer.from(signature);
const calculated = Buffer.from(expected);
if (supplied.length !== calculated.length || !crypto.timingSafeEqual(supplied, calculated)) {
return { statusCode: 401, body: 'invalid signature' };
}
return {
statusCode: 202,
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ accepted: true }),
};
};Reject stale provider timestamps before comparing the signature, and persist the
provider event ID before applying side effects so retries are idempotent. Compare
signatures with timingSafeEqual as above. Convert event.body according to
event.is_base64_encoded for the exact signed bytes; do not parse and
reserialize it first.
With auth user token (user context)
For user-facing operations. The function must be authenticated or public;
a private function answers 404, as if it didn't exist.
curl -X POST "$INVOKE_URL" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"payload":{"action":"get_profile"}}'Function receives:
{
"action": "get_profile",
"__volcano_auth": {
"user_id": "uuid",
"email": "user@example.com",
"project_id": "project-uuid",
"role": "authenticated", // or "anonymous" for anonymous users
"access_token": "eyJhbGci..." // the caller's access token, e.g. to forward to the Volcano SDK/API
}
}With anon key (public functions only)
Use this only for explicitly public functions:
curl -X POST "$INVOKE_URL" \
-H "Authorization: Bearer $ANON_KEY" \
-H "Content-Type: application/json" \
-d '{"payload":{"action":"public_ping"}}'Requirements:
- Function
visibilitymust bepublic - Anon key must include
functions.invokepermission
Security note: Treat a
publicfunction as internet-facing. An anon key ships in browser code, a Frontend Function route forwards requests with no credential, andhttp+noneserves its DNS endpoint without a token.
Everything on this page is about standard functions. A durable function is not invocable through any of these: it answers 404 on this endpoint and on a function URL, whatever its visibility, because it runs as an execution you start and then poll.
Function receives:
{
"action": "public_ping"
// No __volcano_auth for anon key invocation
}Payload format
Send any JSON:
{
"action": "create_post",
"title": "My Post",
"content": "Content here",
"tags": ["tech", "coding"],
"metadata": {
"source": "web"
}
}Function receives this payload, plus __volcano_auth if you call it with an
auth token. The top-level __volcano_auth and __volcano_http keys are
reserved: Volcano removes caller-supplied values for both, so an RPC payload
never carries the HTTP-ingress marker described in HTTP mode request event.
Response format
// Volcano now forwards your function response directly.
// HTTP status code, headers, and body come from the function return value.Every invocation response says which build and region answered:
X-Volcano-Version:<version>inproduction,<env>-<version>elsewhere (example:staging-xyz)X-Volcano-Region: the region your function ran in (example:us-east-1)
When Volcano dispatched to your function, it also reports:
X-Volcano-Proxy-Ms: milliseconds Volcano spent preparing the call, counted from the moment your request arrived until your function was dispatched. Covers authenticating the caller, validating the request, resolving the function, and quota. Does not include function execution.X-Volcano-Proxy-Handler-Ms: the part ofX-Volcano-Proxy-Msspent in the invoke endpoint itself. Subtract it fromX-Volcano-Proxy-Msto see what authentication and request validation cost.X-Volcano-Compute-Ms: milliseconds spent running the function, from dispatch until it returned. Does not include proxy preparation. A streamed response sends its headers before the function finishes, so there it is the time until the response started.
X-Volcano-Proxy-Ms: 9
X-Volcano-Proxy-Handler-Ms: 2
X-Volcano-Compute-Ms: 41Here the call waited 9 ms on Volcano, 7 ms of it before the invoke endpoint was reached, and 41 ms on the function.
X-Volcano-Function-Invoked: true appears only once your function has actually
run. Use it to tell your function's own 404 from Volcano's: a 404 without it
means Volcano had no such function to call, so a client caching a function's id
can discard that id and look the name up again. A 404 with it came from your
code, and repeating the call would run your code twice. Do not read
X-Volcano-Version for this — it is stamped on every response, including errors
raised before your function is reached.
X-Volcano-Version, X-Volcano-Region, X-Volcano-Function-Invoked,
X-Volcano-Proxy-Ms, X-Volcano-Proxy-Handler-Ms, X-Volcano-Compute-Ms, and
X-Volcano-Health are set by
the platform: a response header your function returns under one of those names
is dropped rather than forwarded, as are Volcano's own internal headers. Every
other header you return is forwarded as written.
Handling responses
Successful response
// In function
return {
statusCode: 200,
body: JSON.stringify({ result: 'success' }),
};For a binary response, base64 encode body and return
isBase64Encoded: true. HEAD executes the function with method: "HEAD" but
Volcano suppresses the response body.
{
"result": "success"
}Error response
// In function
return {
statusCode: 404,
body: JSON.stringify({ error: 'Not found' }),
};{
"error": "Not found"
}Function errors
If your function throws an error:
{
"errorMessage": "Something went wrong"
}Stream a response
An HTTP-mode Node.js function can send its response while it is still running: server-sent events, model tokens, progress lines. Volcano forwards each chunk as your function writes it, on the function's URL and on Frontend Function routes.
Export config = { supportsResponseStreaming: true } from the handler module
and return a Response whose body is a stream:
// index.mjs
export const config = { supportsResponseStreaming: true };
export const handler = async () => {
const encoder = new TextEncoder();
const body = new ReadableStream({
async start(controller) {
for (let tick = 1; tick <= 5; tick++) {
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ tick })}\n\n`));
await new Promise((resolve) => setTimeout(resolve, 1000));
}
controller.enqueue(encoder.encode('event: done\ndata: {}\n\n'));
controller.close();
},
});
return new Response(body, {
headers: { 'content-type': 'text/event-stream', 'cache-control': 'no-cache' },
});
};The usual response object works too. body can be any async iterable of
strings or bytes, such as an async generator or a Node.js Readable:
// index.js
exports.config = { supportsResponseStreaming: true };
exports.handler = async () => {
async function* lines() {
yield 'working\n';
await new Promise((resolve) => setTimeout(resolve, 2000));
yield 'done\n';
}
return { statusCode: 200, headers: { 'content-type': 'text/plain' }, body: lines() };
};The function must use invocation_mode: http; see
Choose an invocation mode. Status, headers, and
cookies are sent before the first chunk, so set them all in the returned
response. A handler that does not return a stream answers normally.
Read the stream with curl -N, which prints each event as it arrives. This
function is public with http_auth_mode: none; in volcano auth mode, add
the Authorization header as for any HTTP-mode call:
curl -N "$INVOKE_URL/events"data: {"tick":1}
data: {"tick":2}
...
event: done
data: {}From Node.js:
const response = await fetch(`${process.env.INVOKE_URL}/events`);
const decoder = new TextDecoder();
for await (const chunk of response.body) {
process.stdout.write(decoder.decode(chunk, { stream: true }));
}From Python:
import os
import requests
with requests.get(f"{os.environ['INVOKE_URL']}/events", stream=True, timeout=(5, 60)) as response:
response.raise_for_status()
for line in response.iter_lines(decode_unicode=True):
if line.startswith("data: "):
print(line.removeprefix("data: "), flush=True)A browser can read the same events with new EventSource(url) when the
function needs no Authorization header, for example behind a Frontend
Function route.
Streaming limits
- Availability on the function's URL. Check the
functions.response_streamingcapability inGET /capabilities. Where it isunavailable, the function's URL still answers, with the same status, headers, cookies, and body, but sends the whole response once the function finishes. A response sent whole is limited to 6 MiB, headers included, and a larger one fails the invocation. Frontend Function routes andvolcano startstream either way. - Time limit. The whole response counts toward the function timeout:
30 s on HOBBY and 180 s on SUPERAGENT. See
Plans and limits. At the limit
Volcano stops the function and closes the connection without ending the
response, so the client reports an incomplete transfer. End the stream with
a final event, like
doneabove, so a client can tell a finished stream from a cut one. - Gaps between chunks. Within the time limit, the connection stays open however long your function waits between chunks, on the function's URL and behind a Frontend Function route.
- Errors. A function that throws before it returns a response gets a
500. One that fails after the first chunk has its connection closed the same way as at the time limit. HEAD. The function runs, and Volcano drops the body.- Opt-in. Returning a stream without the
configexport fails the invocation with a500, and the function's logs show an error that names the export. A callback-style handler,(event, context, callback), cannot opt in. - Python and Ruby. These runtimes return the whole response at once. Volcano sends it when the function returns.
- RPC mode. RPC invocations, including
volcano.functions.invokein the SDKs, always return one complete JSON result. A function that returns a stream to an RPC call fails witha streamed response needs the function's invocation_mode set to http. - Local mode.
volcano startstreams Node.js functions the same way.
Accessing user context in functions
Check if user is authenticated
exports.handler = async (event) => {
// Check if any user (anonymous or authenticated)
if (!event.__volcano_auth) {
return {
statusCode: 401,
body: JSON.stringify({ error: 'Unauthorized' }),
};
}
const { user_id, email, role } = event.__volcano_auth;
// Proceed with authenticated operation
// All users have a user_id for data tracking
};Differentiate between user types
exports.handler = async (event) => {
const auth = event.__volcano_auth;
if (!auth) {
// No authentication - reject or handle as public
return { statusCode: 401, body: 'Unauthorized' };
}
if (auth.role === 'anonymous') {
// Guest user - maybe prompt to create account
return {
statusCode: 200,
body: JSON.stringify({
data: result,
message: 'Create an account to save your data permanently',
}),
};
}
if (auth.role === 'authenticated') {
// Full registered user
return {
statusCode: 200,
body: JSON.stringify({ data: result }),
};
}
};Available user context fields
When invoked with an auth user token, functions receive:
event.__volcano_auth = {
user_id: 'uuid', // Unique user ID (same for anonymous → authenticated conversion)
email: 'user@example.com', // User's email (or internal format for anonymous)
project_id: 'uuid', // Project the user belongs to
role: 'authenticated', // "authenticated" or "anonymous"
access_token: 'eyJhbGci...', // The caller's access token
};Role values:
"authenticated"- User signed up with email/password or OAuth"anonymous"- Guest user (signed up without credentials)
Important: Both user types have a user_id - use this to track user data. When an anonymous user converts to authenticated, their user_id stays the same, preserving all their data.
Using frontend SDK
A signed-in client can invoke authenticated and public functions. Set the
level in volcano-config.yaml for every function your pages call:
# volcano-config.yaml
version: 1
functions:
- name: get-data
visibility: authenticatedconst volcano = new VolcanoAuth({...});
// Sign in first
await volcano.auth.signIn({...});
// Invoke function (access token sent automatically)
const result = await volcano.functions.invoke('get-data', {
action: 'get_data'
});
console.log(result.status); // HTTP status returned by function
console.log(result.version); // X-Volcano-Version
console.log(result.data); // Parsed response bodyError handling
| Status | Description |
|---|---|
| 401 Unauthorized | Missing Authorization header, invalid token, or token for wrong project |
| 403 Forbidden | Anon key without functions.invoke: anon key does not have functions.invoke permission |
| 403 Forbidden | Anon key invoking an authenticated function by id: function is not public; anon keys can only invoke public functions |
| 404 Not Found | Function doesn't exist in your project; is private and the caller isn't a service key; or an anon key calls a non-public function by name through an SDK |
| 503 Service Unavailable | Function still provisioning; check status and retry |
An SDK reports these 404s as a not-found error, and may keep doing so for
about 30 seconds after you widen a function's visibility. See
Choose who can invoke it.
What's next
| Guide | Description |
|---|---|
| User context in functions | Complete guide to accessing user information |
| Creating functions | Deploy new functions |
| Function logs | View execution logs |
| Anonymous users | Guest user authentication |