Plain text API tests. No magic.
hitspec is a file-based HTTP API testing tool that emphasizes simplicity and readability. Write your API tests in plain text files that look like actual HTTP requests.
| Tool | hitspec Advantage |
|---|---|
| Postman | Plain text files, version-controllable, no GUI needed |
| REST Client (VSCode) | Adds assertions, captures, dependencies, stress testing |
| Hurl | Built-in stress testing, DB assertions, mock server, import/export |
| newman | Simpler file format, no collection export needed |
| curl | Full test framework on top of HTTP requests |
| k6 | Simpler syntax, no JavaScript required for basic tests |
brew install --cask abdul-hamid-achik/tap/hitspecgo install github.com/abdul-hamid-achik/hitspec/apps/cli@latestDownload from GitHub Releases.
Create a test file api.http:
@baseUrl = https://jsonplaceholder.typicode.com
### Get a post
# @name getPost
GET {{baseUrl}}/posts/1
>>>
expect status 200
expect body.id == 1
expect body.title exists
<<<
>>>capture
authorId from body.userId
<<<
### Create a post
# @name createPost
POST {{baseUrl}}/posts
Content-Type: application/json
{
"title": "Hello hitspec",
"body": "Testing made simple",
"userId": 1
}
>>>
expect status 201
expect body.id exists
<<<
### Get the post's author
# @name getAuthor
# @depends getPost
GET {{baseUrl}}/users/{{getPost.authorId}}
>>>
expect status 200
expect body.name exists
expect body.email exists
<<<Run it:
hitspec run api.http| Document | Description |
|---|---|
| Full Documentation | Mintlify-hosted docs |
| Examples | Example test files |
- Plain text test files -
.httpformat, readable and version-controllable - Interactive terminal app - Charm-powered
hitspec studio(like Postman, but file-backed and keyboard-first) - 26 assertion operators -
==,!=,>,<,contains,matches,exists,length,type,schema,snapshot, and more - 22 metadata directives -
@name,@tags,@depends,@timeout,@retry,@auth,@waitFor, and more - 17 built-in functions -
$uuid(),$timestamp(),$random(),$base64(),$sha256(),$env(), and more - 8 authentication types - Bearer, Basic, API Key, Digest, AWS Signature v4, OAuth2
- Built-in stress testing - Load test with
--stressflag, real-time metrics dashboard - Mock server - Generate mock APIs from
.httpfiles - Variable captures - Chain requests by capturing response values
- Request dependencies - Control execution order with
@depends - Multiple environments - Dev, staging, prod configurations
- Parallel execution - Run independent tests concurrently
- Multiple output formats - Console, JSON, JUnit, TAP, HTML
- Watch mode - Re-run on file changes
- Snapshot testing - Capture and compare response bodies against baselines
- Database assertions - Verify database state after HTTP requests
- Shell commands - Run scripts before/after requests
- API coverage reporting - Measure test coverage against OpenAPI specs
- curl/Insomnia/OpenAPI import - Convert existing tests
- Export to curl - Export hitspec tests as executable curl commands
- SSE support - Test Server-Sent Events endpoints
- Contract testing - Verify API contracts against providers
- Response fetch + MCP - Download one response as raw bytes, readable text, Markdown, or JSON from the CLI or a bounded MCP server
Install the official hitspec VSCode extension for full syntax support:
From VSIX (manual install):
- Build the extension:
cd apps/vscode && npm run package - In VSCode, open Command Palette (
Cmd+Shift+P/Ctrl+Shift+P) - Run "Extensions: Install from VSIX..."
- Select the generated
.vsixfile
Features:
- Full syntax highlighting for
.httpand.hitspecfiles - Code snippets for requests, assertions, captures
- Support for hitspec-specific syntax:
>>>,<<<,[[[,]]] - Variable interpolation highlighting (
{{variable}}) - Built-in function highlighting (
$uuid(),$timestamp(), etc.)
Alternatively, the REST Client extension provides basic HTTP syntax highlighting.
Generate shell completion scripts for bash, zsh, fish, or PowerShell:
# Bash
hitspec completion bash > /etc/bash_completion.d/hitspec
# Zsh
hitspec completion zsh > "${fpath[1]}/_hitspec"
# Fish
hitspec completion fish > ~/.config/fish/completions/hitspec.fish>>>
expect status == 200 # Equals
expect status != 404 # Not equals
expect body.count > 0 # Greater than
expect duration < 1000 # Less than
expect body contains "success" # Contains substring
expect body.id matches /^\d+$/ # Regex match
expect body.error !exists # Does not exist
expect body.items length 10 # Array length equals
expect body.items length > 0 # Array length greater than
expect body.tags includes "api" # Array contains
expect status in [200, 201] # Value in array
expect body.data type object # Type check
expect body schema ./schema.json # JSON Schema validation
expect body snapshot "response" # Snapshot comparison
<<<{{$uuid()}} # Generate UUID v4
{{$timestamp()}} # Unix timestamp
{{$now()}} # RFC3339 datetime
{{$date(2006-01-02)}} # Custom date format
{{$random(1, 100)}} # Random integer
{{$randomEmail()}} # Random email
{{$base64(value)}} # Base64 encode
{{$sha256(value)}} # SHA256 hash
{{$env(API_TOKEN)}} # Environment variable# @name myRequest # Request identifier
# @tags smoke, auth # Tags for filtering
# @depends login # Run after login request
# @timeout 5000 # Timeout in ms
# @retry 3 # Retry on failure
# @auth bearer {{token}} # Authentication>>>capture
token from body.access_token
userId from body.user.id
<<<
# Use in subsequent requests:
GET {{baseUrl}}/users/{{login.userId}}
Authorization: Bearer {{login.token}}Open a keyboard-first interactive workspace for editing, running, and debugging your .http files — right in the terminal:
hitspec studio # Open in the current directory
hitspec studio ./tests/ # Open a specific workspace
hitspec studio ./tests/users.http # Open the file's parent workspace
hitspec studio --read-only # Safe mode for inspectionFeatures:
- Responsive workspace: file tree, source editor, request table, tabbed response viewer
- Inline source editing with
Ctrl+Ssave to disk - Tabbed responses (Body / Headers / Assertions / Timing / Captures) with syntax-highlighted JSON
- Command palette (
Ctrl+P), fuzzy workspace search (Ctrl+F), environment switcher (Ctrl+E) - Copy a request as curl, HTTPie, Python, fetch, Go, Ruby, or wget (method, URL, headers, and body)
- Run history with drill-down, rename/duplicate files, quick ad-hoc requests
- Stress, mock, recording proxy, contract, import, cookies, and settings screens
- Toast notifications, confirm dialogs, real-time file watching, live execution progress
Flags: --read-only, --watch/-w, --env/-e, --config, --allow-shell, --allow-db, --verbose/-v, --log-format, --log-level
REST/WebSocket API server — for integrations and editors:
hitspec serve --api-only --port 8080 # JSON endpoints + WebSocket events
hitspec serve --api-only --cors # Enable CORSDevelopment:
task studio:dev # Run the interactive app locally
task serve:dev # Run the REST/WebSocket API server
task build # Build the single Go binaryhitspec run tests/ # Run all tests in directory
hitspec run tests/ --env prod # Use production environment
hitspec run tests/ --tags smoke # Run only smoke tests
hitspec run tests/ --parallel # Run in parallel
hitspec run tests/ --watch # Watch mode
hitspec run tests/ -o json # JSON output
hitspec run tests/ --update-snapshots # Update snapshot files
hitspec validate tests/ # Validate syntax
hitspec list tests/ # List all requests
hitspec import curl "curl ..." # Import from curl
hitspec import insomnia export.json # Import from Insomnia
hitspec import openapi spec.yaml # Import from OpenAPI spec
hitspec export curl tests/api.http # Export as curl commands
hitspec fetch https://example.com --format markdown -o response.md
hitspec fetch tests/api.http --name getUser --format json
hitspec diff baseline.json current.json # Compare test results
hitspec diff baseline.json current.json --threshold 10%
hitspec studio # Open the interactive app
hitspec serve --api-only --port 8080 # Start the REST/WebSocket API server
hitspec mcp serve --workspace . # Start the workspace-scoped MCP server
hitspec mcp probe --require-tool echo -- ./mcp-server
hitspec mcp call echo --args '{"message":"hi"}' -- ./mcp-serverhitspec fetch emits exactly one response body; raw is byte-exact while
text, markdown, and json provide readable or machine-safe representations.
Use hitspec mcp probe to negotiate with another MCP server and verify its
advertised tools over stdio or Streamable HTTP. Use hitspec mcp call for one
explicit tool invocation with advertised input/output schema validation;
--json makes both commands CI-friendly, and contract mismatches or tool
results with isError: true exit non-zero.
Register hitspec as a stdio MCP server with command hitspec and args
["mcp", "serve", "--workspace", "/absolute/workspace"]. It exposes bounded
fetch, request-discovery, and validation tools. In v2.18.0, configure
--search-provider tavily to add bounded hitspec_search_web discovery and
--fcheap-path /absolute/path/to/fcheap to add
hitspec_capture_webpage. Tavily stays behind Hitspec as an interchangeable
server-side provider. MCP initialization instructions name only the tools
registered for that server process and state whether hitspec_fetch is
public-only or has operator-enabled non-public HTTP(S) authority, including
private, loopback, link-local, and reserved targets. Search
results are discovery candidates;
hitspec_capture_webpage turns a public webpage into durable file.cheap
evidence, while hitspec_fetch never persists content. Agent requests are
public-network only unless the server operator explicitly enables non-public
targets. See the
fetch and
MCP references.
See the examples directory:
- Basic CRUD - GET, POST, PUT, DELETE operations
- Petstore API - Real-world API example with dependencies
- Auth Flow - Bearer, Basic, API Key, Digest authentication
- GraphQL - GraphQL queries with variables and assertions
- Stress Testing - Load testing with weights, setup, teardown
- Database - DB assertions with
>>>dbblocks - Contract Testing - Schema/type validation against API contracts
- Conditional -
@if/@unlessconditional execution - Retry -
@retry,@retryOn,@retryDelayfor flaky endpoints
@baseUrl = https://api.example.com
@token = your-api-token
@userId = 123Use {{variableName}} syntax:
GET {{baseUrl}}/users/{{userId}}
Authorization: Bearer {{token}}| Function | Description | Example |
|---|---|---|
$uuid() |
Generate UUID v4 | {{$uuid()}} → 550e8400-e29b-41d4-a716-446655440000 |
$timestamp() |
Unix timestamp (seconds) | {{$timestamp()}} → 1705612800 |
$timestampMs() |
Unix timestamp (milliseconds) | {{$timestampMs()}} → 1705612800000 |
$now() |
Current datetime (RFC3339) | {{$now()}} → 2024-01-18T12:00:00Z |
$date(format) |
Custom date format (default: YYYY-MM-DD) | {{$date(2006-01-02)}} → 2024-01-18 |
$random(min, max) |
Random integer | {{$random(1, 100)}} → 42 |
$randomString(len) |
Random alphanumeric | {{$randomString(8)}} → aB3kL9mN |
$randomEmail() |
Random email | {{$randomEmail()}} → user_abc123@example.com |
$randomAlphanumeric(len) |
Random alphanumeric | {{$randomAlphanumeric(10)}} → K8mNp2qRsT |
$base64(value) |
Base64 encode | {{$base64(hello)}} → aGVsbG8= |
$base64Decode(value) |
Base64 decode | {{$base64Decode(aGVsbG8=)}} → hello |
$md5(value) |
MD5 hash | {{$md5(hello)}} → 5d41402abc4b2a76... |
$sha256(value) |
SHA256 hash | {{$sha256(hello)}} → 2cf24dba5fb0a30e... |
$urlEncode(value) |
URL encode | {{$urlEncode(hello world)}} → hello%20world |
$urlDecode(value) |
URL decode | {{$urlDecode(hello%20world)}} → hello world |
$json(value) |
JSON passthrough | {{$json({"key": "value"})}} |
$env(name, default) |
System environment variable | {{$env(API_TOKEN)}} |
Inline in URL:
GET {{baseUrl}}/search?query=test&limit=10Explicit syntax:
GET {{baseUrl}}/search
? query = test
? limit = 10JSON Body:
POST {{baseUrl}}/users
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com"
}Form URL-Encoded:
POST {{baseUrl}}/login
Content-Type: application/x-www-form-urlencoded
& username = john
& password = secret123Multipart Form Data:
POST {{baseUrl}}/upload
>>>multipart
field name = John Doe
file @./photo.jpg
<<<GraphQL:
POST {{baseUrl}}/graphql
Content-Type: application/json
>>>graphql
query GetUser($id: ID!) {
user(id: $id) {
id
name
}
}
>>>variables
{
"id": "123"
}
<<<| Operator | Syntax | Description |
|---|---|---|
== |
expect status == 200 |
Equals |
!= |
expect body.error != null |
Not equals |
> |
expect body.count > 0 |
Greater than |
>= |
expect body.count >= 1 |
Greater than or equal |
< |
expect duration < 1000 |
Less than |
<= |
expect duration <= 500 |
Less than or equal |
| Operator | Syntax | Description |
|---|---|---|
contains |
expect body contains "success" |
Contains substring |
!contains |
expect body !contains "error" |
Does not contain |
startsWith |
expect body.url startsWith "https" |
Starts with prefix |
endsWith |
expect body.email endsWith ".com" |
Ends with suffix |
matches |
expect body.id matches /^\d+$/ |
Matches regex |
| Operator | Syntax | Description |
|---|---|---|
exists |
expect body.id exists |
Value is not null |
!exists |
expect body.error !exists |
Value is null |
type |
expect body.items type array |
Check value type (null, boolean, number, string, array, object) |
| Operator | Syntax | Description |
|---|---|---|
length |
expect body.items length 10 |
Array/string length equals |
length > |
expect body.items length > 0 |
Array/string length greater than |
length >= |
expect body.items length >= 1 |
Array/string length greater than or equal |
length < |
expect body.items length < 100 |
Array/string length less than |
length <= |
expect body.items length <= 50 |
Array/string length less than or equal |
includes |
expect body.tags includes "admin" |
Array contains value |
!includes |
expect body.tags !includes "test" |
Array does not contain |
in |
expect status in [200, 201, 204] |
Value is in array |
!in |
expect status !in [400, 404, 500] |
Value is not in array |
each |
expect body.items each type object |
Apply assertion to each element |
| Operator | Syntax | Description |
|---|---|---|
schema |
expect body schema ./schema.json |
Validate against JSON Schema |
| Operator | Syntax | Description |
|---|---|---|
snapshot |
expect body snapshot "responseName" |
Compare against saved snapshot |
| Subject | Description | Example |
|---|---|---|
status |
HTTP status code | expect status 200 |
duration |
Response time (ms) | expect duration < 1000 |
header <name> |
Response header | expect header Content-Type contains json |
body |
Full response body | expect body contains "success" |
body.<path> |
JSON path | expect body.user.name == "John" |
body[n] |
Array index | expect body[0].id exists |
| Directive | Description | Example |
|---|---|---|
@name |
Request identifier | # @name createUser |
@description |
Human-readable description | # @description Creates a user |
@tags |
Tags for filtering | # @tags smoke, auth |
@skip |
Skip request | # @skip Temporarily disabled |
@only |
Run only this request | # @only |
@timeout |
Timeout in ms | # @timeout 5000 |
@retry |
Retry attempts | # @retry 3 |
@retryDelay |
Delay between retries (ms) | # @retryDelay 1000 |
@retryOn |
Status codes that trigger retry | # @retryOn 500, 502, 503 |
@depends |
Dependencies | # @depends login, setupData |
@if |
Conditional execution | # @if {{runTests}} |
@unless |
Conditional skip | # @unless {{skipAuth}} |
@auth |
Authentication method | # @auth bearer {{token}} |
@before |
Run script before request | # @before ./setup.sh |
@after |
Run script after request | # @after ./cleanup.sh |
@db |
Database connection string | # @db sqlite://./test.db |
@waitFor |
Poll URL until ready | # @waitFor {{baseUrl}}/health 200 30000 1000 |
@stress.weight |
Relative weight for stress testing | # @stress.weight 3 |
@stress.think |
Think time in ms after request | # @stress.think 500 |
@stress.skip |
Exclude from stress tests | # @stress.skip |
@stress.setup |
Run once before stress test starts | # @stress.setup |
@stress.teardown |
Run once after stress test ends | # @stress.teardown |
# Bearer Token
# @auth bearer {{token}}
# Basic Auth
# @auth basic {{username}}, {{password}}
# API Key (Header)
# @auth apiKey X-API-Key, {{apiKey}}
# API Key (Query String)
# @auth apiKeyQuery api_key, {{apiKey}}
# Digest Auth
# @auth digest {{username}}, {{password}}
# AWS Signature v4
# @auth aws {{accessKey}}, {{secretKey}}, {{region}}, {{service}}
# OAuth2 Client Credentials
# @auth oauth2 client_credentials {{tokenUrl}}, {{clientId}}, {{clientSecret}}, scope1,scope2
# OAuth2 Password Grant
# @auth oauth2 password {{tokenUrl}}, {{clientId}}, {{clientSecret}}, {{username}}, {{password}}, scope1,scope2Capture values from responses for use in subsequent requests:
### Login
# @name login
POST {{baseUrl}}/auth/login
Content-Type: application/json
{"username": "john", "password": "secret"}
>>>capture
token from body.access_token
userId from body.user.id
<<<
### Get Profile
# @depends login
GET {{baseUrl}}/users/{{login.userId}}
Authorization: Bearer {{login.token}}
>>>
expect status 200
expect body.id == "{{login.userId}}"
<<<Note: Captured variables can be used in expect clauses with {{requestName.captureName}} syntax.
Capture Sources:
| Source | Syntax | Description |
|---|---|---|
| Body JSON path | token from body.access_token |
Capture from response body |
| Header | contentType from header Content-Type |
Capture from response header |
| Status | code from status |
Capture status code |
| Duration | time from duration |
Capture response time (ms) |
Use the official hitspec action to run API tests in your CI pipeline:
- uses: abdul-hamid-achik/hitspec@v1
with:
files: tests/
output: junit
output-file: test-results.xml
- uses: actions/upload-artifact@v7
with:
name: test-results
path: test-results.xml| Input | Description | Default |
|---|---|---|
files |
Files/directories to test | (required) |
env |
Environment name | dev |
env-file |
Path to .env file | - |
output |
Output format (console, json, junit, tap, html) | junit |
output-file |
Write output to file | - |
tags |
Filter by tags | - |
name |
Filter by request name pattern | - |
timeout |
Global timeout in milliseconds | - |
parallel |
Run in parallel | false |
concurrency |
Max concurrent requests when parallel is enabled | - |
bail |
Stop on first failure | false |
verbose |
Show detailed output | false |
insecure |
Disable SSL certificate validation | false |
proxy |
Proxy URL for requests | - |
stress |
Enable stress testing | false |
duration |
Stress test duration | 30s |
rate |
Requests per second | 10 |
vus |
Number of virtual users (alternative to rate) | - |
max-vus |
Maximum concurrent requests in stress mode | - |
think-time |
Think time between requests per VU | - |
ramp-up |
Ramp-up time to reach target rate/VUs | - |
threshold |
Pass/fail thresholds | - |
profile |
Stress profile name from hitspec.yaml | - |
version |
hitspec version | latest |
See action.yml for the authoritative input list.
- uses: abdul-hamid-achik/hitspec@v1
with:
files: api.http
stress: true
duration: 2m
rate: 100
threshold: 'p95<200ms,errors<0.5%'- name: Create .env file
run: |
echo "API_TOKEN=${{ secrets.API_TOKEN }}" >> .env.ci
- uses: abdul-hamid-achik/hitspec@v1
with:
files: tests/
env: staging
env-file: .env.ciAll major CLI flags can be set via environment variables with the HITSPEC_ prefix:
| Environment Variable | CLI Flag | Description |
|---|---|---|
HITSPEC_ENV |
--env |
Environment to use |
HITSPEC_ENV_FILE |
--env-file |
Path to .env file |
HITSPEC_CONFIG |
--config |
Path to config file |
HITSPEC_TIMEOUT |
--timeout |
Request timeout |
HITSPEC_TAGS |
--tags |
Filter by tags |
HITSPEC_OUTPUT |
--output |
Output format |
HITSPEC_OUTPUT_FILE |
--output-file |
Output file path |
HITSPEC_BAIL |
--bail |
Stop on first failure |
HITSPEC_PARALLEL |
--parallel |
Run in parallel |
HITSPEC_CONCURRENCY |
--concurrency |
Concurrent requests |
HITSPEC_QUIET |
--quiet |
Suppress output |
HITSPEC_NO_COLOR |
--no-color |
Disable colors |
HITSPEC_PROXY |
--proxy |
Proxy URL |
HITSPEC_INSECURE |
--insecure |
Skip SSL verification |
Example:
export HITSPEC_ENV=staging
export HITSPEC_TIMEOUT=60s
export HITSPEC_BAIL=true
hitspec run tests/See .github/workflows/example-hitspec.yml for more examples.
hitspec/
├── apps/
│ ├── cli/ # CLI binary (Go + Cobra)
│ ├── docs/ # Mintlify documentation site
│ ├── vscode/ # VSCode extension
│ └── nvim/ # Neovim plugin
├── packages/
│ ├── core/ # Parser + runner
│ ├── clientmgr/ # In-process API Client Manager (drives studio)
│ ├── tui/ # hitspec studio — Charm v2 interactive terminal app
│ ├── serve/ # REST/WebSocket API server for --api-only
│ ├── stress/ # Stress testing engine
│ ├── mock/ # Mock server
│ ├── http/ # HTTP client
│ ├── assertions/ # Assertion evaluation
│ ├── output/ # Output formatters
│ ├── import/ # Importers (curl, Insomnia, OpenAPI)
│ └── ... # Other feature packages
├── examples/ # Example .http files
# Install dependencies
task deps # Go dependencies
# Run tests
task test # Go tests
# Build
task build # CLI binary
# Development
task studio:dev # Interactive app
task serve:dev # REST/WebSocket API server
task docs:dev # Mintlify docs locally
# Run with example
task devMIT