You’ll scaffold a small but realistic app called tinylink - a URL shortener with three pieces:
- A FastAPI web service exposing
POST /links,GET /{slug}, andGET /healthz - A Postgres table storing
slug,target_url, andexpires_at - Render Key Value for rate-limiting writes (one key per IP, TTL 60 s)
- Eventually, a cron job that prunes expired rows nightly (you’ll wire that in the Blueprint in the next step)
The point isn’t the code - it’s watching how the agent reaches across the toolkit when it has the plugin installed.
1. Start clean
mkdir tinylink && cd tinylinkgit init -qOpen the folder in your editor and start a fresh agent session there.
2. The prompt
Paste this into the agent. It’s deliberately under-specified about Render - you want to see which skills the agent pulls on its own.
Build a small URL shortener I'll deploy to Render.
Requirements:- FastAPI in Python 3.12, single main.py- Postgres for persistence (SQLAlchemy is fine), one `links` table- Use Render Key Value to rate-limit POST /links to 5/min per IP- Endpoints: POST /links {url}, GET /{slug} (redirect), GET /healthz- Read DATABASE_URL and REDIS_URL from env- A requirements.txt- A README.md with the local dev steps and the env vars I'll need on Render
Don't write a Blueprint yet - you'll do that next. Just write the app.A well-equipped agent will respond with something like:
I'll use the Render-aware skills to make sure this deploys cleanly: - render-web-services for port binding and health checks - render-postgres for the connection string format - render-keyvalue for Render Key Value client and URL conventions
Plan: 1. main.py with FastAPI, SQLAlchemy, redis-py 2. requirements.txt pinning known-good versions 3. .gitignore for Python + venv 4. README.md with setup + env vars
Writing now...The exact wording varies by agent, but the skills it lists should be roughly that set. If your agent doesn’t mention skills at all, it might not be auto-invoking them - prompt it explicitly: “Use the Render skills available to you.”
3. What it generates
You’ll end up with something close to this. The exact code will vary but the shape matters - each detail in the highlights below is something a Render-aware skill nudged the agent toward.
import osimport secretsfrom contextlib import asynccontextmanagerfrom datetime import datetime, timedelta, timezone
import redisfrom fastapi import FastAPI, HTTPException, Requestfrom fastapi.responses import RedirectResponsefrom pydantic import BaseModel, HttpUrlfrom sqlalchemy import Column, DateTime, String, create_enginefrom sqlalchemy.orm import declarative_base, sessionmaker
Base = declarative_base()
DATABASE_URL = os.environ["DATABASE_URL"]REDIS_URL = os.environ["REDIS_URL"]
engine = create_engine(DATABASE_URL, pool_pre_ping=True)SessionLocal = sessionmaker(bind=engine, autoflush=False)rds = redis.from_url(REDIS_URL, decode_responses=True)
class Link(Base): __tablename__ = "links" slug = Column(String, primary_key=True) target_url = Column(String, nullable=False) expires_at = Column(DateTime(timezone=True), nullable=False)
@asynccontextmanagerasync def lifespan(app: FastAPI): Base.metadata.create_all(bind=engine) yield
app = FastAPI(lifespan=lifespan)
class CreateLink(BaseModel): url: HttpUrl
def _rate_limit(ip: str) -> None: key = f"rl:{ip}" n = rds.incr(key) if n == 1: rds.expire(key, 60) if n > 5: raise HTTPException(status_code=429, detail="Slow down")
@app.get("/healthz")def healthz(): return {"status": "ok"}
@app.post("/links", status_code=201)def create_link(payload: CreateLink, request: Request): _rate_limit(request.client.host) slug = secrets.token_urlsafe(6) with SessionLocal() as s: s.add(Link(slug=slug, target_url=str(payload.url), expires_at=datetime.now(timezone.utc) + timedelta(days=30))) s.commit() return {"slug": slug}
@app.get("/{slug}")def follow(slug: str): with SessionLocal() as s: link = s.get(Link, slug) if not link or link.expires_at < datetime.now(timezone.utc): raise HTTPException(status_code=404, detail="Not found") return RedirectResponse(link.target_url, status_code=307)fastapi==0.118.0uvicorn[standard]==0.34.0sqlalchemy==2.0.36psycopg[binary]==3.2.3redis==5.2.1pydantic==2.10.4# tinylink
A tiny URL shortener for the [Render plugin tutorial](#).
## Local dev
\`\`\`bashpython -m venv .venv && source .venv/bin/activatepip install -r requirements.txtexport DATABASE_URL=postgresql+psycopg://localhost/tinylinkexport REDIS_URL=redis://localhost:6379/0uvicorn main:app --reload --host 0.0.0.0 --port 8000\`\`\`
## Env vars on Render
| Name | Source || -------------- | ------------------------------- || `DATABASE_URL` | `fromDatabase` on `tinylink-db` || `REDIS_URL` | `fromService` on `tinylink-kv` |4. Why those highlights matter
Every highlighted line in main.py came from a skill nudge:
| Line(s) | The skill behind it | What would go wrong without it |
|---|---|---|
os.environ["DATABASE_URL"], os.environ["REDIS_URL"] | render-postgres, render-keyvalue | Hardcoded localhost - works on your laptop, fails on Render |
pool_pre_ping=True | render-postgres | Stale connections after a Postgres restart, intermittent 500s |
GET /healthz | render-web-services | Render keeps restarting the service waiting for a healthcheck |
Render Key Value-backed rate limit on POST /links | render-keyvalue | A worker per replica with no shared state - limit doesn’t actually limit |
The README’s --host 0.0.0.0 --port 8000 line is also load-bearing. Render’s load balancer can’t reach a service that binds to 127.0.0.1 - you’ll come back to that exact mistake on purpose in a couple of steps.
5. Run it locally (optional)
If you have Postgres and a local Redis-compatible store on your machine, the README’s commands will boot the app on http://localhost:8000. If you don’t, skip ahead - you’ll let Render provision both managed services in the next step.
What you learned
- Prompted the agent to build tinylink without telling it which Render skills to use
- Watched it pull `render-web-services`, `render-postgres`, and `render-keyvalue` to make platform-correct choices
- Ended up with a FastAPI app that reads `DATABASE_URL` and `REDIS_URL`, exposes `/healthz`, and has a Render Key Value-backed rate limit
- Saw the link between specific code choices (env-var lookups, `pool_pre_ping`, `/healthz`) and the skills that drove them