Supabase-native

The AI gateway built into your Edge Functions.

No odnoga package to install. Verify the Supabase JWT and forward its subject as one header and every request is metered, billed and traced per end user — without writing the metering layer yourself.

supabase/functions/_shared/odnoga.ts
const BASE = "https://api.odnoga.com/functions/v1/airouter-openai-compat/v1";

import { createClient } from "jsr:@supabase/supabase-js@2";

export async function odnoga(req: Request) {
  const jwt = (req.headers.get("authorization") ?? "").split(" ")[1];
  // Verify the token — a decoded-but-unverified subject can be forged, and the
  // spend would land on someone else's account.
  const supabase = createClient(
    Deno.env.get("SUPABASE_URL")!,
    Deno.env.get("SUPABASE_ANON_KEY")!,
  );
  const endUser = jwt ? (await supabase.auth.getUser(jwt)).data.user?.id : undefined;

  return (path: string, body: unknown) =>
    fetch(BASE + path, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        Authorization: `Bearer ${Deno.env.get("ODNOGA_API_KEY")}`,
        ...(endUser ? { "x-odnoga-end-user": endUser } : {}),
      },
      body: JSON.stringify(body),
    });
}

// supabase/functions/chat/index.ts
Deno.serve(async (req) => {
  const ai = await odnoga(req);
  const r = await ai("/chat/completions", {
    model: "auto",
    messages: [{ role: "user", content: "Hi" }],
  });
  return new Response(r.body, { headers: { "content-type": "application/json" } });
});

Set one secret: supabase secrets set ODNOGA_API_KEY=sk_live_xxx

What you get

Built for the Supabase stack

Copy-paste helper

One small file in _shared/. No package, no build step, no version to keep up with.

end_user from JWT

Decode auth.uid() from the caller's Authorization header and send it as x-odnoga-end-user. Every request is metered per user.

RAG over pgvector

Embeddings + chat in two calls; supabase.rpc("match_documents") in between. No glue code.

Edge function traceability

Every call is a row: tenant, end-user, prompt version, model, cost, latency.

One secret to set

supabase secrets set ODNOGA_API_KEY=… and you're live.

Streaming, images, embeddings

Same OpenAI-compatible endpoints for every modality — /chat/completions, /embeddings, /images/generations.

Recipe

RAG over pgvector, end to end

const ai = await odnoga(req);

const { data: emb } = await ai("/embeddings", {
  model: "text-embedding-3-small",
  input: question,
}).then((r) => r.json());

const { data: docs } = await supabase.rpc("match_documents", {
  query_embedding: emb[0].embedding,
  match_count: 5,
});

const r = await ai("/chat/completions", {
  model: "gpt-4o-mini",
  messages: [
    { role: "system", content: `Use:\n${docs.map(d => d.content).join("\n---\n")}` },
    { role: "user", content: question },
  ],
});
FAQ

Frequently asked

Is this an official Supabase project?

No — we're an independent gateway. The integration is a plain HTTP call from your Edge Function, so nothing in your project depends on us shipping a package.

Is there an npm or JSR package?

A published SDK is in progress. Today the integration is deliberately package-free: an OpenAI-compatible HTTP endpoint plus one header. Any OpenAI SDK works by pointing base_url at odnoga.

What about row-level security?

odnoga never touches your tables. It records the end-user id you send to attribute cost; your Supabase client + RLS continue to govern what data the user sees.

Can I use it from the browser (eut tokens)?

Yes. Mint a short-lived eut_ token from your edge function via POST /v1/sessions, pass it to the browser, and use it as the bearer.

Ship AI from your edge functions today.