# Set up and ship something tiny (Module 0)

> Get the plumbing working before the learning starts: run the notebooks, make one model call with a spend cap on it, keep your key out of your code, and put a working function on a public URL.

Source: https://goeveryway.com/resources/ai-expert-track/00-setup/ · Published 2026-11-11 · Last verified 2026-11-11 · Everyway

Most people who stall in a course like this don't stall on the ideas. They stall on the plumbing: a package that won't install, a key that won't work, a bill they didn't expect, a function that runs on their laptop and nowhere else.

So before any machine learning, get the plumbing working once. By the end of this module you'll have run the course notebooks, made a model call with two spend caps on it, and put a tiny function on a public URL. Every module after this one builds on those three things.

## The idea

### What you need, and what it costs

You need three things, and only the first is required to start.

1. **Somewhere to run Python.** Google Colab in a browser, or Python 3.11 to 3.13 on your own machine. Either works for every module.
2. **The starter kit.** It holds the data, the notebooks and `lib/llm.py`, the small client every module uses to call a model. Modules 1 to 4 are classic machine learning on your laptop and never call a model.
3. **A model provider account, when you want real model calls.** Until you add a key, the kit runs in [mock mode](#mock-mode), which costs nothing and runs every notebook. You'll want a real key by Module 5, when embeddings start to matter.

For the deploy at the end of this module you also need an account on a hosting platform. The worked example uses Vercel; check its current plan limits before you rely on them.

What it costs is your own number, and the kit is built so you can see it. Every call is logged with its tokens and cost. The worked provider in this course is OpenRouter, which puts many providers' models behind one key. It runs on prepaid credits, so you top up first and spend down. Some of its models have free variants, with IDs ending in `:free`, that come with daily request caps. Prices change too often to print here; your log is the source of truth.

### Mock mode

Every module in this course runs without a key. With no `LLM_API_KEY` set (or with `LLM_PROVIDER=mock`), `lib/llm.py` answers every call itself instead of sending it to a provider:

- **A chat request** gets a canned reply that starts with `[mock reply]`.
- **A request for structured output** gets JSON that matches the schema you asked for, with placeholder text in every field.
- **A request that offers tools** gets a call to the first tool, with arguments that fit its schema.
- **An embedding request** gets a word-hash vector: texts that share words end up close together, and nothing about meaning is learned.

Every mock call is logged like a real one, with a cost of $0 and rough token counts.

What mock mode proves: your code runs end to end, the pieces connect, and the checks you wrote catch what they should. What it can't tell you: how good a model's answers are, how much they vary from run to run, how long real calls take, or what they cost. Wherever a module quotes a number produced in mock mode, it says so. When you add a key, rerun the same notebook and compare.

### Colab or your own machine

**Colab** needs nothing installed. Each module page has an "Open the notebook in Colab" button, and the notebook's first cell fetches the kit and its packages. The trade-off is that Colab sessions reset. Files you create there vanish when the session ends, unless you save them somewhere.

**Your own machine** takes about 15 minutes to set up: clone the kit, make a virtual environment, install the requirements, copy `.env.example` to `.env`. The kit's README has the exact commands. After that, everything persists, and you can open the code in whatever editor you like.

If you're unsure, start in Colab. You can move later without losing anything that matters, because the kit is the same in both places.

### Your key, and two spend caps

Create a key at your provider, then put two caps on it before you make a single real call.

**The provider cap.** On OpenRouter you can give each key a credit limit in dollars, with an optional daily, weekly or monthly reset. Once the key hits it, requests fail until the window resets or you raise the limit. This is the cap that matters most, because it holds no matter what code is calling: yours, a bug in yours, or someone who got hold of the key.

**The kit cap.** Set `LLM_DAILY_BUDGET_USD` in `.env` (or in Colab's Secrets panel). Before every call, `lib/llm.py` adds up today's logged spend and refuses to call once it reaches the budget. It's a seatbelt for honest mistakes, like a loop that runs a thousand times instead of ten. It only sees calls that go through it, which is exactly why the provider cap still matters.

### Keep the key out of your code

A key is a password that spends money. Treat it that way.

<figure class="not-prose my-8">
<svg viewBox="0 0 640 300" role="img" aria-labelledby="m0-fig-title m0-fig-desc" class="w-full" style="font-family: inherit">
  <title id="m0-fig-title">Where your API key lives</title>
  <desc id="m0-fig-desc">Three safe places hold the key: a .env file on your laptop that git ignores, the Secrets panel in Colab, and a sensitive environment variable on Vercel that only the deployed function reads. All three send the key to the model provider. The unsafe places, a notebook cell, a git commit, a screenshot or a chat message, sit in a red box marked never here.</desc>
  <g font-size="13" fill="#1c1f2e">
    <rect x="20" y="20" width="200" height="56" rx="10" fill="#f3f0ff" stroke="#8b5cf6"/>
    <text x="120" y="44" text-anchor="middle" font-weight="600">Laptop</text>
    <text x="120" y="63" text-anchor="middle" font-size="12">.env (git ignores it)</text>
    <rect x="20" y="96" width="200" height="56" rx="10" fill="#f3f0ff" stroke="#8b5cf6"/>
    <text x="120" y="120" text-anchor="middle" font-weight="600">Colab</text>
    <text x="120" y="139" text-anchor="middle" font-size="12">Secrets panel</text>
    <rect x="20" y="172" width="200" height="56" rx="10" fill="#f3f0ff" stroke="#8b5cf6"/>
    <text x="120" y="196" text-anchor="middle" font-weight="600">Vercel</text>
    <text x="120" y="215" text-anchor="middle" font-size="12">env var, marked sensitive</text>
    <rect x="270" y="172" width="130" height="56" rx="10" fill="#ecfeff" stroke="#22d3ee"/>
    <text x="335" y="196" text-anchor="middle" font-weight="600">Your function</text>
    <text x="335" y="215" text-anchor="middle" font-size="12">reads it at runtime</text>
    <rect x="450" y="96" width="170" height="56" rx="10" fill="#1c1f2e"/>
    <text x="535" y="120" text-anchor="middle" font-weight="600" fill="#ffffff">Model provider</text>
    <text x="535" y="139" text-anchor="middle" font-size="12" fill="#d9dce6">checks key, applies cap</text>
  </g>
  <g stroke="#8b5cf6" stroke-width="2" fill="none">
    <path d="M220 48 C 340 48, 380 110, 448 118"/>
    <path d="M220 124 L 448 124"/>
    <path d="M220 200 L 268 200"/>
    <path d="M400 200 C 430 200, 430 140, 448 132"/>
  </g>
  <g font-size="12" fill="#4b5068">
    <rect x="20" y="248" width="600" height="40" rx="10" fill="#fff5f5" stroke="#f87171" stroke-dasharray="4 3"/>
    <text x="320" y="273" text-anchor="middle" fill="#b91c1c"><tspan font-weight="600">Never here:</tspan> a notebook cell · a git commit · a screenshot · a chat message</text>
  </g>
</svg>
<figcaption class="mt-2 text-sm text-graphite-500">The key lives in exactly one place per environment, and every one of them is outside the code.</figcaption>
</figure>

The kit's `.gitignore` already lists `.env`, and the Module 0 notebook checks two things for you: that git ignores `.env`, and that your key appears in no file git tracks.

If a key leaks anyway, the order matters. **Revoke it at the provider first**, then create a new one, then clean up. Deleting the commit doesn't help: the key is still in the history, and you can't know who copied it while it was visible. Assume a leaked key is in use until you've killed it.

### Use an AI coding assistant, and check its work

You'll write a lot of code in this course, and an AI coding assistant will speed that up a lot. Use one. Ask it to write a cell, then ask it to explain the cell line by line. If the explanation doesn't make sense to you, you don't understand the code yet, and that's where the bugs will be.

Four habits keep it honest:

- **Run it before you trust it.** Code that looks right and code that runs are different things. The notebooks' done-when checks exist for this.
- **Check anything time-sensitive against the source.** Assistants are confident about model names, API parameters and setup steps that changed months ago. For those, the vendor's current docs win.
- **Try it on a small slice first.** Ten rows, one call. Then scale up.
- **Never paste your key into it.** Paste the error message, not the `.env` file.

### Ship one function

Most of what you'll build in this course has to run somewhere other than your laptop eventually. So ship something tiny now, while the stakes are low.

The steps are the same on every platform:

1. Write a function that reads its secrets from environment variables, never from the code.
2. List its dependencies in a file the platform understands.
3. Set the secrets on the host, in the platform's own settings.
4. Deploy, and get a public URL.
5. Call the URL and check what comes back.
6. Limit what a caller can do, because anyone who finds the URL can call it, and every call spends your money.

The kit's `deploy/app.py` is that function. It asks a model one fixed question, caps the reply at 60 tokens and returns JSON. With no key it answers in mock mode, so you can run it on your machine before it ever touches the internet.

### Worked example: Vercel

We checked these steps against Vercel's docs on November 11, 2026. Hosting platforms change their setup often. If a step doesn't match what you see, Vercel's [Python runtime docs](https://vercel.com/docs/functions/runtimes/python) win.

Vercel's Python runtime looks for an app named `app` in a file such as `app.py`, and reads dependencies from `requirements.txt`. The kit's `deploy/` folder already has both, with Flask as the only dependency. The default Python version is 3.12.

```bash
npm i -g vercel              # the Vercel CLI; needs Node.js
vercel login
cd deploy
vercel                       # creates the project; a new project's first deploy is production
vercel env add LLM_API_KEY --environment production --sensitive
vercel env add LLM_MODEL --environment production
vercel --prod                # redeploy, so the new variables apply
curl https://<your-project>.vercel.app/
```

Two details trip people up.

**Environment variables only apply to new deployments.** The running deployment keeps the values it was built with, which is why there's a second deploy after adding them.

**Use the short production URL.** Vercel's Standard Protection, available on every plan, protects every deployment URL except production domains. Depending on how your project is set up, the long per-deployment URL the CLI prints may ask for a Vercel login. The short `<your-project>.vercel.app` address is the one to call.

If everything worked, `curl` prints JSON with a one-sentence reply, the model that wrote it, the tokens used and the cost.

## Why it matters for revenue

Every system later in this course, from lead scoring to drafting outreach, has to run on a schedule or behind a URL, without your laptop open. The first time you do that shouldn't be the first time a client is watching.

Cost visibility matters just as much. "What does this cost per lead?" is the first question a finance person asks about an AI system, and the answer has to come from a log, not a guess. You start keeping that log today.

## Where people go wrong

- **A key in a notebook cell, pushed to GitHub.** It's the most common leak, and it happens to careful people. Keys go in `.env` or Secrets, and the notebook checks for you.
- **Only a code-side cap.** A budget check in your code can't stop a bug that skips it or a leaked key. Set the provider's limit first.
- **Testing only on the deployed URL.** When it fails there, you can't tell whether the code, the settings or the platform is at fault. Run the function locally first; the notebook does exactly that.
- **Forgetting to redeploy.** You change a variable, nothing changes, and you start debugging code that's fine.
- **Letting the caller choose the prompt.** A public endpoint that takes any prompt and any length is a free model for whoever finds it, on your bill.

## Build this

You start with the kit and the notebook `00-set-up-and-ship-something-tiny`. The function is already written in `deploy/`. Your job is the setup around it.

1. Open the notebook (the Colab button at the top of this page, or `jupyter notebook` locally) and run every cell. In mock mode it works with no key.
2. Create a key at your provider and give it a credit limit. Put it in `.env` as `LLM_API_KEY`, with `LLM_MODEL` set to a model ID from your provider's list. In Colab, use the Secrets panel instead.
3. Add `LLM_DAILY_BUDGET_USD=1.00` (or any number you're comfortable with) the same way, then set `provider_cap_set = True` in the notebook.
4. Rerun from the top. The call cell should now show a real reply, and the log line should show real tokens and a cost.
5. Run `python deploy/test_app.py` in the kit folder. All checks should pass without a network connection.
6. Deploy with the Vercel steps above, setting `LLM_API_KEY` and `LLM_MODEL` on the host.
7. Paste your production URL into `DEPLOYED_URL` and run the last two cells. Fix anything marked with a cross.
