> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zap.wzrd.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Supabase Secrets: Encrypted BYOK Provider Key Setup

> Configure Supabase for creator wallet auth and encrypted bring-your-own-key provider storage. Covers the migration, edge functions, and required secrets.

Zap uses Supabase project **`wzrdstudio`** for creator wallet authentication and bring-your-own-key (BYOK) provider secret storage. All creator API keys are encrypted server-side before being written to the database and are revealed in plaintext only during authenticated live runs — never returned to the browser. The `/settings` page lets creators connect their wallet and manage BYOK keys; the `/api/secrets` route proxies all secret operations through the `zap-user-secrets` edge function.

***

## Environment Variables

The web app requires three Supabase variables. Set them in Vercel and in your local `.env.local`:

```bash theme={null}
NEXT_PUBLIC_SUPABASE_URL=https://<project-ref>.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=<publishable key>
# or, for legacy projects:
NEXT_PUBLIC_SUPABASE_ANON_KEY=<anon key>
```

`NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY` is the current Supabase naming convention. `NEXT_PUBLIC_SUPABASE_ANON_KEY` is the equivalent legacy alias — set whichever matches your Supabase dashboard.

***

## Database Migration

Apply the migration at `supabase/migrations/20260703000000_zap_user_secrets.sql` before enabling live BYOK runs.

```bash theme={null}
supabase db push --project-ref <your-project-ref>
```

The migration creates or extends the `user_secrets` table with the full encrypted storage shape:

```sql theme={null}
create table if not exists public.user_secrets (
  user_id uuid not null references auth.users(id) on delete cascade
);

alter table if exists public.user_secrets
  add column if not exists user_id     uuid references auth.users(id) on delete cascade,
  add column if not exists secret_type text,
  add column if not exists ciphertext  text,
  add column if not exists last4       text,
  add column if not exists nonce       text,
  add column if not exists provider    text,
  add column if not exists key_version integer     not null default 1,
  add column if not exists created_at  timestamptz not null default now(),
  add column if not exists updated_at  timestamptz not null default now();
```

It also creates `wallet_auth_users` and `wallet_auth_nonces` tables, unique indexes, and a full set of Row Level Security policies so that each user can only access their own secrets.

<Warning>
  The existing `user_secrets` table (if present from an earlier deployment) and the legacy `manage-user-secrets` edge function are **not aligned** with the encrypted shape above. Apply this migration and redeploy the `zap-user-secrets` edge function before any live BYOK runs depend on Supabase, or encrypted key retrieval will fail.
</Warning>

### Row Level Security

RLS is enabled and forced on `user_secrets`, `wallet_auth_users`, and `wallet_auth_nonces`. The policies enforce:

* `SELECT`, `INSERT`, `UPDATE`, `DELETE` on `user_secrets` are scoped to `auth.uid() = user_id`
* `SELECT` on `wallet_auth_users` is scoped to `auth.uid() = user_id`
* No direct browser access to `wallet_auth_nonces` — nonce records are written and consumed only by edge functions

Only JWT-protected edge functions return masked secret metadata to the browser. Server-side runtime paths may retrieve plaintext keys only for the authenticated owner of the active run, using the `x-zap-server-secret` header alongside the user JWT.

***

## Edge Functions

Deploy both functions with `supabase functions deploy <name>`.

### `zap-user-secrets`

Handles list, save, and delete of encrypted BYOK secrets. Returns masked metadata (e.g. `last4`) to the browser; returns plaintext only to the Zap server.

**Required Supabase secrets:**

```bash theme={null}
USER_SECRETS_ENCRYPTION_KEY=<long random value>
ZAP_SECRET_REVEAL_TOKEN=<long random value — must match Vercel>
```

**Access model:**

| Caller                                        | Allowed operations                                         |
| --------------------------------------------- | ---------------------------------------------------------- |
| Browser (user JWT)                            | List masked secrets, save a new key, delete a key          |
| Zap server (user JWT + `x-zap-server-secret`) | All of the above, plus plaintext key reveal for a live run |

The `ZAP_SECRET_REVEAL_TOKEN` must match the value set in Vercel's environment variables. This shared secret is what restricts plaintext reveals to the Zap server only.

### `zap-wallet-proof`

Handles EIP-191 wallet signature verification and Supabase Auth session minting for wallet-based login.

**Required Supabase secrets:**

```bash theme={null}
ZAP_WALLET_AUTH_SECRET=<long random value>
ZAP_WALLET_TOKEN_TTL_SECONDS=604800
```

**Flow:**

1. Client submits a signed EIP-191 payload and a one-time nonce to `/api/auth/wallet-proof`
2. The Next.js route proxies the request to `zap-wallet-proof` (or the function named in `ZAP_WALLET_PROOF_FUNCTION`)
3. The function verifies the wallet signature, confirms the nonce has not been used, and records it in `wallet_auth_nonces`
4. It creates or reuses a Supabase Auth user mapped in `wallet_auth_users` (keyed by wallet address)
5. Supabase Auth mints a session token, which is returned to the client

`ZAP_WALLET_TOKEN_TTL_SECONDS` controls session lifetime; `604800` equals seven days.

<Note>
  `SUPABASE_URL` and `SUPABASE_SECRET_KEYS` are injected automatically by the Supabase Edge Function runtime — do not set them manually. The legacy `SUPABASE_SERVICE_ROLE_KEY` is also supported if your project uses it.
</Note>

***

## BYOK Secret Types

The following `secret_type` values are supported by the `zap-user-secrets` edge function. Each maps to a provider credential a creator can supply via `/settings`.

| `secret_type`        | Description                                 |
| -------------------- | ------------------------------------------- |
| `gmi_api_key`        | GMI (Generative Media Intelligence) API key |
| `gmi_org_id`         | GMI organisation ID                         |
| `fal_key`            | Fal.ai API key                              |
| `runware_key`        | Runware API key                             |
| `prodia_key`         | Prodia API key                              |
| `openrouter_key`     | OpenRouter API key                          |
| `ai_gateway_api_key` | AI gateway API key                          |

Do not use the legacy `profiles.*_api_key` columns for Zap provider execution. All provider credentials for live runs must go through the encrypted `user_secrets` table and the `zap-user-secrets` edge function.
