Skill: derick001/api-endpoint-tester (version 1.0.1)

--- name: api-endpoint-tester description: CLI tool to test REST API endpoints with various HTTP methods, headers, and payloads. version: 1.0.0 author: skill-factory metadata: openclaw: requires: bins: - python3 packages: - requests --- # API Endpoint Tester ## What This Does A simple CLI tool to send HTTP requests to REST API endpoints and validate responses. Supports GET, POST, PUT, DELETE, PATCH methods with custom headers and request bodies (JSON or form data). ## When To Use - You need to test API endpoints manually or in scripts - You want to validate HTTP status codes and response formats - You're debugging API integrations and need quick requests - You need to check if an endpoint is reachable and responding correctly ## Usage Basic GET request: python3 scripts/main.py run --url "https://api.example.com/users" --method GET POST with JSON body: python3 scripts/main.py run --url "https://api.example.com/users" --method POST --body '{"name": "John", "email": "[REDACTED_SECRET]"}' With custom headers: python3 scripts/main.py run --url "https://api.example.com/users" --method GET --headers '{"Authorization": "Bearer token123"}' ## Examples ### Example 1: Simple GET request ```bash python3 scripts/main.py run --url "https://jsonplaceholder.typicode.com/posts/1" --method GET ``` Output: ```json { "status": "success", "status_code": 200, "headers": { "content-type": "application/json; charset=utf-8" }, "body": { "userId": 1, "id": 1, "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto" }, "response_time_ms": 245 } ``` ### Example 2: POST with validation ```bash python3 scripts/main.py run --url "https://jsonplaceholder.typicode.com/posts" --method POST --body '{"title": "foo", "body": "bar", "userId": 1}' --expected-status 201 ``` ## Requirements - Python 3.x - `requests` library (install via pip if not available) ## Limitations - This is a CLI tool, not an auto-integration plugin - Does not support WebSocket or streaming endpoints - Limited to HTTP/HTTPS protocols (no gRPC, GraphQL, etc.) - No built-in authentication beyond headers - Does not save test suites or history (single request at a time) - Timeouts default to 10 seconds

path: README.md
content: # API Endpoint Tester

A command-line tool for testing REST API endpoints with various HTTP methods, headers, and payloads.

## Installation

This skill requires Python 3.x and the `requests` library.

```bash
pip install requests
```

## Quick Start

```bash
# Basic GET request
python3 scripts/main.py run --url "https://jsonplaceholder.typicode.com/posts/1" --method GET

# POST with JSON body
python3 scripts/main.py run --url "https://jsonplaceholder.typicode.com/posts" --method POST --body '{"title": "test", "body": "content"}'

# With custom headers
python3 scripts/main.py run --url "https://api.example.com/data" --method GET --headers '{"Authorization": "Bearer token123", "User-Agent": "MyApp"}'
```

## Command Reference

```
python3 scripts/main.py run [OPTIONS]
```

Options:
- `--url`: Target URL (required)
- `--method`: HTTP method (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS). Default: GET
- `--headers`: JSON string of headers. Default: {}
- `--body`: Request body (JSON or form data). Default: null
- `--timeout`: Request timeout in seconds. Default: 10
- `--expected-status`: Expected HTTP status code. If provided, will check against actual status.
- `--verify-ssl`: Verify SSL certificates. Default: true
- `--allow-redirects`: Follow redirects. Default: true
- `--output-file`: Save response to file (optional)

## Response Format

The tool returns a JSON object with the following structure:

```json
{
  "status": "success" | "error",
  "error_message": "Description if status is error",
  "status_code": 200,
  "headers": {},
  "body": {},
  "response_time_ms": 123,
  "url": "https://example.com",
  "method": "GET"
}
```

## Examples

### Basic GET Request

```bash
python3 scripts/main.py run --url "https://jsonplaceholder.typicode.com/posts/1"
```

### POST with JSON Body

```bash
python3 scripts/main.py run \
  --url "https://jsonplaceholder.typicode.com/posts" \
  --method POST \
  --body '{"title": "My Post", "body": "Content here", "userId": 1}' \
  --headers '{"Content-Type": "application/json"}'
```

### DELETE Request with Authentication

```bash
python3 scripts/main.py run \
  --url "https://api.example.com/users/123" \
  --method DELETE \
  --headers '{"Authorization": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9"}'
```

### Validate Status Code

```bash
python3 scripts/main.py run \
  --url "https://httpbin.org/status/404" \
  --expected-status 404
```

If the actual status code doesn't match the expected, the tool returns an error.

## Error Handling

- Invalid URLs: Returns error with message
- Connection errors: Returns error with details
- Timeouts: Returns error after specified timeout
- Invalid JSON in body/headers: Returns parse error
- SSL verification failures: Returns error

## Use Cases

1. **API Development**: Quickly test endpoints during development
2. **Monitoring**: Check if APIs are responding correctly
3. **Debugging**: Inspect response headers and bodies
4. **Integration Testing**: Validate API contracts in scripts
5. **Documentation Examples**: Generate example requests/responses

## Limitations

- Does not support WebSocket or streaming endpoints
- Limited to HTTP/HTTPS protocols
- No built-in authentication beyond headers (Basic Auth via headers)
- No cookie jar or session persistence
- Single request at a time (no batch processing)
- Response size limited by memory

## License

MIT

path: scripts/main.py
content: #!/usr/bin/env python3
"""
API Endpoint Tester - CLI tool to test REST API endpoints
"""

import argparse
import json
import os
import sys
import time
from typing import Dict, Any, Optional
import requests
from requests.exceptions import RequestException, Timeout, ConnectionError, SSLError

def make_request(
    url: str,
    method: str = "GET",
    headers: Optional[Dict[str, str]] = None,
    body: Optional[Any] = None,
    timeout: int = 10,
    verify_ssl: bool = True,
    allow_redirects: bool = True
) -> Dict[str, Any]:
    """
    Make an HTTP request and return standardized response.
    """
    start_time = time.time()
    
    # Normalize method
    method = method.upper()
    if method not in ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]:
        raise ValueError(f"Unsupported HTTP method: {method}")
    
    # Prepare headers
    request_headers = headers or {}
    
    # Prepare body
    data = None
    json_data = None
    
    if body is not None:
        # If body is a dict and Content-Type suggests JSON, send as JSON
        content_type = request_headers.get("Content-Type", "").lower()
        if isinstance(body, dict) and ("application/json" in content_type or content_type == ""):
            json_data = body
        else:
            data = body
    
    try:
        response = requests.request(
            method=method,
            url=url,
            headers=request_headers,
            json=json_data,
            data=data,
            timeout=timeout,
            verify=verify_ssl,
            allow_redirects=allow_redirects
        )
        response_time_ms = int((time.time() - start_time) * 1000)
        
        # Try to parse JSON response, fall back to text
        try:
            response_body = response.json()
        except ValueError:
            response_body = response.text
        
        return {
            "status": "success",
            "status_code": response.status_code,
            "headers": dict(response.headers),
            "body": response_body,
            "response_time_ms": response_time_ms,
            "url": url,
            "method": method
        }
    
    except Timeout:
        return {
            "status": "error",
            "error_message": f"Request timed out after {timeout} seconds",
            "url": url,
            "method": method
        }
    except ConnectionError:
        return {
            "status": "error",
            "error_message": "Connection failed - check URL and network",
            "url": url,
            "method": method
        }
    except SSLError:
        return {
            "status": "error",
            "error_message": "SSL certificate verification failed",
            "url": url,
            "method": method
        }
    except RequestException as e:
        return {
            "status": "error",
            "error_message": str(e),
            "url": url,
            "method": method
        }
    except Exception as e:
        return {
            "status": "error",
            "error_message": f"Unexpected error: {str(e)}",
            "url": url,
            "method": method
        }

def parse_json_arg(value: str) -> Any:
    """Parse JSON string argument, or return raw string if invalid."""
    if not value:
        return None
    try:
        return json.loads(value)
    except json.JSONDecodeError:
        # If it's not JSON, return as string
        return value

def run(args):
    """Execute the API test."""
    # Parse headers and body from JSON strings
    headers = parse_json_arg(args.headers)
    body = parse_json_arg(args.body)
    
    # Validate URL
    if not args.url:
        return {
            "status": "error",
            "error_message": "URL is required"
        }
    
    # Validate URL format
    if not args.url.startswith(("http://", "https://")):
        return {
            "status": "error",
            "error_message": "URL must start with http:// or https://"
        }
    
    # Validate timeout
    if args.timeout <= 0:
        return {
            "status": "error",
            "error_message": "Timeout must be positive"
        }
    
    # Validate output file path is within skill directory
    if args.output_file:
        try:
            skill_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
            output_path = os.path.abspath(args.output_file)
            if not output_path.startswith(skill_dir):
                return {
                    "status": "error",
                    "error_message": f"Output file must be within skill directory: {skill_dir}"
                }
        except Exception as e:
            return {
                "status": "error",
                "error_message": f"Invalid output file path: {str(e)}"
            }
    
    # Make the request
    result = make_request(
        url=args.url,
        method=args.method,
        headers=headers,
        body=body,
        timeout=args.timeout,
        verify_ssl=args.verify_ssl,
        allow_redirects=args.allow_redirects
    )
    
    # Check expected status if provided
    if args.expected_status is not None and result.get("status") == "success":
        if result.get("status_code") != args.expected_status:
            result["status"] = "error"
            result["error_message"] = f"Expected status {args.expected_status}, got {result.get('status_code')}"
    
    # Output to file if requested
    if args.output_file and result.get("status") == "success":
        try:
            with open(args.output_file, "w") as f:
                json.dump(result, f, indent=2)
        except Exception as e:
            result["status"] = "error"
            result["error_message"] = f"Failed to write output file: {str(e)}"
    
    return result

def main():
    parser = argparse.ArgumentParser(
        description="Test REST API endpoints with various HTTP methods, headers, and payloads."
    )
    parser.add_argument(
        "command",
        choices=["run", "help"],
        help="Command to execute"
    )
    
    # Required arguments for 'run'
    parser.add_argument(
        "--url",
        help="Target URL for the request"
    )
    parser.add_argument(
        "--method",
        default="GET",
        choices=["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"],
        help="HTTP method (default: GET)"
    )
    parser.add_argument(
        "--headers",
        default="{}",
        help="JSON string of headers (default: {})"
    )
    parser.add_argument(
        "--body",
        help="Request body (JSON or raw string)"
    )
    parser.add_argument(
        "--timeout",
        type=int,
        default=10,
        help="Request timeout in seconds (default: 10)"
    )
    parser.add_argument(
        "--expected-status",
        type=int,
        help="Expected HTTP status code (will error if mismatch)"
    )
    parser.add_argument(
        "--verify-ssl",
        type=lambda x: x.lower() == "true",
        default=True,
        help="Verify SSL certificates (default: true)"
    )
    parser.add_argument(
        "--allow-redirects",
        type=lambda x: x.lower() == "true",
        default=True,
        help="Follow redirects (default: true)"
    )
    parser.add_argument(
        "--output-file",
        help="Save response to file (optional)"
    )
    
    args = parser.parse_args()
    
    if args.command == "run":
        result = run(args)
        print(json.dumps(result, indent=2))
    else:
        parser.print_help()

if __name__ == "__main__":
    main()

Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)