Build Your First AI App: From API Key to Working Demo

Here is a scene I know too well. You leave a meeting, open a file called notes-thursday.txt, and it is a wall of half-sentences: "sync with Priya re: migration — blockers? Ravi said staging ok, deploy freeze next wk, ACTION: nobody??" Ten minutes later you remember nothing. So you paste the whole mess into a chat window, ask for a summary, copy the answer back into the file. It works — once. By the third time, you are doing the same copy-paste dance and thinking: this should be a button.

This post makes it a button. Not a framework, not a platform, not a twelve-step tutorial with a deployed microservice — a single small program that takes a file of meeting notes and returns a crisp summary. By the end you will have a working AI app you built yourself, and more importantly you will understand the one shape that nearly every AI app is built on: messages in, text out.

Ahead of us: getting an API key without doing anything careless, making the first API call with curl, reading the response, doing the same call from Python, adding one real feature (a system prompt that sets the tone), handling failures like an adult, keeping an eye on cost, and three ideas for what to build next. Every code block in this post is real and runnable — the only things you must replace are your key, your model name, and your provider's URL, all marked clearly. Never paste a real API key into code you share; use a placeholder like YOUR_API_KEY and keep reading to see where it actually belongs.

The core shape: messages in, text out

Before we touch anything, let's demystify what an AI app actually is. Strip away the chat UIs, the streaming animations, and the marketing, and the entire industry is one HTTP call:

  • Your app sends JSON containing a list of messages — a conversation so far.
  • The API sends back JSON containing the model's reply — text.
  • Your app does whatever it wants with that text.

Everything else — system prompts, retries, streaming, tools — is decoration around this loop. Learn the loop and you can build with any provider. Here it is as a diagram:

One call, two directions Your app sends + reads API model’s brain Your app uses the text JSON: messages[] JSON: text Request: what happened so far → Response: what the model says next

Hold onto this picture. Whenever a future tutorial throws streaming, function calling, or agents at you, come back here: it is still messages in, text out, with extra machinery.

Step 1: Get an API key — and treat it like a password

Most AI providers offer a hosted API: you sign up, get a secret key, and pay (or use free credits) per call. The steps are generic across providers, because the business model is generic:

  1. Sign up with a provider that offers a chat-style API. Many offer free starter credits, which is all you need for this tutorial.
  2. Go to the provider's dashboard (usually under "API keys", "Developer", or similar) and create a new key. Name it something boring like notes-summarizer so you remember what it is for.
  3. Copy it once and store it somewhere safe. Many dashboards show the full key only once. Treat it exactly like a password: anyone who has it can spend your money.

Now, where does the key live? The rule that will save you from the most common beginner mistake:

  • Never paste the key into source code you share, commit, or screenshot. In examples — including this post — write YOUR_API_KEY.
  • Put it in an environment variable, set on your own machine. Your code reads the variable; the key never touches the file. On macOS/Linux:
export AI_API_KEY="sk-...your real key here..."

(On Windows, use set AI_API_KEY=... in Command Prompt or $env:AI_API_KEY="..." in PowerShell.) Verify it took effect with echo $AI_API_KEY — and this is also the last time the raw key appears in this post. From here on it is YOUR_API_KEY or the environment variable, and I will mean it.

One more note before we call anything: providers differ in details. The request/response shape below (often called the chat completions shape) is widely documented and supported by many providers, including OpenAI-compatible ones — but check your provider's docs for the exact URL and the current model names. URLs change, and model names version constantly, so this post uses placeholders rather than a model name that may be stale by the time you read it.

Step 2: The minimal call — curl edition

We start with curl because it shows the HTTP truth with nothing hidden: a POST, an authorization header, and a JSON body. Three placeholders are yours to replace: the URL (your provider's docs), the key, and the model (your provider's current model list).

curl https://api.example-provider.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "model": "your-chosen-model",
    "messages": [
      { "role": "user", "content": "Summarize this meeting note in two sentences: Sync with Priya about the migration. Ravi says staging is fine. Deployment freeze starts next week." }
    ]
  }'

Let's read this carefully, because every line teaches something about the shape:

  • The URL path (/v1/chat/completions) is the conventional chat endpoint. Many providers mirror this path precisely so clients are portable — but confirm it in your provider's docs.
  • Authorization: Bearer YOUR_API_KEY is how the provider knows who is paying for the call. The shell never stores this; it lives in the command you type. (For everyday use, prefer the environment variable form shown in the Python section below rather than typing keys into shells that log history.)
  • model picks which model answers. Model names change over time, so treat the value here as a placeholder and copy the name from your provider's current docs.
  • messages is an array. Each entry has a role and content. Roles you will meet: user (you), assistant (the model's previous replies), and — coming shortly — system (instructions for the model).

Step 3: Reading the response

Run that curl and the provider sends back JSON like this (formatting and extra fields trimmed):

{
  "id": "chatcmpl-abc123",
  "model": "your-chosen-model",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "The team synced with Priya on the migration; Ravi confirmed staging is healthy. Note the deployment freeze begins next week, so plan releases around it."
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 42,
    "completion_tokens": 38,
    "total_tokens": 80
  }
}

The part you care about 99% of the time is choices[0].message.content — that is the model's reply as plain text. Two other fields deserve your attention now, because they matter later:

  • finish_reason: "stop" means the model finished its answer naturally. If you ever see "length", the answer was cut off by a token limit — ask again or raise the limit.
  • usage tells you how many tokens this call consumed. File this away; it is the basis of cost, which we will discuss shortly.

See? Messages in, text out. The diagram was honest.

Step 4: The same call in Python — our actual app

curl is for learning the shape; Python is for building the tool. Below is a complete, runnable program. Save it as summarize.py in a folder with a file called notes.txt containing your real meeting notes. Install the requests library first if you don't have it (pip install requests).

import json
import os
import sys

import requests

# ---- Replace these three with YOUR values ----
API_URL = "https://api.example-provider.com/v1/chat/completions"  # your provider's docs
API_KEY = os.environ.get("AI_API_KEY")                             # set in your shell, never in code
MODEL = "your-chosen-model"                                        # current name from your provider's docs
# ----------------------------------------------

if not API_KEY:
    sys.exit("Set the AI_API_KEY environment variable before running.")

with open("notes.txt", "r", encoding="utf-8") as f:
    notes = f.read().strip()

if not notes:
    sys.exit("notes.txt is empty — paste some meeting notes into it first.")

response = requests.post(
    API_URL,
    headers={
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
    },
    json={
        "model": MODEL,
        "messages": [
            {
                "role": "system",
                "content": (
                    "You are a concise meeting-notes summarizer. "
                    "Reply with a 3-line summary, then a bulleted list "
                    "of action items with owners if named."
                ),
            },
            {
                "role": "user",
                "content": f"Summarize these meeting notes:\n\n{notes}",
            },
        ],
    },
    timeout=60,
)

response.raise_for_status()
summary = response.json()["choices"][0]["message"]["content"]
print(summary)

Run it with python summarize.py (with AI_API_KEY exported as in Step 1) and you get a summary of your notes. Congratulations: that is an AI app. It reads a file, calls the model, prints text.

Step 5: One real feature — the system prompt sets the tone

Look at the first message in the array. The system role is not part of the conversation the user sees; it is instructions that steer every answer. Change it and the app's personality changes, with no other code touched. Try these variations to feel it:

  • Strict and businesslike: "You are a terse executive assistant. Output only a numbered list of decisions and action items. No greetings, no commentary."
  • Kind to beginners: "You are a patient mentor summarizing for someone who missed the meeting. Use plain language and define any jargon."
  • Focused on follow-through: "Extract only commitments: who promised what, by when. If an owner or date is missing, say so explicitly."

This is the cheapest, most powerful lever in AI app building. The system prompt is your app's job description for the model — write it deliberately, and your one-trick tool starts behaving like a product. A word of honesty, though: a system prompt nudges, it does not guarantee. Treat its instructions as a strong request to a capable but literal colleague, and verify the output on your real notes before trusting it with anything that matters. If the summary invents an action item that was never discussed, the prompt needs tightening — or the task needs a human in the loop.

You have now gone from zero to a genuinely useful tool in about thirty lines. Everything from here is making it robust.

Step 6: Error handling — what to do when the call fails

Network calls fail. APIs fail. Keys expire. A demo that explodes with a stack trace on the first hiccup is not an app; here is the minimum that earns the name. Replace the requests.post(...) section above with a version that distinguishes failure types:

import time

def call_model(payload):
    """POST once, retrying a single time on transient failures."""
    for attempt in (1, 2):
        try:
            resp = requests.post(API_URL, headers={
                "Authorization": f"Bearer {API_KEY}",
                "Content-Type": "application/json",
            }, json=payload, timeout=60)
        except requests.exceptions.RequestException as e:
            # Network-level problem: DNS, connection reset, timeout.
            if attempt == 1:
                time.sleep(2)
                continue
            sys.exit(f"Network error calling the API: {e}")
        if resp.status_code == 200:
            return resp
        if resp.status_code == 401:
            # Your key is wrong or revoked. Retrying won't help.
            sys.exit("Authentication failed (401). Check AI_API_KEY and that the key is still active.")
        if resp.status_code == 429:
            # Rate limit: the one failure worth retrying once, then waiting.
            if attempt == 1:
                time.sleep(5)
                continue
            sys.exit("Rate limited (429). Wait a minute and try again.")
        # Anything else: show the provider's message, don't guess.
        sys.exit(f"API error {resp.status_code}: {resp.text[:300]}")
    sys.exit("Gave up after one retry.")

The principles, which hold for every API you will ever call:

  • Retry once on transient failures (network blips, 429 rate limits, occasional 500s). Retrying forever turns your app into a load cannon; one polite retry is the sweet spot for a script.
  • Never retry on 401. The key is wrong; trying again changes nothing and wastes time.
  • Show the provider's error message. resp.text usually tells you exactly what went wrong ("model not found", "insufficient quota"). Surfacing it beats any guessing game.
  • Set a timeout. The timeout=60 above means your script fails loudly after a minute instead of hanging forever. A hung script is the worst failure mode because it tells you nothing.

Wire it in by building the payload first, then calling resp = call_model({"model": MODEL, "messages": [...]}) and continuing with resp.json()["choices"][0]["message"]["content"] as before. Try it: temporarily export a bogus key and watch the script explain the 401 instead of crashing. Deliberately breaking things to see the error path is not paranoia — it is how you know the app works.

Step 7: Cost awareness — tokens are money

Most providers bill by tokens — chunks of text, roughly three-quarters of a word each in English. Both your input (the messages you send) and the output (the text you get back) count, and remember that usage block in the response? That is your receipt: prompt_tokens plus completion_tokens.

For this tutorial's experiments, cost is negligible — pennies or free credits. But build the right habits now, because they compound:

  • Keep prompts short while experimenting. Don't paste a 50-page document into notes.txt on day one; test on a page first. Every token you send is a token you pay for, and long inputs are where bills come from.
  • Ask for concise outputs. The system prompt asking for "a 3-line summary" isn't just style — it caps what you pay for on the response side too.
  • Check your dashboard. Nearly every provider shows per-key usage and lets you set spending limits or alerts. Set one before you build anything that runs unattended.
  • Don't ship secrets to save cents. The cheapest call in the world is not worth a leaked key. A key in a public repo is a key in someone else's crypto miner.

I am deliberately not quoting prices here — they change often enough that any number I print would be stale. Your provider's pricing page is the source of truth; glance at it once so the mental model ("input tokens + output tokens, billed together") is anchored.

What to build next: three ideas

You now know the whole foundation. The loop is the same for everything below — only the system prompt, the input source, and what you do with the output change. Pick one and you have your weekend project:

  1. The follow-up digester. Point the script at a folder of notes files instead of one file, and have it print a single digest: decisions made this week, open action items with owners, items mentioned twice that still have no owner. It is the same messages in, text out loop — just a loop around the loop, one file at a time, and the system prompt becomes "merge these summaries".
  2. The "explain my diff" helper. Capture the output of git diff into a file, then ask the model for a plain-English explanation of what the changes do and what might break. Developers use this constantly; it is the meeting-notes summarizer with code as the input. Mind the cost rule: diffs can be long, so start small.
  3. Your own tiny FAQ bot. Take a document you know well — a product manual, your team's onboarding guide, a hobby rulebook — and build a loop: ask a question, send the document plus the question as messages, print the answer. The limitation you will hit is that big documents don't fit in one call, which is exactly the motivation behind retrieval (sending only the relevant pages) — a whole topic in its own right, and a natural next step once this post's loop feels boring.

Notice the pattern across all three: nobody needed a new framework. The skill that transfers is shaping the input (what goes into messages) and using the output (where the text goes). Frameworks can come later; they are conveniences over this exact shape, and you will now recognize them as such.

Wrap-up

Let's recap the journey, because it was shorter than it felt:

  • You got an API key and stored it in an environment variable — never in code, never in a screenshot.
  • You made the minimal call with curl: POST, bearer token, JSON with model and messages.
  • You learned to read the response: the answer lives at choices[0].message.content, and usage is your receipt.
  • You built the same call as a Python script that summarizes a real file of meeting notes — a working app, not a snippet.
  • You added a real feature: a system prompt that sets the tone, changeable without touching code.
  • You learned to fail gracefully: retry once on transient errors, never on 401, always show the provider's message, always set a timeout.
  • You learned that tokens are money: keep prompts short while experimenting, and set a spending limit before anything runs unattended.

The meeting-notes summarizer sitting in your folder is small, but it is complete: real input, real output, real failure handling, real cost awareness. Most "AI apps" you will encounter are this same loop with nicer clothes. When you are ready to make the input smarter — feeding the model only the relevant pages of a large document instead of the whole thing — that is the natural next mountain, and the loop you learned today is the base camp.

Want the bigger picture? The AI tutorial hub collects this track's guides in one place — this post is where hands get dirty, and the hub is where you decide what to build next.

Comments

Popular posts from this blog

JSP Servlet Interview Questions For Freshers Series 1

What are the core classes and interfaces of hibernate 4

Java Banking Finance Services and Insurance (BFSI) domain interview questions