# Calling functions from your app

> Call SupaHost Edge Functions with supabase-js functions.invoke, fetch, curl and other clients, pass bodies and headers, call sub-paths and handle errors.

Source: https://supahost.app/docs/functions/calling-functions · Updated 2026-11-11

A deployed function is an HTTPS endpoint, so anything that can send a request can call it. supabase-js wraps that in `functions.invoke`, which adds the right headers for you.

Every function URL has the form:

```
https://app-one.acme.supahost.space/functions/v1/<function-name>
```

## With supabase-js

```ts
import { createClient } from "@supabase/supabase-js";

const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!);

const { data, error } = await supabase.functions.invoke("create-invoice", {
  body: { customerId: "c_381", lines: [{ item: "Design work", hours: 6, rate: 90 }] },
});
```

`invoke` sends:

- `Authorization: Bearer <token>`: the signed-in user's access token if there's a session, otherwise the anon key. That's what the **Require a JWT** check looks at.
- `apikey` with the anon key. SupaHost doesn't need it on `/functions/v1/*`, but it does no harm.
- A `Content-Type` that matches the body.

### Bodies, methods and headers

| You pass as `body` | Sent as |
|---|---|
| A plain object or array | JSON (`application/json`) |
| A string | `text/plain` |
| `FormData` | `multipart/form-data` |
| `Blob` / `File` / `ArrayBuffer` | `application/octet-stream` |

The default method is `POST`. Change it, and add your own headers, with the options object:

```ts
const { data, error } = await supabase.functions.invoke("invoices/inv_1042", {
  method: "GET",
  headers: { "x-request-source": "dashboard" },
});
```

Putting a path after the name (`invoices/inv_1042`) calls the same function with a longer path, which is how you build small REST APIs inside one function (see [Writing functions](/docs/functions/writing-functions)). Add any custom header to your function's CORS `Access-Control-Allow-Headers` list.

`data` is parsed from the response's `Content-Type`: an object for `application/json`, a `Blob` for `application/octet-stream`, `FormData` for `multipart/form-data`, the raw `Response` for `text/event-stream`, and text for anything else.

### Handling errors

`error` is one of three classes exported from supabase-js:

| Class | When |
|---|---|
| `FunctionsHttpError` | The function (or the router) answered with a non-2xx status. `error.context` is the `Response`. |
| `FunctionsRelayError` | The request failed between the gateway and the function. |
| `FunctionsFetchError` | The request never got a response (network down, DNS, CORS blocked). |

```ts
import { FunctionsFetchError, FunctionsHttpError, FunctionsRelayError } from "@supabase/supabase-js";

const { data, error } = await supabase.functions.invoke("create-invoice", { body: draft });

if (error instanceof FunctionsHttpError) {
  const code = error.context.headers.get("sb-error-code"); // set on router errors, e.g. UNAUTHORIZED_INVALID_JWT
  const details = await error.context.json().catch(() => null);
  console.error(`Function failed (${error.context.status})`, code, details);
} else if (error instanceof FunctionsRelayError) {
  console.error("Relay error:", error.message);
} else if (error instanceof FunctionsFetchError) {
  console.error("Network or CORS problem:", error.message);
} else {
  console.log("Invoice created", data);
}
```

The `sb-error-code` header tells router errors (`NOT_FOUND`, `UNAUTHORIZED_NO_AUTH_HEADER`, `UNAUTHORIZED_INVALID_JWT`, `BOOT_ERROR`, `WORKER_RESOURCE_LIMIT`, `IDLE_TIMEOUT`, `WORKER_ERROR`) apart from errors your own code returned. See [Logs and debugging](/docs/functions/logs) for what each means.

## With fetch

Use `fetch` when you want a streamed response or don't use supabase-js:

```ts
const { data: { session } } = await supabase.auth.getSession();

const res = await fetch(`${SUPABASE_URL}/functions/v1/export-invoices`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${session?.access_token ?? SUPABASE_ANON_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ month: "2026-10" }),
});

if (!res.ok) throw new Error(`export-invoices failed: ${res.status}`);
const csv = await res.text();
```

## From a terminal

```bash
curl -i -X POST "https://app-one.acme.supahost.space/functions/v1/create-invoice" \
  -H "Authorization: Bearer $SUPABASE_ANON_KEY" \
  -H "Content-Type: application/json" \
  -d '{"customerId":"c_381","lines":[{"item":"Audit","hours":2,"rate":120}]}'
```

`-i` prints the response headers, including `sb-error-code` when the router rejects the call.

## From your server

Server code can call a function as a user (forward their access token) or as your backend. For backend-only functions, send the `service_role` key and check the role inside the function (see [Authentication and JWT verification](/docs/functions/auth)):

```ts
import { createClient } from "@supabase/supabase-js";

// Server only: never ship the service_role key to a browser.
const admin = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!, {
  auth: { persistSession: false },
});

const { data, error } = await admin.functions.invoke("rebuild-reports", { body: { since: "2026-11-01" } });
```

## From another function

Inside a function, create a client with `SUPABASE_URL` and the key you want to call with, then use `functions.invoke` the same way. Chained calls each count toward the 150-second response limit of the outer function, so keep them short.

## From the database

To call a function when a row changes, use a database webhook or `net.http_post` from a trigger: see [Database webhooks and HTTP requests with pg_net](/docs/database/webhooks). For a timer, see [Running functions on a schedule](/docs/functions/scheduling).

## Other client libraries

The Flutter, Swift, Kotlin and Python Supabase clients all have a `functions.invoke` method that works the same way against your SupaHost project URL. See the [JavaScript reference for functions.invoke](https://supabase.com/docs/reference/javascript/functions-invoke) for every option.

## Related

- [CORS for browser calls](/docs/functions/cors)
- [Edge Function returns 401](/kb/function-401)
- [Edge Function returns 404](/kb/function-404)
