Skip to content

Latest commit

 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PURL.

PURL

persistent api test agent

Python FastAPI React TypeScript Claude

PURL is an agentic API testing tool. You write a plain-English user story describing how your API should behave, and PURL takes it from there. It generates test cases, executes them against a live endpoint, and streams the results back in real time. If something goes wrong mid-run, it reanalyzes and self-corrects on the next case.

It sits one layer above tools like Postman and Bruno. Those tools require you to define every request and assertion manually. PURL infers what to test from a natural language description and runs the full cycle on its own.

v1 targets solo developers testing their own APIs locally during active development. It is not a hosted service or a team tool.


how it works

PURL implements a genuine agentic loop, not a one-shot prompt. The reasoning layer and the execution layer are strictly separated.

user story
    |
    v
claude plans test cases
    |
    v
tool_use: http_request  -->  fastapi executes HTTP call
    |                               |
    v                               v
tool_result: raw response  <--------
    |
    v
claude evaluates: passed / failed / error
    |
    v
SSE stream --> frontend card resolves
    |
    v
repeat until generate_report

A few things worth calling out:

  • Self-correction: if a request returns an unexpected shape (wrong field name, missing required field), Claude observes the response, adjusts, and retries with a corrected request on the next case. You can watch this happen in real time.
  • Token carry-forward: if a test case calls a login endpoint and gets back a bearer token, PURL extracts it automatically and injects it into all subsequent authenticated requests. No manual step.
  • Automatic re-auth: if a token expires mid-run and a protected endpoint returns 401, PURL silently re-authenticates, gets a fresh token, and resumes from the failed case. The re-auth banner appears inline in the stream right where it happened.
  • OpenAPI schema fetch: at run start PURL attempts GET {baseURL}/openapi.json. If it finds a schema, it passes it to Claude as context for more precise test case generation. If not, Claude works from the story alone.

stack

layer tech
backend FastAPI, Python
frontend React, TypeScript
AI Anthropic Claude (tool use API)
HTTP client httpx (async)
streaming Server-Sent Events (SSE)

project structure

purl/
  backend/
    agent/
      loop.py          # core agentic loop -- tool_use / tool_result cycle
      prompts.py       # system prompt and run prompt builders
      tools.py         # tool definitions passed to Claude
    execution/
      http_request.py  # fires actual HTTP calls, measures latency
      auth_handler.py  # re-auth and token extraction
      schema_fetch.py  # OpenAPI schema retrieval
    streaming/
      sse.py           # SSE event formatters
    models/
      schemas.py       # all shared data types
    routers/
      run.py           # /run SSE endpoint
      auth.py          # /get-token endpoint
    config.py
    main.py
  frontend/
    src/
      components/
        InputPanel/    # user story, base URL, auth, slider, run button
        OutputPanel/   # streaming terminal, test cards, final report
        TestCard/      # individual test case card
      hooks/
        useRun.ts      # SSE consumer, run state
        useAuth.ts     # Get Token flow
      types/
        run.ts         # frontend type definitions

getting started

prerequisites

  • Python 3.11+
  • Node.js 18+
  • An Anthropic API key

backend

cd backend
python -m venv venv
source venv/bin/activate  # windows: venv\Scripts\activate
pip install -r requirements.txt

Create a .env file in backend/:

# Anthropic
ANTHROPIC_API_KEY=your_key_here
SONNET_MODEL=claude-sonnet-4-6
HAIKU_MODEL=claude-haiku-4-5-20251001

# Timeouts (seconds)
REQUEST_TIMEOUT_SECONDS=10
SCHEMA_TIMEOUT_SECONDS=3
AUTH_TIMEOUT_SECONDS=10

# Rate limiting
RATE_LIMIT=5/minute

# CORS Origins
CORS_ORIGINS=["http://localhost:5173","http://localhost:3000"]

Start the backend:

uvicorn main:app --reload

Backend runs on http://localhost:8000.

frontend

cd frontend
npm install
npm run dev

Frontend runs on http://localhost:5173.


usage

  1. Open http://localhost:5173
  2. Write a plain-English user story in the input panel
  3. Set your base URL (localhost is fully supported)
  4. If your API requires auth, expand the authentication section and fill in your credentials, then click Get Token to auto-fill the bearer field
  5. Adjust the test case slider (default 8, max 15)
  6. Click Run PURL

Results stream in as each test case resolves. The final report shows total, passed, failed, and errored with the total run time.

authentication

PURL supports Bearer token APIs. The auth section takes:

field description
auth endpoint login path, e.g. /auth/login
email / username identifier sent in the login request body
password password sent in the login request body
credential field the JSON field name for the identifier (default: email)
token path dot-notation path to extract the token from the login response (default: access_token)
bearer token auto-filled by Get Token, manually editable as fallback

writing good user stories

The quality of the test cases depends heavily on the story. A few things that help:

  • Focus on one feature per run. A story about login will generate sharper cases than a story about the whole API.
  • State expected behavior explicitly. "Returns 200 with an access token" gives Claude something concrete to evaluate against.
  • Include failure modes. "Invalid credentials should return 401" tells PURL to cover the unhappy path too.

A story that works well:

A user can register a new account and login to receive a bearer token. Valid registration returns 200. Duplicate accounts are rejected with 400. Logging in with valid credentials returns 200 with an access token that grants access to protected endpoints. Invalid credentials are rejected with 401.


known limitations

  • Bearer token APIs only: cookie-based auth is not supported in v1
  • No run history: runs are impermanent, results are not persisted
  • Rate limits: large prompts or long runs may hit Anthropic's input token rate limit. If this happens, wait 60 seconds and retry. Automatic retry handling is deferred to v2.
  • Test case cap: runs are capped at 15 test cases to keep latency and API cost bounded

v2 roadmap

  • PostgreSQL run history and history UI
  • Recently used stories as preset chips (replaces hardcoded presets, powered by run history)
  • Expandable request detail per test case: method, URL, headers, and body that were sent
  • Cookie-based auth support
  • Export run as Bruno / Postman collection
  • Public hosting with per-session API key
  • Automatic rate limit retry
  • SSRF protection (deferred until public hosting)
  • Raise or remove the 15-case cap based on real-world run time data

why this exists

Setting up Postman and Bruno collections takes time, defining every request, writing every assertion, keeping it all in sync as your API changes. Sometimes you just want to point at an endpoint and know if it works. PURL is that shortcut.


purl reads, infers, executes, evaluates -- while you work.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages