# 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 accept `POST`, the request body is `{ "payload": ... }`,
  and Volcano authentication is required.
- `http` turns the DNS endpoint into an HTTP handler. It accepts `GET`, `HEAD`,
  `POST`, `PUT`, `PATCH`, and `DELETE` on 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](https://docs.volcano.dev/platform/functions/creating-functions.md#choose-who-can-invoke-it)
  admits.
- `none` skips Volcano authentication so third-party webhooks can reach the
  function. It is valid only when `visibility` is `public`; your function must
  verify the provider's signature or application credential.

Configure an existing function with the management API:

```bash
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:

```bash
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](https://docs.volcano.dev/platform/frontends/function-routes.md).

## Two ways to consume an RPC function

You can invoke a function in either of these ways:

1. **DNS endpoint (recommended):** the function's `invoke_url`  
   Geo-routed, and the only way to reach the function on its own domain.
2. **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`:

```bash
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:

```bash
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](https://docs.volcano.dev/platform/functions/creating-functions.md#choose-who-can-invoke-it). 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:

```bash
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:

```javascript
{
  "action": "process",
  "data": "value"
  // No __volcano_auth (admin operation)
}
```

## HTTP mode request event

HTTP-mode functions receive this event instead of the RPC payload:

```json
{
  "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:

```javascript
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.

```bash
curl -X POST "$INVOKE_URL" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"payload":{"action":"get_profile"}}'
```

Function receives:

```javascript
{
  "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:

```bash
curl -X POST "$INVOKE_URL" \
  -H "Authorization: Bearer $ANON_KEY" \
  -H "Content-Type: application/json" \
  -d '{"payload":{"action":"public_ping"}}'
```

Requirements:

- Function `visibility` must be `public`
- Anon key must include `functions.invoke` permission

> **Security note:** Treat a `public` function as internet-facing. An anon key
> ships in browser code, a [Frontend Function route](https://docs.volcano.dev/platform/frontends/function-routes.md)
> forwards requests with no credential, and `http` + `none` serves its DNS
> endpoint without a token.

Everything on this page is about standard functions. A [durable function](https://docs.volcano.dev/platform/functions/durable-functions.md) 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:

```javascript
{
  "action": "public_ping"
  // No __volcano_auth for anon key invocation
}
```

## Payload format

Send any JSON:

```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](#http-mode-request-event).

## Response format

```json
// 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>` in `production`, `<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 of `X-Volcano-Proxy-Ms` spent in the
  invoke endpoint itself. Subtract it from `X-Volcano-Proxy-Ms` to 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](#stream-a-response) sends its headers before the
  function finishes, so there it is the time until the response started.

```text
X-Volcano-Proxy-Ms: 9
X-Volcano-Proxy-Handler-Ms: 2
X-Volcano-Compute-Ms: 41
```

Here 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

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

```json
{
  "result": "success"
}
```

### Error response

```javascript
// In function
return {
  statusCode: 404,
  body: JSON.stringify({ error: 'Not found' }),
};
```

```json
{
  "error": "Not found"
}
```

### Function errors

If your function throws an error:

```json
{
  "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](https://docs.volcano.dev/platform/frontends/function-routes.md).

Export `config = { supportsResponseStreaming: true }` from the handler module
and return a `Response` whose body is a stream:

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

```javascript
// 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](#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:

```bash
curl -N "$INVOKE_URL/events"
```

```text
data: {"tick":1}

data: {"tick":2}

...

event: done
data: {}
```

From Node.js:

```javascript
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:

```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_streaming` capability in
  [`GET /capabilities`](https://docs.volcano.dev/platform/api-reference/overview.md#capabilities). Where it is
  `unavailable`, 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 and
  `volcano start` stream 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](https://docs.volcano.dev/platform/guides/plans-and-limits.md#functions). 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 `done` above, 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 `config` export fails the
  invocation with a `500`, 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.invoke` in the
  SDKs, always return one complete JSON result. A function that returns a
  stream to an RPC call fails with
  `a streamed response needs the function's invocation_mode set to http`.
- **Local mode.** `volcano start` streams Node.js functions the same way.

## Accessing user context in functions

### Check if user is authenticated

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

```javascript
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:

```javascript
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:

```yaml
# volcano-config.yaml
version: 1
functions:
  - name: get-data
    visibility: authenticated
```

```javascript
const 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 body
```

## Error 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 `404`s 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](https://docs.volcano.dev/platform/functions/creating-functions.md#choose-who-can-invoke-it).

## What's next

| Guide                                                   | Description                                  |
| ------------------------------------------------------- | -------------------------------------------- |
| [User context in functions](https://docs.volcano.dev/platform/functions/user-context.md)            | Complete guide to accessing user information |
| [Creating functions](https://docs.volcano.dev/platform/functions/creating-functions.md)             | Deploy new functions                         |
| [Function logs](https://docs.volcano.dev/platform/functions/logs.md)                                | View execution logs                          |
| [Anonymous users](https://docs.volcano.dev/platform/authentication/anonymous-users.md) | Guest user authentication                    |
