Hurl
A command-line tool that runs HTTP requests defined in plain text format, making it easy to test APIs, chain requests, and assert on responses.
Hurl is a command-line tool for running and testing HTTP requests defined in a
simple, human-readable plain text format. A .hurl file describes one or more
HTTP requests along with assertions about the responses — status codes, headers,
body content, JSON values, XPath expressions, and more. It's equally at home as
a quick API scratchpad and as a rigorous integration test suite in CI.
Features
- Plain text format — requests and assertions live in a
.hurlfile that is readable, diffable, and easy to commit alongside your code - Chaining — multiple requests in a single file run in sequence; values captured from one response (e.g. a token) can be injected into the next request
- Rich assertions — assert on status code, response headers, JSON values (via JSONPath), XML values (via XPath), regex matches, body content, and more
- Variables — parameterise requests with
--variableflags or.envfiles for different environments - HTML and JSON reports — generate test reports suitable for CI dashboards
- Parallel execution — run multiple
.hurlfiles in parallel with--parallel - Cookie handling — automatically manages cookies across requests in a session
- Fast — built in Rust on top of
libcurl, with minimal overhead - No runtime dependencies — a single binary handles everything
Installation
cargo install hurl
Or via your system package manager:
# macOS
brew install hurl
# Arch Linux
pacman -S hurl
# Debian / Ubuntu (Debian 12+)
apt install hurl
# Fedora
dnf install hurl
Pre-built .deb and .rpm packages are also available on the
releases page.
Pre-built binaries for all major platforms are available on the releases page.
File Format
A .hurl file contains one or more HTTP request/response pairs:
# A simple GET request
GET https://httpbin.org/get
HTTP 200
[Asserts]
header "Content-Type" contains "application/json"
jsonpath "$.url" == "https://httpbin.org/get"
Multiple requests chain together in a single file:
# Step 1: Log in and capture the token
POST https://api.example.com/auth/login
Content-Type: application/json
{
"username": "alice",
"password": "s3cret"
}
HTTP 200
[Captures]
token: jsonpath "$.access_token"
# Step 2: Use the captured token in the next request
GET https://api.example.com/users/me
Authorization: Bearer {{token}}
HTTP 200
[Asserts]
jsonpath "$.username" == "alice"
jsonpath "$.email" isStringUsage
# Run a single .hurl file
hurl api.hurl
# Run with verbose output (shows full request/response)
hurl --verbose api.hurl
# Run and treat assertion failures as a non-zero exit code (for CI)
hurl --test api.hurl
# Run all .hurl files in a directory
hurl --test tests/*.hurl
# Run files in parallel
hurl --test --parallel tests/*.hurl
# Pass variables (e.g. for different environments)
hurl --variable base_url=https://staging.example.com api.hurl
# Load variables from a file
hurl --variables-file .env.staging api.hurl
# Generate an HTML report
hurl --test --report-html report/ tests/*.hurl
# Generate a JUnit XML report (for CI)
hurl --test --report-junit report.xml tests/*.hurlAssertions
Hurl supports a rich set of assertion types:
GET https://api.example.com/products
HTTP 200
[Asserts]
# Status and timing
status == 200
duration < 500 # response time in ms
# Headers
header "Content-Type" == "application/json; charset=utf-8"
header "X-RateLimit-Remaining" toInteger greaterThan 0
# JSONPath
jsonpath "$.products" isCollection
jsonpath "$.products" count == 10
jsonpath "$.products[0].name" isString
jsonpath "$.products[0].price" greaterThan 0.0
# Regex on the body
body matches /products.*available/
# XPath (for XML/HTML responses)
xpath "//title/text()" == "Products"Why not just use curl or Postman?
curl is excellent for one-off requests but gets unwieldy for chained
workflows, variable substitution, and structured assertions. Postman and similar
GUI tools work well interactively but don't version-control well and add
friction to CI pipelines. Hurl occupies a sweet spot: plain text files that live
in your repo, run from the command line, and integrate naturally into any CI
system.