DEV Community

Cover image for Using Cursor AI Code Tracking API with FastAPI
Ayush Kumar
Ayush Kumar

Posted on Originally published at logiclooptech.dev

Using Cursor AI Code Tracking API with FastAPI

If you need to capture every edit that Cursor AI makes to your codebase, the Cursor AI Code Tracking API is the tool you’re looking for. In a few minutes you can authenticate, pull change events, persist them, and build a simple dashboard – all inside a FastAPI service you already run in production.

Below is a step-by-step walkthrough that shows exactly how to do it, where things tend to break, and what you should keep an eye on. I’ve stripped out the fluff and kept the focus on things that actually matter when you’re shipping a product.


Overview of Cursor AI Code Tracking API

What does the Cursor AI Code Tracking API give you? It streams JSON payloads that describe each file change, the prompt that triggered it, and a timestamp. The endpoint is GET https://api.cursor.com/v1/tracking. It returns a paginated list of events, each looking like:

{
  "id": "evt_12345",
  "file_path": "app/main.py",
  "change_type": "modification",
  "prompt": "Refactor the request handler",
  "diff": "-old line\n+new line",
  "created_at": "2024-08-14T12:34:56Z"
}
Enter fullscreen mode Exit fullscreen mode

That’s all you need to reconstruct a timeline of AI-generated edits. The API is low-latency, but it enforces rate limits (30 calls per minute) and requires a valid API key for every request.


Authentication and API key setup

How do you securely store and use the Cursor API key? The service expects the key in an Authorization: Bearer <token> header. In production I keep the token in an environment variable called CURSOR_API_KEY. Never hard-code it.

import os
from fastapi import Depends, HTTPException, status

def get_cursor_token() -> str:
    token = os.getenv("CURSOR_API_KEY")
    if not token:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="Cursor API key not configured"
        )
    return token
Enter fullscreen mode Exit fullscreen mode

If the key is missing, the service fails fast with a 500 error – that’s intentional. It prevents you from silently sending unauthenticated requests and hitting obscure 401 responses later in the pipeline.

Common failure: forgetting to add the env var to your Docker or Kubernetes secret. The result is a cascade of 401 errors that look like “invalid credentials” but actually stem from a missing secret. Double-check your deployment manifest.


Integrating the API with a FastAPI service

What does the integration code look like? Below is a minimal FastAPI router that pulls the latest 100 events and returns them as a list.

import httpx
from fastapi import APIRouter, Depends

router = APIRouter()

CURSOR_ENDPOINT = "https://api.cursor.com/v1/tracking"

@router.get("/cursor/events")
async def get_cursor_events(token: str = Depends(get_cursor_token)):
    async with httpx.AsyncClient(timeout=10) as client:
        resp = await client.get(
            CURSOR_ENDPOINT,
            headers={"Authorization": f"Bearer {token}"},
            params={"limit": 100}
        )
        if resp.status_code != 200:
            # Propagate the exact error for debugging
            raise HTTPException(
                status_code=resp.status_code,
                detail=f"Cursor API error: {resp.text}"
            )
        return resp.json()["events"]
Enter fullscreen mode Exit fullscreen mode

A few things to note:

  1. Async client – FastAPI runs on ASGI, so use httpx.AsyncClient to avoid blocking the event loop.
  2. Timeout – set a reasonable timeout (10 seconds here) to prevent hanging workers.
  3. Error propagation – surface the exact error; otherwise you’ll see generic 500s that make troubleshooting harder.

Trade-off: pulling 100 events on every request is cheap for a dev dashboard but could become expensive at scale. If you need real-time updates, consider a webhook or a background poller instead of on-demand fetching.


Persisting tracking data in PostgreSQL or Redis

Where should you store the events? Two common patterns:

PostgreSQL (relational, query-heavy)

Create a table that mirrors the JSON payload:

CREATE TABLE cursor_events (
    id TEXT PRIMARY KEY,
    file_path TEXT NOT NULL,
    change_type TEXT NOT NULL,
    prompt TEXT,
    diff TEXT,
    created_at TIMESTAMPTZ NOT NULL
);
Enter fullscreen mode Exit fullscreen mode

Insert with async SQLAlchemy:

from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import insert
from models import CursorEvent  # SQLAlchemy model matching the table

async def save_events(events: list[dict], db: AsyncSession):
    stmt = insert(CursorEvent).values(events)
    await db.execute(stmt)
    await db.commit()
Enter fullscreen mode Exit fullscreen mode

Failure mode: duplicate id values raise an integrity error. Wrap the insert in a try/except block and ignore duplicates if you’re polling the same page repeatedly.

Redis (fast, volatile, analytics)

If you only need recent changes for a dashboard, a Redis stream works well:

import aioredis

redis = await aioredis.from_url("redis://localhost:6379")

async def push_to_redis(events: list[dict]):
    for ev in events:
        await redis.xadd("cursor:events", ev)
Enter fullscreen mode Exit fullscreen mode

Redis gives you O(1) writes and easy range queries (XRANGE). The downside is data loss on restart unless you enable persistence, and you lose relational querying capabilities.

When NOT to use Redis: when you need to join events with other domain tables (e.g., linking a change to a ticket). In that case PostgreSQL is the safer bet.


Building a dashboard to visualize code changes

How can you turn stored events into a useful UI? I built a tiny Vue front-end that calls a FastAPI endpoint /dashboard/events. The endpoint reads from PostgreSQL and returns a paginated list with optional filters.

from fastapi import Query

@router.get("/dashboard/events")
async def dashboard_events(
    skip: int = Query(0, ge=0),
    limit: int = Query(20, le=100),
    file_path: str | None = None,
    db: AsyncSession = Depends(get_db)
):
    query = select(CursorEvent).order_by(CursorEvent.created_at.desc())
    if file_path:
        query = query.where(CursorEvent.file_path == file_path)
    result = await db.execute(query.offset(skip).limit(limit))
    return result.scalars().all()
Enter fullscreen mode Exit fullscreen mode

The UI shows a table with diff previews, timestamps, and the original prompt. Clicking a row expands the diff. I added a simple heat-map that counts changes per file; the query is a GROUP BY file_path aggregation.

Pitfall: pulling the full diff for every row can bloat the response. Send only a snippet (first 200 characters) and let the UI request the full diff on demand via another endpoint.


Security and best-practice considerations

What should you watch out for when exposing AI-generated code data? A few hard-earned lessons:

  1. Never expose raw diff data to unauthenticated users. Diffs may contain secrets that the AI unintentionally inserted (API keys, passwords). Always scrub before rendering. A quick regex replace works for most cases, but consider a dedicated secret-detection step – see my AI generated code platform security checklist.

  2. Rate-limit your own endpoint to avoid cascading failures if Cursor imposes stricter limits. A simple slowapi decorator can throttle calls to /cursor/events.

  3. Validate the schema of incoming JSON. Cursor can evolve its payload format; using Pydantic models catches mismatches early:

from pydantic import BaseModel, Field

class CursorEventModel(BaseModel):
    id: str
    file_path: str
    change_type: str
    prompt: str | None
    diff: str | None
    created_at: datetime
Enter fullscreen mode Exit fullscreen mode
  1. Log responsibly. Store only event IDs and timestamps in your audit log. Full diffs belong in a secure data store, not in plain-text logs that may be shipped to external services.

If you’re already wrestling with code-quality regressions caused by AI, check out my post on Fixing AI Generated Code Quality Issues in Production for additional context.


FAQ

How often can I poll the Cursor API?

The official limit is 30 requests per minute. Exceeding it returns a 429 response. Use exponential back-off or a background worker that respects the limit.

Can I use the API without FastAPI?

Absolutely. The endpoint is language-agnostic; any HTTP client can call it. FastAPI just makes integration painless for Python services.

What happens if an event’s diff contains a secret?

Treat it as a security incident. Scrub the diff before persisting, rotate the leaked secret, and consider adding the event to a secret-detection pipeline.

Is there a webhook alternative?

Cursor currently only offers a pull-based API. If you need push notifications you’ll have to implement a poller that writes to a message queue (e.g., RabbitMQ) and lets downstream services consume the events.


Key Takeaways

  • The Cursor AI Code Tracking API provides JSON change events; authenticate with a bearer token stored in an env var.
  • Use httpx.AsyncClient inside FastAPI to fetch events without blocking.
  • Persist events in PostgreSQL for relational queries or Redis streams for fast, temporary analytics.
  • Build a lightweight dashboard that filters and paginates results; avoid sending full diffs unless requested.
  • Scrub secrets, enforce rate limits, and validate payloads to keep the system secure.

If you’ve hit a wall trying to stitch these pieces together, I’m happy to help you get a production-ready pipeline up and running. Feel free to reach out via the hire page and we can tackle the rough spots together.

Top comments (0)