# Neon Functions

A [Neon Function](https://neon.com/docs/compute/functions/overview) runs
JavaScript or TypeScript on Node.js 24 and gets its branch's `DATABASE_URL`
automatically. Use seekrit for credentials Neon does not supply, such as a
third-party API key. The function holds one `SEEKRIT_TOKEN`; `@seekrit/sdk`
fetches ciphertext and **decrypts inside the function** when a request needs a
secret. You do not need to copy every provider key into Neon's function settings.

This guide makes a small function that reads `OPENAI_API_KEY` from seekrit and
calls the [OpenAI Models
API](https://developers.openai.com/api/reference/resources/models/methods/list).
It also checks a separate caller token before resolving anything: [Neon Function URLs are
public](https://neon.com/docs/compute/functions/authentication).

## 1. Prepare a seekrit environment

Start with an [app and environment](/docs/quickstart). Add the provider key to
that environment, then mint a [service token](/docs/guides/service-tokens)
bound to it. For an existing `api/production` environment:

```bash
read -r -s OPENAI_API_KEY # paste your key, then press Enter
seekrit secrets set OPENAI_API_KEY "$OPENAI_API_KEY" --app api --env production
unset OPENAI_API_KEY
seekrit token create --name neon-function --app api --env production
# Save the skt_… value shown once.
```

You can also set the key and mint the token in the dashboard. A service token
can decrypt **the entire environment** it is bound to, including composed
groups. Give the function a narrow environment if it should only read a few
keys.

## 2. Define the function

Install the [Neon CLI](https://neon.com/docs/cli) and dependencies in your
function project, then link it to a Neon project in a [supported
region](https://neon.com/docs/compute/functions/overview):

```bash
npm install -g neon@latest
npm install @neon/config @seekrit/sdk
npm install --save-dev @types/node
neon link --no-env-pull
```

Create `neon.ts`. Neon's `env` values are captured at deploy time from the
shell running `neon deploy`. Keep the two token values out of this file and
out of version control:

```ts
// neon.ts
import { defineConfig } from "@neon/config/v1";

const seekritToken = process.env.SEEKRIT_TOKEN;
const functionAccessToken = process.env.FUNCTION_ACCESS_TOKEN;
if (!seekritToken || !functionAccessToken) {
  throw new Error("Set SEEKRIT_TOKEN and FUNCTION_ACCESS_TOKEN before deploying");
}

export default defineConfig({
  functions: {
    openaimodels: {
      name: "OpenAI models",
      source: "./functions/models.ts",
      env: {
        SEEKRIT_TOKEN: seekritToken,
        FUNCTION_ACCESS_TOKEN: functionAccessToken,
      },
    },
  },
});
```

`FUNCTION_ACCESS_TOKEN` authenticates **callers of your function**. It is
different from `SEEKRIT_TOKEN`, which lets the function decrypt its seekrit
environment. Never send `SEEKRIT_TOKEN` to a caller. For a user-facing app,
replace this simple caller token with [JWT
verification](https://neon.com/docs/compute/functions/authentication#verify-a-jwt).

Create the handler:

```ts
// functions/models.ts
import { timingSafeEqual } from "node:crypto";
import { Seekrit } from "@seekrit/sdk";

const seekrit = new Seekrit(); // reads SEEKRIT_TOKEN from the function environment
const encoder = new TextEncoder();

function authorized(request: Request): boolean {
  const supplied = request.headers.get("authorization")?.match(/^Bearer (.+)$/i)?.[1];
  const expected = process.env.FUNCTION_ACCESS_TOKEN;
  if (!supplied || !expected) return false;

  const a = encoder.encode(supplied);
  const b = encoder.encode(expected);
  return a.length === b.length && timingSafeEqual(a, b);
}

export default {
  async fetch(request: Request): Promise<Response> {
    if (request.method !== "GET") {
      return new Response("Method not allowed", { status: 405 });
    }
    if (!authorized(request)) {
      return new Response("Unauthorized", { status: 401 });
    }

    let apiKey: string | undefined;
    try {
      apiKey = await seekrit.get("OPENAI_API_KEY");
    } catch {
      return new Response("Secret unavailable", { status: 503 });
    }
    if (!apiKey) {
      return new Response("OPENAI_API_KEY is missing", { status: 503 });
    }

    let upstream: Response;
    try {
      upstream = await fetch("https://api.openai.com/v1/models", {
        headers: { authorization: `Bearer ${apiKey}` },
      });
    } catch {
      return new Response("Provider unavailable", { status: 502 });
    }
    if (!upstream.ok) {
      return new Response("Provider request failed", { status: 502 });
    }

    const result = (await upstream.json()) as { data: unknown[] };
    return Response.json({ models: result.data.length });
  },
};
```

The `Seekrit` client is reused across requests on one function instance, but
`get()` resolves on each call. A changed provider key is available on the next
request without redeploying the function. Resolution fails closed; the handler
returns an error rather than continuing with an empty key.

## 3. Run and deploy

Set the one-time service token in the shell used for development and deployment.
The first command below accepts it without echoing it or putting it in shell
history. Make a separate random token for callers:

```bash
read -r -s SEEKRIT_TOKEN
export SEEKRIT_TOKEN
export FUNCTION_ACCESS_TOKEN="$(openssl rand -hex 32)"
neon dev
```

Save `FUNCTION_ACCESS_TOKEN` for the caller in your usual secret store before
closing this shell. It is needed to invoke the deployed function and is not a
seekrit credential.

`neon dev` prints the local function URL. You can call it with the same
authorization header shown below while the dev server runs. Stop the dev
server, then run `neon deploy` from the shell holding both tokens. Retrieve
the function's public `invocation_url`:

```bash
neon deploy
neon functions get openaimodels -o yaml
# Copy the invocation_url from the output above:
FUNCTION_URL="https://..."
curl -H "Authorization: Bearer $FUNCTION_ACCESS_TOKEN" "$FUNCTION_URL"
```

The response is a count such as `{"models":42}`; neither token nor the
provider key is returned. For more about configuration and deployment, see
[Neon's function setup](https://neon.com/docs/compute/functions/get-started)
and [environment variable docs](https://neon.com/docs/compute/functions/environment-variables).

## Branches and rotation

Neon gives each branch its own function and database connection. A seekrit
service token still points to the **seekrit environment chosen when that token
was minted**. A Neon preview branch does not automatically switch it to a
seekrit preview environment. Deploy each branch with a token bound to the
matching seekrit environment, especially before exposing a preview URL.

Neon injects `DATABASE_URL` from the function's branch. Read that directly if
your function uses Postgres; do not copy a production database URL into this
seekrit environment for the function to use. Neon also injects credentials for
its own enabled services, such as AI Gateway. Use seekrit for credentials you
manage separately.

Changing `OPENAI_API_KEY` in seekrit takes effect on the next `get()` call. A
revoked service token fails on the next resolution. To replace it, put a newly
minted `SEEKRIT_TOKEN` in Neon's function environment and redeploy: Neon stores
a deployment snapshot of user-defined variables.
