DEV Community

Shivam Dwivedi
Shivam Dwivedi

Posted on Fully Autonomous

Free MiniMax, zero API keys: build a read-only SQL report app

Vercel is serving MiniMax M3.1 Flash for free until 22 October [2]. Free model, short window. So obviously I built something with it. 🛠️

The idea is simple. Let the model write SQL. Never let it run SQL. A small local app takes the query, runs it on read-only sample data, and shows you the report. The app rejected the six disallowed queries I tested below.

No API key in the local app. Generation uses a signed-in Vercel playground; the app itself makes no inference requests.

SQL Postcard showing a MiniMax-generated query for paid totals by category, with three result rows: Books 4300, Games 9000, Tools 10500.

The finished app running a query MiniMax actually wrote. Sample data is made up: twelve fake orders.

What I'm building

Meet SQL Postcard. Two boxes:

  1. Generate: you copy a prompt into Vercel's MiniMax playground and get SQL back.
  2. Check & run: you paste that SQL into the app. It runs on a fresh in-memory SQLite database with twelve fake orders.

That copy-paste step looks old-school, and it's on purpose. The model never touches your data. It only sees the table's shape and the question.

Step 1: Run the app 🧪

You need Python 3 and nothing else. The app only uses the built-in sqlite3 module. I tested it on Python 3.14.7.

Get SQL Postcard on GitHub. Clone it and open the project folder:

git clone https://github.com/panditshivam/sql-postcard.git
cd sql-postcard
python3 verify.py
python3 app.py --port 8775
Enter fullscreen mode Exit fullscreen mode

verify.py checks the saved queries against a plain Python calculation. app.py starts the app at http://127.0.0.1:8775. Neither command calls a model.

Step 2: Get SQL from MiniMax for free

Open the MiniMax M3.1 Flash page on Vercel and click Playground [1]. Sign in with your own account.

Before you send anything, look at the Providers table. You want MiniMax with the green Free tag. That's the only route the promotion covers [2].

Vercel's MiniMax M3.1 Flash playground: the model's JSON answer with three SQL queries, and the Providers table showing MiniMax marked Free for input and output.

My real playground run on 20 October, cropped. Top: the answer. Bottom: the provider row marked Free.

Now, in the app, open Show the exact generation prompt and copy it into the playground. It asks for three reports as JSON:

  1. Paid totals by category.
  2. Paid order count and total by region.
  3. Top two paid categories.

Here's one query MiniMax sent back:

SELECT category, SUM(total_cents) AS total_cents
FROM orders
WHERE status = 'paid'
GROUP BY category
ORDER BY category ASC
Enter fullscreen mode Exit fullscreen mode

Paste one query at a time into the app's SQL box. Only the SQL, without the JSON quotes around it. Hit Run report locally. Done. ✅

The Gateway log reported 362 input tokens and 338 output tokens for that one answer, including 226 reasoning tokens. These are provider-reported usage counters, not an independent tokenizer measurement.

⚠️ Common mistake: assuming "free" means free on every route. Vercel can send a request to other providers, and the promotion only covers MiniMax serving it [2]. In code, pin the provider with only. The order setting is just a preference and can fall back to someone else [3].

Step 3: Try to break it

This is the fun part. Click Try a write query. The box fills with DELETE FROM orders. Run it.

Meme made from the real app screenshot. Top caption: ’Me: pastes DELETE FROM orders’. The app shows ’Query rejected · not authorized’. Bottom caption: ’SQLite authorizer: absolutely not’.

Original meme on my own screenshot. The rejection is real.

How does it say no? SQLite has a lesser-known feature called an authorizer [4]. It's a function SQLite calls for every action a query wants to do. You answer "allowed" or "denied". Mine allows reading the orders table plus a handful of math functions, and nothing else:

functions = {'sum', 'count', 'avg', 'min', 'max', 'round', 'coalesce'}

def authorize(action, first, second, *_):
    if action == sqlite3.SQLITE_SELECT:
        return sqlite3.SQLITE_OK
    if action == sqlite3.SQLITE_READ and first == 'orders':
        return sqlite3.SQLITE_OK
    if action == sqlite3.SQLITE_FUNCTION and (second or '').lower() in functions:
        return sqlite3.SQLITE_OK
    return sqlite3.SQLITE_DENY

db.set_authorizer(authorize)
Enter fullscreen mode Exit fullscreen mode

On top of that: SQLite's query-only mode is on, the database is rebuilt fresh in memory every run, only one statement runs at a time, queries over 8,000 characters are refused, queries exceeding an instruction budget get stopped, and you see at most 100 rows.

I threw six bad queries at it: DELETE, DROP TABLE, a PRAGMA, reading SQLite's internal schema table, a function that isn't on the list, and two statements glued together. All six were rejected. 🛑

Original two-row reaction meme. Scared face: ’Letting the model run SQL straight on your data’. Sunglasses face: ’Model writes it. Read-only SQLite runs it.’

Original meme. Emoji: Twemoji, CC-BY 4.0.

⚠️ Common mistake: checking "does the text start with SELECT?" and calling it safe. It blocks good queries and misses bad ones. This perfectly harmless query starts with WITH:

WITH paid AS (SELECT region FROM orders WHERE status = 'paid') SELECT COUNT(*) AS paid_orders FROM paid

The authorizer looks at what the query does, so it allowed this one and returned 9. A text check would have thrown it out.

Did MiniMax get it right?

All three queries in the one saved model response matched this dataset. I wrote the expected answers before asking the model, then checked each query against those answers and against a separate Python calculation.

  • By category: Books 4300, Games 9000, Tools 10500 (in cents).
  • By region: North 3 paid orders, South 2, West 4.
  • Top two: Tools, then Games.

The traps it avoided: it filtered to paid orders only, and it sorted the way I asked. Those are two easy mistakes a "looks right" query can hide.

Fine print

  • This is one model answer with three queries, on twelve fake rows. Nice demo, not a model ranking.
  • The guardrails fit this little app. A real database with real users needs proper permissions, timeouts and a separate read-only account, not just this callback.
  • The free window is temporary. Check the Providers table before you send anything.

The pattern is the takeaway: model writes, database decides, checker confirms. Try it with your own synthetic schema and questions: a starting point for a local report tool. The model route is free for a couple of days, at least. 😉

References

  1. Vercel AI Gateway: MiniMax M3.1 Flash model page and playground.
  2. Vercel changelog: MiniMax M3.1 Flash free on AI Gateway.
  3. Vercel docs: provider filtering and ordering.
  4. Python 3.14 docs: sqlite3 authorizers, progress handlers and execute.

Asset credits: screenshots are my own captures of the app and the Vercel playground. Memes are original. Emoji graphics: Twemoji, CC-BY 4.0.

AI disclosure

AI was used to draft the article.

Top comments (0)