Skip to main content
hitspec tests live in plain text files with .http or .hitspec extensions. Each file contains one or more HTTP requests along with optional variables, assertions, and captures.

Minimal Example

The smallest valid test is a request with one assertion:
GET https://api.example.com/health

>>>
expect status 200
<<<

File Structure

A typical .http file follows this structure:
@baseUrl = https://api.example.com
@token = my-secret-token

### Get all users
# @name getUsers
# @tags smoke, users

GET {{baseUrl}}/users
Authorization: Bearer {{token}}

>>>
expect status 200
expect body type array
<<<

>>>capture
total from header X-Total-Count
<<<

### Get single user
# @name getUser
# @depends getUsers

GET {{baseUrl}}/users/1

>>>
expect status 200
expect body.id == 1
<<<

Request Separator

Use ### to separate multiple requests within a single file. Everything before the first ### (or before the first request line if no separator is used) is treated as the file-level scope for variable definitions.
### First request
GET https://api.example.com/posts

>>>
expect status 200
<<<

### Second request
GET https://api.example.com/users

>>>
expect status 200
<<<
The ### separator is required when a file contains more than one request. Text after ### on the same line is treated as a human-readable title and is ignored by the parser.

Variables

Define variables at the top of a file using the @variable = value syntax:
@baseUrl = https://api.example.com
@apiVersion = v1
@timeout = 5000
Reference them anywhere in the file with double curly braces:
GET {{baseUrl}}/{{apiVersion}}/users
See Variables for the full variable system including built-in functions and captures.

Request Line

Every request starts with an HTTP method followed by a URL:
GET {{baseUrl}}/users
POST {{baseUrl}}/users
PUT {{baseUrl}}/users/1
PATCH {{baseUrl}}/users/1
DELETE {{baseUrl}}/users/1
HEAD {{baseUrl}}/health
OPTIONS {{baseUrl}}/users

Headers

Headers follow the request line, one per line, using standard Name: Value format:
POST {{baseUrl}}/users
Content-Type: application/json
Authorization: Bearer {{token}}
Accept: application/json
X-Request-Id: {{$uuid()}}
A blank line separates headers from the request body. Any lines after the blank line (until the next block marker or separator) are treated as the body.

Request Body

JSON

POST {{baseUrl}}/users
Content-Type: application/json

{
  "name": "Jane Doe",
  "email": "jane@example.com",
  "role": "admin"
}

Form URL-Encoded

Use the & prefix syntax for readable form data:
POST {{baseUrl}}/login
Content-Type: application/x-www-form-urlencoded

& username = john
& password = secret123
& remember = true

Multipart Form Data

Use the >>>multipart block for file uploads and mixed form data:
POST {{baseUrl}}/upload

>>>multipart
field name = John Doe
field email = john@example.com
file @./photo.jpg
file @./report.pdf
<<<

GraphQL

Use >>>graphql and >>>variables blocks:
POST {{baseUrl}}/graphql
Content-Type: application/json

>>>graphql
query GetUsers($limit: Int) {
  users(limit: $limit) {
    id
    name
    email
  }
}
<<<

>>>variables
{
  "limit": 10
}
<<<

XML

POST {{baseUrl}}/soap
Content-Type: application/xml

<?xml version="1.0" encoding="UTF-8"?>
<request>
  <action>getUser</action>
  <id>123</id>
</request>

File-Based Body

Reference an external file as the request body using < ./path:
POST {{baseUrl}}/users
Content-Type: application/json

< ./fixtures/user.json
The file path is resolved relative to the .http file’s directory. Content-Type is auto-detected from the file extension (.json, .xml, .yaml, .html, .csv, .txt) when no explicit Content-Type header is set. Variable interpolation works in the file path:
POST {{baseUrl}}/import
Content-Type: application/json

< ./fixtures/{{scenario}}.json

Query Parameters

Inline with the URL:
GET {{baseUrl}}/search?query=test&limit=10
Or use the explicit ? prefix syntax for readability:
GET {{baseUrl}}/search
? query = test
? limit = 10
? sort = created_at
? order = desc

Assertion Blocks

Wrap assertions in >>> and <<< markers:
GET {{baseUrl}}/users

>>>
expect status 200
expect body type array
expect body[0].id exists
expect duration < 1000
<<<
See Assertions for all 26 operators.

Capture Blocks

Capture response values for use in later requests:
POST {{baseUrl}}/auth/login
Content-Type: application/json

{"email": "test@example.com", "password": "secret"}

>>>capture
token from body.access_token
userId from body.user.id
<<<
See Captures for the full capture system.

Metadata Directives

Metadata directives are comments that start with # @ and control request behavior:
### Create user
# @name createUser
# @description Creates a new user account
# @tags smoke, users, write
# @timeout 10000
# @retry 3
# @retryDelay 1000
# @depends login
# @auth bearer {{login.token}}

POST {{baseUrl}}/users
Content-Type: application/json

{"name": "Test User"}

All Directives

DirectiveDescriptionExample
@nameRequest identifier for captures and dependencies# @name createUser
@descriptionHuman-readable description# @description Creates a user
@tagsComma-separated tags for filtering# @tags smoke, auth
@skipSkip this request# @skip Temporarily disabled
@onlyRun only this request# @only
@timeoutRequest timeout in milliseconds# @timeout 5000
@retryNumber of retry attempts# @retry 3
@retryDelayDelay between retries in milliseconds# @retryDelay 1000
@retryOnStatus codes that trigger a retry# @retryOn 500, 502, 503
@dependsRequest dependencies (comma-separated names)# @depends login, setup
@authAuthentication method# @auth bearer {{token}}
@beforeShell script to run before the request# @before ./setup.sh
@afterShell script to run after the request (always runs)# @after ./cleanup.sh
@dbDatabase connection for DB assertions# @db sqlite://./test.db
@waitForPoll URL until ready before executing# @waitFor {{baseUrl}}/health 200 30000 1000
@ifRun only when variable is truthy# @if {{runSlow}}
@unlessSkip when variable is truthy# @unless {{skipAuth}}
@stress.weightRelative selection weight for stress tests# @stress.weight 3
@stress.thinkThink time in ms after this request (stress)# @stress.think 500
@stress.skipExclude from stress testing# @stress.skip
@stress.setupRun once before stress test starts# @stress.setup
@stress.teardownRun once after stress test ends# @stress.teardown

@waitFor — Service Readiness Polling

The @waitFor directive polls a URL before executing the request. This is useful when your test depends on a service that may not be immediately available (e.g., a container that was just started).
### Create order (waits for payment service)
# @waitFor http://localhost:4000/health 200 30000 1000

POST {{baseUrl}}/orders
Content-Type: application/json

{"product": "Widget", "quantity": 1}
Syntax:
# @waitFor <url> [status] [timeout_ms] [interval_ms]
ParameterDefaultDescription
url(required)URL to poll
status200Expected HTTP status code
timeout_ms30000Maximum time to wait (milliseconds)
interval_ms1000Time between poll attempts (milliseconds)
If the URL does not return the expected status within the timeout, the request fails without executing.

@if / @unless — Conditional Execution

Skip requests based on variable values:
### Run only in CI
# @if {{CI}}

POST {{baseUrl}}/deploy
### Skip when auth is disabled
# @unless {{skipAuth}}
# @auth bearer {{token}}

GET {{baseUrl}}/admin
The condition is truthy when the variable is defined and not empty, "0", or "false".

Stress Test Annotations

Fine-tune how individual requests behave during stress testing:
### Create session (setup)
# @name createSession
# @stress.setup

POST {{baseUrl}}/sessions

### High-traffic endpoint
# @name getProducts
# @stress.weight 5
# @stress.think 200

GET {{baseUrl}}/products

### Cleanup (teardown)
# @name deleteSession
# @stress.teardown

DELETE {{baseUrl}}/sessions/{{createSession.id}}

### Exclude from load test
# @name healthCheck
# @stress.skip

GET {{baseUrl}}/health
AnnotationDescription
@stress.weight NRelative selection weight (default 1). Higher weight = more frequent selection.
@stress.think NThink time in milliseconds after this request completes.
@stress.skipExclude this request from stress test execution.
@stress.setupRun this request once before the stress test starts.
@stress.teardownRun this request once after the stress test ends.

Comments

Lines starting with # that do not have an @ directive are treated as regular comments and ignored:
# This is a comment
# Another comment

### Get users
# @name getUsers

# The following request fetches all active users
GET {{baseUrl}}/users?active=true

Authentication

Use @auth to attach credentials to a request. hitspec supports 8 authentication methods:
# @auth bearer {{token}}

GET {{baseUrl}}/me

File Extensions

hitspec recognizes two file extensions:
  • .http — the standard extension, compatible with REST Client and other HTTP file tools
  • .hitspec — an alternative extension for files that use hitspec-specific features
Both are functionally identical.