api-test-generator

api-test-generator is a skill for Claude Code from matteocervelli/llms. It costs 31 tokens per session (5,138 once invoked), scanned A, original, MIT.

A guide that generates tests for REST and GraphQL API endpoints. APIs are interfaces that let software exchange requests and responses over a network.

In plain words
What is it for?
It helps test HTTP methods, successful and error responses, authentication and authorization, request parameters and bodies, response schemas, and error handling.
Why use it?
It helps find incorrect status codes, authentication failures, validation problems, and mismatches between requested and returned data.

Skill for Claude Code

Written for Claude Code: allowed-tools in frontmatter.

Good fit It helps test HTTP methods, successful and error responses, authentication and authorization, request parameters and bodies, response schemas, and error handling.

Compare 6 skills from other repositories ↓
Install with agentmods
npx agentmods add skills/matteocervelli/llms/api-test-generator
Install

Getting it into your agent

One page per mod, every tool's command on it. A separate URL per tool would split the same page into five that compete with each other.

Any agent
npx skills add matteocervelli/llms --skill api-test-generator
Clone the repo
git clone --depth 1 https://github.com/matteocervelli/llms

Made for: Claude Code.

Wrote this? Show the measurements

A badge with what this costs and how it scanned, read live from this page, so it follows the numbers instead of freezing them. Markdown for a README, HTML for a documentation site or a project page.

agentmods badge for api-test-generator

README.md
[![agentmods](https://agentmods.dev/badge/skills/matteocervelli/llms/api-test-generator/github.svg)](https://agentmods.dev/skills/matteocervelli/llms/api-test-generator)
Your own site
<a href="https://agentmods.dev/skills/matteocervelli/llms/api-test-generator"><img src="https://agentmods.dev/badge/skills/matteocervelli/llms/api-test-generator/github.svg" alt="Measured on agentmods" height="20"></a>

Or the 80×15 button, for a site that already has a row of RSS and ATOM ones. Only the verdict fits; the numbers stay here.

agentmods 80×15 button for api-test-generator

Your own site · 80×15
<a href="https://agentmods.dev/skills/matteocervelli/llms/api-test-generator"><img src="https://agentmods.dev/badge/skills/matteocervelli/llms/api-test-generator.svg" alt="Reviewed on agentmods" width="80" height="20"></a>
Per session 31 Skills are progressive disclosure: only the name and description are preloaded; the body loads when the skill is used.
When invoked 5,138 The whole file, excluding the scripts and references it only reads on demand.
Security scan A 0 findings. A grade says what 26 rules found in the file — not that it is safe.
Origin original No closer match found in the catalogue.
Token cost

What it costs to keep this loaded

Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.

ModelPer sessionOnce invoked
Fable 5.1 $0.00031 $0.05138
Opus 5.5 $0.00012 $0.02055
Sonnet 5.5 $0.00006 $0.01028
Haiku 4.5 $0.00003 $0.00514

Measured 1mo ago against content hash 3a809a20dc42, method: parsed. Prices are Anthropic first-party input rates as of 2026-10-07, from the pricing page.

Security

Grade A, and why

api-test-generator scanned grade A with 0 findings against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 1mo ago.

A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.

Nothing flagged

None of the 26 patterns this scan looks for appear in this file: no shell pipes, no recursive deletes, no credential paths, no hidden text, no instruction-override or anti-refusal phrasing, no agent-config snooping. That is not a guarantee, it is the absence of the things that are checkable.

.archive/claude-v1/skills/api-test-generator/SKILL.md · 826 lines

How it starts

The opening of the file, as written. The whole thing — 826 lines — stays where its author put it; the contents beside it link to each section on GitHub.

API Test Generator Skill

Purpose

This skill generates comprehensive integration tests for API endpoints, covering all HTTP methods, status codes, authentication, authorization, request/response validation, and error handling.

When to Use

  • Generate tests for REST API endpoints
  • Test GraphQL API queries and mutations
  • Validate API request/response contracts
  • Test API authentication and authorization
  • Verify API error handling and status codes

API Test Coverage

For each endpoint, test:

  • ✅ Success cases (200, 201, 204)
  • ✅ Validation errors (400, 422)
  • ✅ Authentication errors (401)
  • ✅ Authorization errors (403)
  • ✅ Not found errors (404)
  • ✅ Conflict errors (409)
  • ✅ Server errors (500)
  • ✅ Request body validation
  • ✅ Query parameter validation
  • ✅ Response schema validation

API Test Generation Workflow

1. Analyze API Routes

Identify endpoints:

# Read route definitions
cat src/routes/users.py
cat src/controllers/user_controller.py

# Identify:
# - Endpoints and HTTP methods
# - Path parameters
# - Query parameters
# - Request body schemas
# - Response schemas
# - Authentication requirements
# - Authorization requirements

Map endpoints:

GET    /api/users              - List users (public)
GET    /api/users/:id          - Get user (public)
POST   /api/users              - Create user (admin only)
PUT    /api/users/:id          - Update user (auth required, owner or admin)
DELETE /api/users/:id          - Delete user (auth required, owner or admin)

Deliverable: API endpoint inventory


2. Generate REST API Test Suite

Test file structure:

"""
Integration tests for Users API endpoints.

Endpoints tested:
- GET    /api/users
- GET    /api/users/:id
- POST   /api/users
- PUT    /api/users/:id
- PATCH  /api/users/:id
- DELETE /api/users/:id
"""

import pytest
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session

from src.models import User


# ============================================================================
# GET /api/users - List Users
# ============================================================================

class TestGetUsers:
    """Tests for GET /api/users endpoint."""

    def test_get_users_empty_returns_empty_list(self, client: TestClient):
        """Test GET /api/users with no users returns empty list."""
        # Act
        response = client.get("/api/users")

        # Assert
        assert response.status_code == 200
        assert response.json() == []

    def test_get_users_returns_user_list(
        self, client: TestClient, db: Session
    ):
        """Test GET /api/users returns list of users."""
        # Arrange: Create test users
        users = [
            User(name=f"User {i}", email=f"user{i}@example.com")
            for i in range(3)
        ]
        db.add_all(users)
        db.commit()

        # Act
        response = client.get("/api/users")

        # Assert
        assert response.status_code == 200
        data = response.json()
        assert len(data) == 3
        assert all("id" in user for user in data)
        assert all("name" in user for user in data)
        assert all("email" in user for user in data)

    def test_get_users_with_pagination(
        self, client: TestClient, db: Session
    ):
        """Test GET /api/users with pagination parameters."""
        # Arrange: Create 10 users
        users = [
            User(name=f"User {i}", email=f"user{i}@example.com")
            for i in range(10)
        ]
        db.add_all(users)
        db.commit()

        # Act
        response = client.get("/api/users?limit=5&offset=0")

        # Assert
        assert response.status_code == 200
        data = response.json()
        assert len(data) == 5

    def test_get_users_with_search_filter(
        self, client: TestClient, db: Session
    ):
        """Test GET /api/users with search query."""
        # Arrange
        users = [
            User(name="Alice", email="[email protected]"),
            User(name="Bob", email="[email protected]"),
            User(name="Alice Smith", email="[email protected]"),
        ]
        db.add_all(users)
        db.commit()

        # Act
        response = client.get("/api/users?search=alice")

        # Assert
        assert response.status_code == 200
        data = response.json()
        assert len(data) == 2
        assert all("alice" in user["name"].lower() for user in data)

    def test_get_users_invalid_limit_returns_400(self, client: TestClient):
        """Test GET /api/users with invalid limit parameter."""
        # Act
        response = client.get("/api/users?limit=-1")

        # Assert
        assert response.status_code == 400
        assert "limit" in response.json()["detail"].lower()


# ============================================================================
# GET /api/users/:id - Get User by ID
# ============================================================================

class TestGetUserById:
    """Tests for GET /api/users/:id endpoint."""

    def test_get_user_by_id_returns_user(
        self, client: TestClient, test_user: User
    ):
        """Test GET /api/users/:id returns specific user."""
        # Act
        response = client.get(f"/api/users/{test_user.id}")

        # Assert
        assert response.status_code == 200
        data = response.json()
        assert data["id"] == test_user.id
        assert data["name"] == test_user.name
        assert data["email"] == test_user.email
        assert "password" not in data  # Sensitive data not included

    def test_get_user_nonexistent_id_returns_404(self, client: TestClient):
        """Test GET /api/users/:id with nonexistent ID returns 404."""
        # Act
        response = client.get("/api/users/99999")

        # Assert
        assert response.status_code == 404
        assert "not found" in response.json()["detail"].lower()

    def test_get_user_invalid_id_format_returns_400(self, client: TestClient):
        """Test GET /api/users/:id with invalid ID format returns 400."""
        # Act
        response = client.get("/api/users/invalid-id")

        # Assert
        assert response.status_code == 400


# ============================================================================
# POST /api/users - Create User
# ============================================================================

class TestCreateUser:
    """Tests for POST /api/users endpoint."""

    def test_create_user_valid_data_returns_created(
        self, client: TestClient, db: Session, admin_headers: dict
    ):
        """Test POST /api/users with valid data creates user."""
        # Arrange
        user_data = {
            "name": "New User",
            "email": "[email protected]",
            "password": "SecurePass123"
        }

        # Act
        response = client.post(
            "/api/users",
            json=user_data,
            headers=admin_headers
        )

        # Assert
        assert response.status_code == 201
        data = response.json()
        assert data["name"] == user_data["name"]
        assert data["email"] == user_data["email"]
        assert "id" in data
        assert "password" not in data  # Password not returned
        assert "created_at" in data

        # Verify in database
        user = db.query(User).filter_by(email=user_data["email"]).first()
        assert user is not None
        assert user.name == user_data["name"]

    def test_create_user_missing_required_field_returns_400(
        self, client: TestClient, admin_headers: dict
    ):
        """Test POST /api/users with missing required field returns 400."""
        # Arrange: Missing email
        invalid_data = {
            "name": "User",
            "password": "password"
        }

        # Act
        response = client.post(
            "/api/users",
            json=invalid_data,
            headers=admin_headers
        )

        # Assert
        assert response.status_code == 400
        assert "email" in response.json()["detail"].lower()

    def test_create_user_invalid_email_returns_400(
        self, client: TestClient, admin_headers: dict
    ):
        """Test POST /api/users with invalid email format returns 400."""
        # Arrange
        invalid_data = {
            "name": "User",
            "email": "not-an-email",
            "password": "password"
        }

        # Act
        response = client.post(
            "/api/users",
            json=invalid_data,
            headers=admin_headers
        )

        # Assert
        assert response.status_code == 400
        assert "email" in response.json()["detail"].lower()

    def test_create_user_weak_password_returns_400(
        self, client: TestClient, admin_headers: dict
    ):
        """Test POST /api/users with weak password returns 400."""
        # Arrange
        invalid_data = {
            "name": "User",
            "email": "[email protected]",
            "password": "123"  # Too short
        }

        # Act
        response = client.post(
            "/api/users",
            json=invalid_data,
            headers=admin_headers
        )

        # Assert
        assert response.status_code == 400
        assert "password" in response.json()["detail"].lower()

    def test_create_user_duplicate_email_returns_409(
        self, client: TestClient, test_user: User, admin_headers: dict
    ):
        """Test POST /api/users with duplicate email returns 409."""
        # Arrange
        duplicate_data = {
            "name": "Another User",
            "email": test_user.email,  # Duplicate
            "password": "password"
        }

        # Act
        response = client.post(
            "/api/users",
            json=duplicate_data,
            headers=admin_headers
        )

        # Assert
        assert response.status_code == 409
        assert "already exists" in response.json()["detail"].lower()

    def test_create_user_without_auth_returns_401(
        self, client: TestClient
    ):
        """Test POST /api/users without authentication returns 401."""
        # Arrange
        user_data = {
            "name": "User",
            "email": "[email protected]",
            "password": "password"
        }

        # Act
        response = client.post("/api/users", json=user_data)

        # Assert
        assert response.status_code == 401

    def test_create_user_as_non_admin_returns_403(
        self, client: TestClient, auth_headers: dict
    ):
        """Test POST /api/users as non-admin user returns 403."""
        # Arrange
        user_data = {
            "name": "User",
            "email": "[email protected]",
            "password": "password"
        }

        # Act
        response = client.post(
            "/api/users",
            json=user_data,
            headers=auth_headers  # Regular user, not admin
        )

        # Assert
        assert response.status_code == 403


# ============================================================================
# PUT /api/users/:id - Update User (Full Replace)
# ============================================================================

class TestUpdateUser:
    """Tests for PUT /api/users/:id endpoint."""

    def test_update_user_own_account_returns_updated(
        self, client: TestClient, test_user: User, auth_headers: dict, db: Session
    ):
        """Test PUT /api/users/:id to update own account."""
        # Arrange
        update_data = {
            "name": "Updated Name",
            "email": test_user.email  # Same email
        }

        # Act
        response = client.put(
            f"/api/users/{test_user.id}",
            json=update_data,
            headers=auth_headers
        )

        # Assert
        assert response.status_code == 200
        data = response.json()
        assert data["name"] == "Updated Name"

        # Verify in database
        db.refresh(test_user)
        assert test_user.name == "Updated Name"

    def test_update_user_other_account_as_admin_succeeds(
        self, client: TestClient, test_user: User, admin_headers: dict, db: Session
    ):
        """Test PUT /api/users/:id as admin to update other user."""
        # Arrange
        update_data = {
            "name": "Admin Updated",
            "email": test_user.email
        }

        # Act
        response = client.put(
            f"/api/users/{test_user.id}",
            json=update_data,
            headers=admin_headers
        )

        # Assert
        assert response.status_code == 200

    def test_update_user_other_account_as_regular_user_returns_403(
        self, client: TestClient, db: Session, auth_headers: dict
    ):
        """Test PUT /api/users/:id to update other user returns 403."""
        # Arrange: Create another user
        other_user = User(name="Other", email="[email protected]")
        db.add(other_user)
        db.commit()

        update_data = {"name": "Unauthorized Update"}

        # Act
        response = client.put(
            f"/api/users/{other_user.id}",
            json=update_data,
            headers=auth_headers  # Regular user, not admin
        )

        # Assert
        assert response.status_code == 403

    def test_update_user_nonexistent_returns_404(
        self, client: TestClient, admin_headers: dict
    ):
        """Test PUT /api/users/:id with nonexistent ID returns 404."""
        # Arrange
        update_data = {"name": "Updated"}

        # Act
        response = client.put(
            "/api/users/99999",
            json=update_data,
            headers=admin_headers
        )

        # Assert
        assert response.status_code == 404


# ============================================================================
# PATCH /api/users/:id - Partial Update User
# ============================================================================

class TestPatchUser:
    """Tests for PATCH /api/users/:id endpoint."""

    def test_patch_user_single_field_updates(
        self, client: TestClient, test_user: User, auth_headers: dict, db: Session
    ):
        """Test PATCH /api/users/:id updates only specified field."""
        # Arrange
        original_email = test_user.email
        patch_data = {"name": "Patched Name"}

        # Act
        response = client.patch(
            f"/api/users/{test_user.id}",
            json=patch_data,
            headers=auth_headers
        )

        # Assert
        assert response.status_code == 200
        data = response.json()
        assert data["name"] == "Patched Name"
        assert data["email"] == original_email  # Unchanged

        # Verify in database
        db.refresh(test_user)
        assert test_user.name == "Patched Name"
        assert test_user.email == original_email


# ============================================================================
# DELETE /api/users/:id - Delete User
# ============================================================================

class TestDeleteUser:
    """Tests for DELETE /api/users/:id endpoint."""

    def test_delete_user_own_account_returns_no_content(
        self, client: TestClient, test_user: User, auth_headers: dict, db: Session
    ):
        """Test DELETE /api/users/:id to delete own account."""
        # Act
        response = client.delete(
            f"/api/users/{test_user.id}",
            headers=auth_headers
        )

        # Assert
        assert response.status_code == 204

        # Verify in database
        deleted_user = db.query(User).filter_by(id=test_user.id).first()
        assert deleted_user is None

    def test_delete_user_as_admin_succeeds(
        self, client: TestClient, test_user: User, admin_headers: dict, db: Session
    ):
        """Test DELETE /api/users/:id as admin."""
        # Act
        response = client.delete(
            f"/api/users/{test_user.id}",
            headers=admin_headers
        )

        # Assert
        assert response.status_code == 204

    def test_delete_user_other_account_returns_403(
        self, client: TestClient, db: Session, auth_headers: dict
    ):
        """Test DELETE /api/users/:id to delete other user returns 403."""
        # Arrange
        other_user = User(name="Other", email="[email protected]")
        db.add(other_user)
        db.commit()

        # Act
        response = client.delete(
            f"/api/users/{other_user.id}",
            headers=auth_headers
        )

        # Assert
        assert response.status_code == 403

    def test_delete_user_without_auth_returns_401(
        self, client: TestClient, test_user: User
    ):
        """Test DELETE /api/users/:id without auth returns 401."""
        # Act
        response = client.delete(f"/api/users/{test_user.id}")

        # Assert
        assert response.status_code == 401

    def test_delete_user_nonexistent_returns_404(
        self, client: TestClient, admin_headers: dict
    ):
        """Test DELETE /api/users/:id with nonexistent ID returns 404."""
        # Act
        response = client.delete(
            "/api/users/99999",
            headers=admin_headers
        )

        # Assert
        assert response.status_code == 404

Read the full file on GitHub · 826 lines

Changes

What this file has done since we first saw it

Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.

  1. 1mo ago First seen · 826 lines · 31 tokens per session scan A 3a809a20dc42

Subscribe to this mod's changes

api-test-generator is a skill published in the GitHub repository matteocervelli/llms (25 stars, last pushed 5mo ago), licensed MIT. It adds 31 tokens to every session and 5,138 once invoked, about $0.0001 per session on Opus 5.5. A static security scan graded it A with 0 findings. No closer match exists in the catalogue, so it is treated as the original; first seen 2026-09-01.

Related

Other skills, from other repositories

server-side-calls

Call tRPC procedures directly from server code using t.createCallerFactory() and router.createCaller(context) for integration testing, internal server logic, and custom API endpoints. Catch TRPCError and extract HTTP status with getHTTPStatusCodeFromError(). Error handling via onError option.

trpc/trpc · 61 tokens

mem0-test-integration

Verify a Mem0 integration produced by /mem0-integrate. Runs in the same workspace on the same branch (loose coupling) — installs dependencies, runs the repo's native test suite, then exercises a real end-to-end smoke flow against the user's API key. Produces a scorecard. TRIGGER when: user has just run /mem0-integrate…

mem0ai/mem0 · 207 tokens

prowler-test-api

Testing patterns for Prowler API: JSON:API, Celery tasks, RLS isolation, RBAC. Trigger: When writing tests for api/ (JSON:API requests/assertions, cross-tenant isolation, RBAC, Celery tasks, viewsets/serializers).

prowler-cloud/prowler · 62 tokens

python-sdk

Implement or modify Python SDK behavior under python/composio, including tools, toolkits, sessions, auth configs, connected accounts, client integration, and shared Python models. Use for Python core runtime/API work; pair with python-testing and cross-sdk-parity when TypeScript must match.

ComposioHQ/composio · 60 tokens

convex-test

Generate convex-test tests for the app's Convex functions.

openclaw/clawhub · 16 tokens

voiden

Create and edit Voiden .void files for API testing. Covers the .void file format and all enabled extension block types.

VoidenHQ/voiden · 28 tokens