
This post is for intermediate builders. You have an app running (maybe built in Lovable on Supabase), you know roughly what a server function is, and you want to add a Grok-powered feature without leaking your API key or burning money.
We will set up a key, put it on the server, call Grok with the OpenAI SDK and build one small feature: turning messy user feedback into a short, tagged summary.
Step 1: Get an API key from the console
- Sign up at console.x.ai.
- Add credits. The API is prepaid, so nothing works until there is a balance.
- Open the API Keys page and create a key. Name it after the app and environment, like
feedback-app-prod, so you know what to revoke later. - Copy the key once and store it somewhere safe. Treat it like a password.
If your team uses the console, set a spending limit while you are there. A runaway loop is a lot cheaper when there is a ceiling.
Step 2: Keep the key on the server
This is the part people get wrong. If your API key is in browser code, anyone can open developer tools, copy it and run up your bill. AI builders sometimes put the key in the front end because it is the fastest way to make a demo work. Do not ship that.
The rule is simple: the browser talks to your server, your server talks to Grok. In a Supabase project, the server is an Edge Function. Store the key as a secret:
supabase secrets set XAI_API_KEY=your_key_here
If you work in Lovable, you can ask it to do this for you. Paste this:
Create a Supabase Edge Function called summarize-feedback.
It reads XAI_API_KEY from the function's environment secrets.
Never put the key in client code or in the repo.
The front end calls this function with the feedback text and
gets back JSON. Require a logged-in user to call it.
Then check the generated code yourself. Search the front end for XAI and api.x.ai. Neither should appear anywhere outside the function.
Step 3: Call Grok with the OpenAI SDK
The xAI API is compatible with the OpenAI SDK. You point the client at xAI's base URL and pass your xAI key. The current xAI docs use the Responses API (client.responses.create). Older tutorials use chat.completions, which xAI now files under legacy, so prefer Responses for new work.
Here is a minimal Edge Function. Supabase Edge Functions run on Deno, which can import npm packages directly:
import OpenAI from "npm:openai";
const client = new OpenAI({
apiKey: Deno.env.get("XAI_API_KEY"),
baseURL: "https://api.x.ai/v1",
});
Deno.serve(async (req) => {
const { feedback } = await req.json();
if (!feedback || feedback.length > 4000) {
return new Response(
JSON.stringify({ error: "Feedback missing or too long" }),
{ status: 400 }
);
}
const response = await client.responses.create({
model: "grok-4.7",
input: [
{
role: "system",
content:
"You summarize customer feedback for a small SaaS team. " +
"Reply with JSON only: {\"summary\": string, " +
"\"tags\": string[], \"sentiment\": \"positive\" | " +
"\"neutral\" | \"negative\"}. Max 3 tags.",
},
{ role: "user", content: feedback },
],
});
return new Response(response.output_text, {
headers: { "Content-Type": "application/json" },
});
});
A few notes on that code:
- The model name.
grok-4.7is the flagship in the xAI docs as I write this. Names change, so check the models page before you deploy. - The length check. Capping input size keeps one user from sending a novel and costing you real money.
- The JSON instruction. Asking for JSON in the system prompt works most of the time. If you need it every time, read the Structured Outputs guide in the xAI docs and validate the result before you save it.
I left out the auth check to keep the example short. In your real function, verify the user's Supabase session before calling Grok. Lovable will usually add this if you asked for a logged-in user in the prompt above.
Step 4: Wire it into the app
Now connect the function to a screen. A prompt for your builder:
On the Feedback page, add a "Summarize" button next to each
feedback item. When clicked, call the summarize-feedback Edge
Function with that item's text. Show a loading state. Save the
returned summary, tags and sentiment to new columns on the
feedback table. If the function returns an error, show a short
message and do not save anything.
Saving the result matters. You pay for every call, so do not call Grok again each time someone opens the page. Summarize once, store it, show the stored version.
Step 5: Add guardrails before real users arrive
Before you open this up, add these:
- Rate limits per user. Something like "no more than 30 summaries per user per hour." Store a counter in a table and check it in the function.
- Logging. Record the user, timestamp and token usage from the response. When the bill surprises you, this is how you find out why.
- A timeout and a friendly failure. Model APIs sometimes slow down. Your UI should say "try again in a minute," not hang.
- A spending limit in the console. If you skipped it in step 1, go back and set it.
Optional: give Grok search
Grok's server-side tools are where it stands out. Adding tools: [{ type: "web_search" }] or tools: [{ type: "x_search" }] to the request lets Grok look things up before it answers. That is useful for a feature like "what are people saying about this product this week."
Two cautions. Tool calls are billed on top of tokens, so check the pricing page. And search makes responses slower and less predictable, so do not add it to a feature that does not need fresh information. Summarizing your own users' feedback does not.
Common problems
- 401 errors. The key is wrong, revoked or not loaded. Log whether
Deno.env.get("XAI_API_KEY")is defined (never log the key itself). - Errors about credits. The account has no balance. Add credits in the console.
- Output that is not valid JSON. Wrap
JSON.parsein a try block, return a clean error, and tighten the prompt or use structured outputs. - It worked locally but not deployed. Secrets set locally are not always set in production. Run the secrets command against the live project.
Where to go next
The recipes include full build specs where an AI call sits behind a server function, which is the same pattern as this post. If you want someone to look over your function before real users hit it, bring it to a cohort.
What would have made it better?