AI Invoice Processor with Gemini

Build an AI app that extracts structured data from PDF invoices.

Introduction

30 Second Summary

Every business deals with stacks of PDF invoices, each formatted differently. Someone has to manually read through every one to pull out vendor names, dates, and dollar amounts.

In this project, you will build an AI-powered invoice processor that uses Google's Gemini API to extract structured data from any PDF invoice. You will wrap it in a FastAPI API with a clean HTML frontend, deploy it to Render, and walk away with a live URL you can drop straight into your AiStudio application.

What You'll Build

Upload a messy PDF invoice and watch your app extract vendor names, dates, line items, and totals into clean, structured data displayed right in your browser.

By the end of this project, you'll have:

  • Upload any PDF invoice and instantly see AI-extracted structured data including vendor name, invoice number, dates, line items, and totals.
  • Compare raw extracted text against AI-structured output side by side, showing exactly what the LLM does with messy invoice data.
  • Ship your app to a live public URL on Render's free tier so anyone can try it from a browser.
  • Secret Mission: Add natural language querying so you can ask questions like "What's my total spend?" across all processed invoices.

Are there any prerequisites?

This project assumes you have VS Code and Python 3.11 installed, along with a Gemini API key. You can get a free API key from Google AI Studio with no credit card required.

Before We Start

Before we dive in, let's commit to what you're about to build and why it matters.

Understanding the goal upfront helps you connect every step back to the skills AiStudio is hiring for in their Forward Deployed Engineer role.

Set Up Your Project

Before writing any code, you need a clean workspace. A dedicated project folder, an isolated Python environment, and the right packages keep your dependencies organized and your code portable.

In this step, you'll create your project folder, set up a virtual environment, and install the three libraries that power your invoice processor.

In this step, get ready to:
  • Create a project folder and a Python virtual environment.
  • Install the project dependencies.
  • Create the project file structure.
Create your project folder and virtual environment

A virtual environment gives your project its own isolated set of Python packages. This prevents version conflicts between different projects on your machine.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal and press Enter to open it.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir ai-invoice-processor
cd ai-invoice-processor
  • Create a virtual environment by running the command for your operating system:

macOS

python3 -m venv .venv

Windows

python -m venv .venv
  • Activate the virtual environment by running the command for your operating system:

macOS

source .venv/bin/activate

This same command works on Linux.

Windows

If you are using Command Prompt:

.venv\Scripts\activate.bat

If you are using PowerShell:

.venv\Scripts\Activate.ps1

Your terminal prompt now starts with (.venv). This confirms you are working inside the virtual environment.

What is a virtual environment?

Think of a virtual environment as a private toolbox for your project. Packages you install here stay separate from every other Python project on your machine.

When you activate it, python and pip point to this environment's copies. Deactivating restores your system defaults.

🙋‍♀️ Don't see (.venv) in your prompt?

  • Make sure you ran the activation command that matches your operating system. macOS uses source. Windows uses the Scripts folder.
  • On Windows PowerShell, you may see an error about running scripts being disabled. Run this command, then try the activation command again:
  • Confirm you are inside the ai-invoice-processor folder. The .venv directory must exist in your current directory.
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

Still stuck? Help me activate my Python virtual environment.

Install the project dependencies
  • Install the three packages your project needs by running this command:
pip install streamlit==1.61.1 pymupdf==1.28.2 google-genai

What do these packages do?

  • streamlit is a Python framework for building interactive web apps. It turns your Python script into a browser-based interface with zero front-end code.
  • pymupdf is a fast PDF library that extracts text from PDF files. It is the engine that pulls raw text out of uploaded invoices.
  • google-genai is Google's SDK for the Gemini API. It sends the raw invoice text to the LLM and receives structured data back.

After a few moments, you should see lines starting with Successfully installed followed by the package names and their versions.

Seeing an installation error?

  • Make sure (.venv) appears in your terminal prompt. If it does not, the virtual environment is not active and pip may point to your system Python.
  • Check your internet connection. All three packages are downloaded from the Python Package Index.
  • If you see a permissions error, do not use sudo. Reactivate the virtual environment and try the install again.

Still stuck? Help me install streamlit, pymupdf, and google-genai in my virtual environment.

Create the project files

Your project needs three files. Two Python files for the application logic and one text file to record your dependencies.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Visual Studio Code and press Enter to open it.
  • Click File in the top menu bar.
  • Click Open Folder.
  • Select the ai-invoice-processor folder on your Desktop, then click Open.
  • In the Explorer sidebar on the left, hover over the folder name and click the New File icon.
  • Type app.py and press Enter.
  • Click the New File icon again and type extractor.py, then press Enter.
  • Click the New File icon one more time and type requirements.txt, then press Enter.
  • Confirm your files exist by switching back to your terminal and running:
ls

You should see app.py, extractor.py, and requirements.txt in the output (alongside the .venv folder).

Missing a file?

  • Double check you created all three files in the ai-invoice-processor folder, not inside the .venv folder or a subfolder.
  • In VS Code, make sure the file names have the correct extensions: .py for Python files and .txt for the requirements file.

Still stuck? Help me create files in VS Code for my Python project.

  • Back in VS Code, click on requirements.txt in the Explorer sidebar to open it.
  • Add the following dependency list:
streamlit==1.61.1
pymupdf==1.28.2
google-genai

What does requirements.txt do?

This file records every package your project depends on, along with version pins. When you deploy your app or share it with someone else, running pip install -r requirements.txt recreates the exact same environment.

Pinning Streamlit to 1.61.1 and PyMuPDF to 1.28.2 locks in the versions you tested against. The google-genai SDK is left unpinned so it always installs the latest compatible release.

  • Save requirements.txt by pressing Cmd+S (macOS) or Ctrl+S (Windows).

✔️ Awesome, I've got everything!

Great. Double check you have saved requirements.txt before moving on.

ⓧ I'd like to double check the full code

Your requirements.txt should look exactly like this:

streamlit==1.61.1
pymupdf==1.28.2
google-genai

The app.py and extractor.py files should be empty for now. You will add code to them in the next steps.

Before you run the next command, what do you expect to see if all three packages imported correctly?

  • Switch back to your terminal and verify everything works by running:
python -c "import pymupdf; import streamlit; from google import genai; print('All imports successful')"

You should see All imports successful printed in your terminal. This confirms that all three packages are installed and importable.

Seeing a ModuleNotFoundError?

  • Confirm your virtual environment is still active. You should see (.venv) in your terminal prompt. If not, reactivate it.
  • Make sure you ran the pip install command inside the activated virtual environment. Rerun the install command from the previous substep.
  • Check that you are using python (not python3) since the virtual environment aliases python to the correct version.

Still stuck? Help me fix a ModuleNotFoundError when importing pymupdf, streamlit, or google-genai.

Your workspace is locked and loaded. Next up, you'll write real code to rip text out of a PDF invoice and see just how messy raw extracted text looks.

Extract Raw Text from a PDF Invoice

Your project environment is ready with all dependencies installed and your file structure in place.

Before AI can process an invoice, you need to get the raw text out of the PDF file. The challenge is that raw extracted text comes out as an unstructured wall of characters. Vendor names, dates, and dollar amounts are all jumbled together with no separation. This is the exact document processing problem that PE portfolio companies face every day.

In this step, get ready to:
  • Write a text extraction function using PyMuPDF.
  • Run it on a sample PDF invoice and see the raw, unstructured output.
  • Understand why raw text alone is not enough for automation.
Write the PDF text extraction function

PyMuPDF is a fast Python library for reading PDFs. It can open a PDF file, iterate through each page, and pull out all the text. The sort=True parameter tells PyMuPDF to return the text in natural top-to-bottom, left-to-right reading order instead of the internal order the PDF was created in.

  • Open extractor.py in VS Code.
  • Add the imports and the extraction function by pasting the following code:
import pymupdf
import json
import sys
from google import genai


def extract_text_from_pdf(pdf_bytes):
    # Open the PDF from raw bytes
    doc = pymupdf.open(stream=pdf_bytes, filetype="pdf")
    text = ""
    # Extract text from each page in reading order
    for page in doc:
        text += page.get_text(sort=True)
    doc.close()
    return text

What does this code do?

pymupdf.open(stream=pdf_bytes, filetype="pdf") opens a PDF from raw bytes in memory instead of from a file path. This makes the function reusable for both command-line scripts and web app uploads.

The for page in doc loop walks through every page, pulling out the text with get_text(sort=True) so the output reads top-to-bottom, left-to-right. Without sort=True, the text order depends on how the PDF was internally created, which can be scrambled.

💡 Why import json and genai now?

The json and genai imports are not used yet. You will use them in the next step when you send the raw text to Gemini for structured extraction. Adding all imports up front keeps the file organized.

Get a sample PDF invoice

You need an actual PDF invoice to test the extraction. Any PDF invoice will work. If you have one handy (a utility bill, a receipt, a vendor invoice), use that.

  • Find a PDF invoice you already have, or search online for "sample invoice PDF" to download a free one.
  • Save or copy the PDF into your project folder (the same folder that contains extractor.py).
  • Rename the file to sample_invoice.pdf so it matches the commands you will run next.
  • Confirm the file is in the right place by running:
ls

You should see sample_invoice.pdf listed alongside app.py, extractor.py, and requirements.txt.

Add the main block and run the extractor

The extraction function is ready, but you need a way to run it from the terminal. A __main__ block reads command-line arguments, loads the PDF as bytes, and passes them to your function.

  • In extractor.py, add this code below the extract_text_from_pdf function:

if __name__ == "__main__":
    # Check that both arguments were provided
    if len(sys.argv) < 3:
        print("Usage: python extractor.py <pdf_path> <api_key>")
        sys.exit(1)

    pdf_path = sys.argv[1]
    api_key = sys.argv[2]

    # Read the PDF file as bytes
    with open(pdf_path, "rb") as f:
        pdf_bytes = f.read()

    # Extract and print the raw text
    print("--- Raw Extracted Text ---")
    raw_text = extract_text_from_pdf(pdf_bytes)
    print(raw_text)

What does this code do?

if len(sys.argv) < 3 checks that the learner provided both a PDF file path and an API key when running the script. If either is missing, it prints a usage message and exits.

The script reads the PDF file as raw bytes with open(pdf_path, "rb"), then passes those bytes to extract_text_from_pdf. The API key is not used yet, but the script accepts it now so you do not need to change the command in the next step.

  • Save extractor.py.

Before you run this, what do you think the output will look like? Clean, organized data with labeled fields, or something messier?

  • Run the extractor on your sample invoice by running this command:
python extractor.py sample_invoice.pdf your-api-key-here

You should see a wall of unstructured text printed under --- Raw Extracted Text ---. The text contains everything from the invoice: vendor names, dates, line item descriptions, dollar amounts. But it is all jumbled together as plain characters with no separation between fields.

A human could read this and pick out the vendor name or total. Software cannot. That is the gap your AI pipeline will close in the next step.

Not seeing any output?

  • Make sure you saved extractor.py before running the command.
  • Check that sample_invoice.pdf is in the same folder as extractor.py. Run ls to confirm both files appear.
  • Make sure your virtual environment is still activated. You should see the environment name in your terminal prompt.
  • If you see a usage message instead of invoice text, make sure you included both arguments: the PDF filename and a placeholder API key.

Still stuck? Help me debug why my extractor.py script is not printing any output when I run it with a sample PDF.

✔️ Awesome, I've got everything!

Great. Double check that you have saved extractor.py and that your terminal showed the raw text output.

ⓧ I'd like to double check the full code

Here is the complete extractor.py file so far. Compare it against yours:

import pymupdf
import json
import sys
from google import genai


def extract_text_from_pdf(pdf_bytes):
    doc = pymupdf.open(stream=pdf_bytes, filetype="pdf")
    text = ""
    for page in doc:
        text += page.get_text(sort=True)
    doc.close()
    return text


if __name__ == "__main__":
    if len(sys.argv) < 3:
        print("Usage: python extractor.py <pdf_path> <api_key>")
        sys.exit(1)

    pdf_path = sys.argv[1]
    api_key = sys.argv[2]

    with open(pdf_path, "rb") as f:
        pdf_bytes = f.read()

    print("--- Raw Extracted Text ---")
    raw_text = extract_text_from_pdf(pdf_bytes)
    print(raw_text)

You have proven that the text is all there inside the PDF. The problem is that it is a shapeless wall of characters that no software can act on. Next up, you will send this raw text to Gemini and watch it transform the mess into clean, structured JSON with labeled fields like vendor name, line items, and totals.

Use Gemini to Extract Structured Invoice Data

In the last step, you extracted raw text from a PDF invoice. The text was all there, but it came out as a wall of characters with no separation between vendor names, dates, and dollar amounts.

A human could read that raw text, but software cannot act on it. In this step, you will send that messy text to Gemini and get back clean, structured JSON with vendor names, dates, line items, and totals.

In this step, get ready to:
  • Write a function that sends raw invoice text to Gemini with a structured extraction prompt.
  • Parse the JSON response and print it alongside the raw text.
  • See the transformation from unstructured text to clean, actionable data.
Write the Gemini extraction function

The key to turning raw text into structured data is the prompt. You need to tell Gemini exactly what fields to extract and exactly what format to return them in.

  • In extractor.py, find the blank line just above if __name__ == "__main__":.
  • Add the following function above that line:
def extract_invoice_data(text, api_key):
    client = genai.Client(api_key=api_key)
    # Prompt telling Gemini exactly what JSON fields to extract
    prompt = (
        "Extract the following fields from this invoice text.\n"
        "Return ONLY valid JSON with no other text or markdown formatting.\n\n"
        "Fields to extract:\n"
        "- vendor_name (string)\n"
        "- invoice_number (string)\n"
        "- invoice_date (string)\n"
        "- due_date (string or null)\n"
        "- line_items (list of objects with: description, quantity, unit_price, amount)\n"
        "- subtotal (number or null)\n"
        "- tax (number or null)\n"
        "- total (number)\n\n"
        "If a field is not found, use null.\n\n"
        f"Invoice text:\n{text}"
    )

    # Send the prompt to Gemini
    response = client.models.generate_content(
        model="gemini-3.6-flash",
        contents=prompt,
    )

    # Strip markdown fences if present, then parse the JSON response
    result_text = response.text.strip()
    if result_text.startswith("```"):
        result_text = result_text.split("\n", 1)[1]
        result_text = result_text.rsplit("```", 1)[0].strip()
    return json.loads(result_text)

What does this code do?

This function does three things in sequence.

  • genai.Client(api_key=api_key) creates a connection to the Gemini API using the key you pass in.
  • The prompt string is the most important part. It lists every field you want extracted (like vendor_name, line_items, total) with their expected types, then appends the raw invoice text at the end. This is called structured output prompting.
  • The fence-stripping block handles a quirk of LLMs. Gemini sometimes wraps JSON in markdown code fences (like ```json ... ```) even when you ask it not to. The code detects and removes those fences before parsing.

💡 Why prompt-based JSON instead of a schema parameter?

Some LLM APIs offer a dedicated parameter to force JSON output. The prompt-based approach used here works across any LLM provider, making your skill more transferable.

Call the extraction function and see the transformation

The function exists, but the script does not call it yet. You need to update the __main__ block to use it.

  • In extractor.py, scroll down to the bottom of the file.
  • Find this line:
    print(raw_text)
  • Add the following code directly below that line:
    print("\n--- Structured Invoice Data ---")
    # Send raw text to Gemini and print the structured result
    data = extract_invoice_data(raw_text, api_key)
    print(json.dumps(data, indent=2))

What does this addition do?

These lines call your new extract_invoice_data function with the raw text and your API key. json.dumps(data, indent=2) pretty-prints the structured result so you can easily read it in the terminal.

  • Save extractor.py.

Before you run this, think about what you expect to see. The script ran fine last time and printed raw text. What do you think changes now that Gemini is involved?

  • Run the script again on your sample invoice by running this command:
python extractor.py sample_invoice.pdf your-api-key-here

You should see two sections of output. First, the same raw text wall from before under --- Raw Extracted Text ---. Then, clean structured JSON under --- Structured Invoice Data --- with fields like vendor_name, invoice_number, line_items, and total neatly organized.

What should I see?

The raw text section looks exactly the same as before. The new structured section is the payoff. Gemini took that messy wall of characters and pulled out exactly the fields you asked for, formatted as clean JSON.

This is the core value of LLM-powered data extraction. A human could read the invoice, but now software can act on it too.

🙋‍♀️ Seeing an error instead of JSON?

  • If you see an API key error, double-check that you replaced your-api-key-here with your actual Gemini API key from Google AI Studio.
  • If you see a JSON parsing error, Gemini may have returned unexpected formatting. Run the command again. LLM responses can vary between calls.
  • If you see an indentation error, make sure the new lines you added are indented with the same spacing as the print(raw_text) line above them.

Still stuck? Help me debug why my Gemini extraction function is not returning valid JSON.

✔️ Awesome, I've got everything!

Great. Double check you have saved extractor.py.

ⓧ I'd like to double check the full code

Scroll up and confirm that your extractor.py file contains these sections in order:

  • The imports at the top (pymupdf, json, sys, genai) from the previous step.
  • The extract_text_from_pdf function from the previous step.
  • The new extract_invoice_data function you just added, with the prompt, API call, and fence-stripping logic.
  • The if __name__ == "__main__": block, now ending with the three new lines that print structured invoice data.

Your extraction pipeline is working. You can turn any PDF invoice into clean, structured data with a single command. Next up, you will wrap this in a web interface so anyone can upload an invoice and see results instantly.

Build the Streamlit Invoice Processor UI

Build a FastAPI Invoice Processing API

Your extraction pipeline works. Running extractor.py from the terminal proves that Gemini can turn a wall of raw PDF text into clean, structured JSON.

But a CLI script is not something you can demo in an interview or share in an application. You need an API that any frontend can call. FastAPI lets you wrap your Python extraction functions as HTTP endpoints with automatic request validation and interactive documentation built in.

In this step, get ready to:
  • Install FastAPI and create an API with a file upload endpoint that returns structured invoice data.
  • Test the API locally with a real PDF invoice.
  • Verify the API returns both raw text and structured JSON.
Set up FastAPI and create the API

FastAPI is a modern Python web framework built for APIs. It uses Python type hints to validate requests automatically and generates interactive API documentation at /docs with no extra code.

  • Open requirements.txt in VS Code.
  • Replace the entire contents with the following:
pymupdf
google-genai
fastapi[standard]
python-multipart

What are these new packages?

fastapi[standard] installs FastAPI along with uvicorn (the server that runs it). python-multipart enables file upload parsing, which FastAPI needs to accept PDF files.

  • Save requirements.txt.
  • Install the updated dependencies by running this command:
pip install -r requirements.txt

You should see FastAPI and its dependencies installing in the terminal output.

Seeing an install error?

  • Make sure your virtual environment is still activated. You should see (.venv) at the start of your terminal prompt.
  • Check that the quotes around fastapi[standard] are not in your requirements.txt. The brackets are part of the package name, not shell syntax. Quotes are only needed when installing directly from the command line.

Help me fix a pip install error for my FastAPI project.

  • Create a new file called main.py in the same folder as extractor.py.
  • Paste the following code to set up the FastAPI app with CORS and a health check:
import os
from fastapi import FastAPI, UploadFile, File
from fastapi.middleware.cors import CORSMiddleware
from extractor import extract_text_from_pdf, extract_invoice_data

# Create the API application
app = FastAPI(title="AI Invoice Processor API")

# Allow requests from any origin so the frontend can call this API
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.get("/")
async def root():
    # Health check endpoint
    return {"status": "ok", "service": "AI Invoice Processor API"}

What does this code do?

  • FastAPI() creates the API application. The title appears in the auto-generated documentation page.
  • CORSMiddleware tells the API to accept requests from any origin. Without this, the Next.js frontend you build in the next step would be blocked by the browser's same-origin policy.
  • The root() endpoint at / returns a simple JSON response. This is useful as a health check to confirm the API is running.
  • Add the invoice processing endpoint below the root() function in main.py:
@app.post("/process")
async def process_invoice(file: UploadFile = File(...)):
    # Read the uploaded PDF as bytes
    pdf_bytes = await file.read()

    # Extract raw text using PyMuPDF
    raw_text = extract_text_from_pdf(pdf_bytes)

    # Get the API key from environment
    api_key = os.environ.get("GEMINI_API_KEY", "")
    if not api_key:
        return {"error": "GEMINI_API_KEY environment variable not set"}

    # Send raw text to Gemini for structured extraction
    invoice_data = extract_invoice_data(raw_text, api_key)

    return {"raw_text": raw_text, "invoice_data": invoice_data}

What does this code do?

  • @app.post("/process") creates a POST endpoint that accepts file uploads. FastAPI automatically validates that the request includes a file.
  • UploadFile = File(...) tells FastAPI to expect a file field in the request body. The ... means the field is required.
  • The endpoint calls the same extract_text_from_pdf and extract_invoice_data functions you built in the previous steps. The API is a thin wrapper around your existing pipeline.
  • The response returns both raw_text and invoice_data so the frontend can display the raw-to-structured transformation.
  • Save main.py.

✔️ Awesome, I've got everything!

Great. Double check that you have saved main.py and that it contains both the imports and the two endpoints.

ⓧ I'd like to double check the full code

Scroll up through main.py and confirm these sections are present, in order:

  • The four import lines at the top (os, FastAPI, CORSMiddleware, and the extractor imports).
  • The app = FastAPI(...) line and the CORSMiddleware block.
  • The root() health check endpoint at GET /.
  • The process_invoice() endpoint at POST /process with PDF reading, text extraction, Gemini call, and the return statement.
Test the API locally

The API code is ready. Time to start the server and send it a real PDF.

  • Set your Gemini API key and start the FastAPI server by running these commands:
export GEMINI_API_KEY=your-api-key-here
uvicorn main:app --reload

What does uvicorn do?

Uvicorn is the ASGI server that runs your FastAPI app. The --reload flag watches for file changes and restarts automatically, just like a development server.

On Windows, replace the export line with set GEMINI_API_KEY=your-api-key-here before running the uvicorn command.

You should see output ending with Uvicorn running on http://127.0.0.1:8000. FastAPI also auto-generates interactive API documentation. Open http://localhost:8000/docs in your browser to see it.

Before you test, what do you think the API will return when you send it a PDF? The same raw text and structured JSON from Step 3, or something different?

  • Open a second terminal tab (keep the server running in the first one).
  • Make sure your virtual environment is activated in the new terminal.
  • Test the API with your sample invoice by running this command:
curl -X POST http://localhost:8000/process -F "file=@sample_invoice.pdf"

You should see a JSON response with two fields: raw_text containing the same extracted text from Step 2, and invoice_data containing the same structured fields (vendor_name, line_items, total) from Step 3.

Getting an error from the API?

  • If you see connection refused, make sure the uvicorn server is still running in your first terminal.
  • If the response says GEMINI_API_KEY environment variable not set, you need to export the key in the same terminal where uvicorn is running. Stop the server with Ctrl+C, export the key, and restart.
  • If you see a 422 error, make sure you used -F "file=@sample_invoice.pdf" (with the @ symbol) in your curl command. The @ tells curl to upload the file contents.

Help me debug my FastAPI invoice processing API.

Your extraction pipeline is now an API. Any application that can send an HTTP request can upload a PDF and get structured invoice data back. Next up, you will build a polished Next.js frontend that calls this API and displays the results.

Deploy to Streamlit Community Cloud

Deploy to Render

In the last step, you built a working API that processes invoices locally. That is great for testing, but a locally running app is not something you can share in a job application.

AiStudio deploys solutions live for portfolio companies, so proving you can ship to production matters. In this step, you will push your code to GitHub and deploy the app to Render, giving you a live URL you can drop straight into your application.

In this step, get ready to:
  • Create a GitHub repository and push your project code.
  • Deploy your FastAPI app to Render's free tier.
  • Add your Gemini API key as an environment variable so the deployed app can process invoices.
Create a GitHub repository and push your code

Render deploys directly from a GitHub repository. Every time you push new code, the deployed app updates automatically.

  • Open github.com/new in your browser to create a new repository.
  • Enter ai-invoice-processor as the repository name.
  • Leave the repository set to Public.
  • Do not check any of the initialization options (no README, no .gitignore, no license).
  • Click Create repository.

Why does the repository need to be public?

Render's free Hobby plan connects to public GitHub repositories. A public repo also means anyone with your deployed URL can see the source code, which is fine for a portfolio project.

After creating the repository, GitHub shows you setup instructions with your repository URL. You will use that URL in the next commands.

  • Switch back to your terminal in VS Code.
  • Stop the running uvicorn server by pressing Ctrl+C.
  • Initialize a Git repository and make your first commit by running these commands:
git init
git add main.py extractor.py requirements.txt
git commit -m "Initial commit: AI invoice processor"
  • Connect your local repository to GitHub and push the code by running these commands:
git branch -M main
git remote add origin [[REPO_URL="https://github.com/your-username/ai-invoice-processor.git"]]
git push -u origin main

Where do I find my repository URL?

On your new GitHub repository page, look for the HTTPS URL under the Quick setup section. It looks like https://github.com/your-username/ai-invoice-processor.git.

  • Refresh your GitHub repository page in the browser.

You should see main.py, extractor.py, and requirements.txt listed in the repository.

Don't see your files on GitHub?

Make sure you copied the correct repository URL from GitHub. If the push failed, check that your GitHub credentials are set up correctly.

Help me push my code to GitHub for my FastAPI invoice processor.

Deploy to Render and add your API key

Your code is on GitHub. Now you will connect it to Render so the app gets a live URL.

  • Open dashboard.render.com in your browser.
  • Click Sign in with GitHub and authorize Render to access your GitHub account.
  • In the top navigation, click New.
  • Select Web Service.

A form appears asking you to connect a repository.

  • Find your ai-invoice-processor repository and click Connect.
  • Set the Name to ai-invoice-processor.
  • Set the Language to Python 3.
  • Set the Build Command to pip install -r requirements.txt.
  • Set the Start Command to uvicorn main:app --host 0.0.0.0 --port $PORT.

Before deploying, you need to choose the free instance type and add your Gemini API key.

  • Under Instance Type, select Free.
  • Scroll down to the Environment Variables section.
  • Click Add Environment Variable.
  • Set the Key to GEMINI_API_KEY.
  • Set the Value to your Gemini API key.

How does the deployed app read your API key?

Your app.py already has the line api_key = os.environ.get("GEMINI_API_KEY", ""). Streamlit Community Cloud automatically makes root-level secrets available as environment variables.

This means the key you entered in the Secrets field is set as GEMINI_API_KEY in the environment, and your code picks it up without any changes.

  • Click Deploy.

Streamlit Community Cloud begins building your app. You can watch the deployment logs on the right side of the screen.

How long does deployment take?

Most apps deploy in a few minutes. The first deployment takes longer because Streamlit needs to install all your dependencies from requirements.txt.

When deployment finishes, your app loads at a URL ending in .streamlit.app.

Seeing a deployment error?

Check the deployment logs for the specific error message. The most common cause is a typo in requirements.txt or a missing file that was not pushed to GitHub.

Make sure app.py, extractor.py, and requirements.txt all appear in your GitHub repository.

help me troubleshoot my Streamlit Community Cloud deployment

Test your deployed app

Before you run this final test, what do you think will happen when you upload an invoice to the deployed app? Will it behave identically to the local version?

  • Copy your deployed app URL from the browser address bar.
  • Open the URL in a new browser tab (or an incognito window) to see it as a visitor would.
  • Upload a PDF invoice using the Browse files button.
  • Click Process Invoice.

The deployed app extracts and structures the invoice data, just like it did locally. You now have a live URL you can include in your AiStudio application.

Getting an error when processing an invoice?

If the app loads but invoice processing fails, your API key secret may not be set correctly. Go back to your app on share.streamlit.io, click the three-dot menu in the lower-right corner of the app, then select Settings.

Check the Secrets section and make sure your key is formatted exactly as GEMINI_API_KEY = "your-key" with the quotes included.

help me fix my deployed Streamlit app's API key secret

Your AI invoice processor is live. Next up, take on the Secret Mission to add natural language querying across all your processed invoices.

Secret mission

Add Natural Language Invoice Querying

Your invoice processor extracts data, but what if users could just ask questions in plain English? Add a query feature so anyone can type "What's my total spend?" and get an instant answer across all processed invoices. This is the difference between a tool and a product.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally and uses free services. The Gemini API free tier has no charges and Streamlit Community Cloud is free for public apps, so there are no ongoing costs.

Decide whether to keep your resources running, take them down, or keep building.

Resources you used:

  • Streamlit Community Cloud app (live at your .streamlit.app URL).
  • GitHub repository containing app.py, extractor.py, and requirements.txt.
  • Local project folder on your Desktop with your Python virtual environment and project files.
  • GEMINI_API_KEY secret stored in Streamlit Community Cloud.

Keep everything running

No action needed. Choose this if you want to keep your deployed app live and continue building on it.

  • Your Streamlit Community Cloud app stays live indefinitely at no cost for public repos.
  • Your GitHub repository remains accessible.
  • Your local project files stay on your Desktop.
  • Your Gemini API key continues working within the free tier.

Pause - I'll come back to this later

Free up local resources while keeping your cloud deployments intact. You can resume work any time.

  • Your Streamlit Community Cloud app and GitHub repository stay live at no cost. No action needed for those.
  • Deactivate your Python virtual environment by running:
deactivate
  • You can reactivate the virtual environment later by navigating to your project folder on the Desktop and running source .venv/bin/activate (macOS) or .venv\Scripts\activate (Windows).

Delete - I don't want to use this again

Remove all project resources. This is permanent.

  • Delete your Streamlit Community Cloud app:
  • Go to share.streamlit.io.
  • Click the three-dot overflow icon next to your app.
  • Click Delete.
  • Enter the required confirmation string and click Delete again.
  • Delete your GitHub repository:
  • Open your repository on GitHub.
  • Click Settings in the repository navigation bar.
  • Scroll down to the Danger Zone section.
  • Click Delete this repository.
  • Click I want to delete this repository.
  • Type the repository name to confirm, then click Delete this repository.
  • Delete your local project files. Open your terminal and run:

macOS

cd ~/Desktop
ls

Windows

cd ~\Desktop
ls
  • Find your project folder name in the output, then delete it:

macOS

rm -rf [[PROJECT_FOLDER="your project folder name"]]

Windows

Remove-Item -Recurse -Force [[PROJECT_FOLDER="your project folder name"]]

Nice Work!

Nice Work!

You just built and deployed an AI-powered invoice processor from scratch. You took a messy, real-world document problem, automated it with an LLM, wrapped it in a web interface, and shipped it live to a public URL you can drop straight into your AiStudio application.

Here's what you accomplished:

  • Built an AI-powered data extraction pipeline using PyMuPDF and the Gemini API to turn messy PDF invoices into clean, structured JSON with vendor names, dates, line items, and totals.
  • Created a Streamlit web app where users upload invoices and instantly see structured results with metrics, a line items table, and full JSON output.
  • Deployed the app live to Streamlit Community Cloud with a shareable URL you can include in your AiStudio application.
  • Secret Mission: Added natural language querying so users can ask questions like "What's my total spend?" across all processed invoices and get AI-generated answers.

Ready to quiz yourself?