Hurl: The Command-Line HTTP Testing Tool Every API Developer Needs
API testing has a tooling problem. Postman collections balloon into unmaintainable JSON blobs. Custom shell scripts with curl become brittle and unreadable. Language-specific frameworks require onboarding everyone to the same tech stack. Hurl cuts through all of that with a refreshingly simple idea: describe HTTP requests and assertions in plain text, run them from the command line, and let the tool handle the rest.
If you have not heard of Hurl yet, it is an open-source command-line tool from the team at Orange (yes, the telecom company) that lets you run HTTP requests defined in .hurl files. It is fast, scriptable, and CI-friendly out of the box. This post covers what makes Hurl worth your attention, how to write effective tests with it, and how it fits into a modern API testing pipeline.
What Is Hurl?
Hurl is a command-line tool that runs HTTP requests defined in a simple plain-text format. Each .hurl file contains one or more request-response pairs. You write the request, specify what you expect back, and Hurl tells you whether the API delivered.
The tool is written in Rust, which means it is a single statically-linked binary with no runtime dependencies. Installation is a one-line download on most platforms. It supports HTTP/1.1 and HTTP/2, handles cookies and sessions automatically across requests in the same file, and can output test results in JUnit XML or JSON format for CI systems.
Hurl is not a GUI tool, not a framework, and not a cloud service. It is a CLI tool you run locally or in a pipeline, and the test files live right next to your application code in version control.
The Hurl File Format
The syntax is intentionally minimal. Here is a basic example that tests a public API endpoint:
GET https://api.example.com/users/42
HTTP 200
[Asserts]
jsonpath "$.name" == "Alice"
jsonpath "$.email" isString
jsonpath "$.role" == "admin"That is the entire test. Three lines for the request, four lines for assertions. Now here is a more realistic sequence that creates a resource, then reads it back:
# Create a new user
POST https://api.example.com/users
Content-Type: application/json
{
"name": "Bob",
"email": "bob@example.com",
"role": "viewer"
}
HTTP 201
[Captures]
user_id: jsonpath "$.id"
# Retrieve the created user
GET https://api.example.com/users/{{user_id}}
HTTP 200
[Asserts]
jsonpath "$.name" == "Bob"
jsonpath "$.email" == "bob@example.com"
# Clean up
DELETE https://api.example.com/users/{{user_id}}
HTTP 204The [Captures] section extracts values from responses and makes them available as variables for subsequent requests. This is how you chain dependent calls without glue code.
Key Features Worth Knowing
Variable substitution — You can inject variables from the environment or from the command line:
GET https://{{host}}/api/health
Authorization: Bearer {{token}}
HTTP 200Run it with:
hurl --variable host=api.staging.example.com \
--variable token=$API_TOKEN \
health-check.hurlThis makes the same test file reusable across environments without modification.
Cookie handling — Hurl manages cookies automatically within a file. If a login endpoint sets a session cookie, subsequent requests in the same file carry it without any extra configuration.
TLS certificate control — For testing services with self-signed certificates in staging environments, --insecure skips verification. For production, certificate pinning is supported.
Response body assertions — Beyond JSON path, Hurl supports XPath for XML APIs, regex matching, header assertions, status code checks, and duration assertions (useful for SLA validation):
GET https://api.example.com/products
HTTP 200
[Asserts]
header "Content-Type" contains "application/json"
jsonpath "$.products" count >= 1
duration < 500HTML report generation — --report-html ./report produces a browsable HTML report showing each request, the response, and which assertions passed or failed. Useful for sharing results with stakeholders who do not live in the terminal.
Writing Realistic API Tests with Hurl
Here is a more complete example covering an authentication flow:
# Step 1: Login
POST https://api.example.com/auth/login
Content-Type: application/json
{
"username": "testuser@example.com",
"password": "{{test_password}}"
}
HTTP 200
[Captures]
access_token: jsonpath "$.access_token"
refresh_token: jsonpath "$.refresh_token"
[Asserts]
jsonpath "$.token_type" == "Bearer"
jsonpath "$.expires_in" > 0
# Step 2: Access a protected endpoint
GET https://api.example.com/me
Authorization: Bearer {{access_token}}
HTTP 200
[Asserts]
jsonpath "$.username" == "testuser@example.com"
jsonpath "$.plan" isString
# Step 3: Refresh the token
POST https://api.example.com/auth/refresh
Content-Type: application/json
{
"refresh_token": "{{refresh_token}}"
}
HTTP 200
[Captures]
new_token: jsonpath "$.access_token"
# Step 4: Verify new token works
GET https://api.example.com/me
Authorization: Bearer {{new_token}}
HTTP 200
# Step 5: Logout
POST https://api.example.com/auth/logout
Authorization: Bearer {{new_token}}
HTTP 204This entire flow is one .hurl file, readable by anyone who knows HTTP, and runnable in CI without any framework installation beyond the Hurl binary.
Pros and Cons
Pros:
- Minimal syntax — readable by developers, QA engineers, and technical PMs alike
- Single binary, no runtime dependencies
- Native CI support with JUnit XML output
- Fast execution (Rust performance)
- Version-control friendly — diff
.hurlfiles like any other text file - Handles multi-step flows with captures and variables
Cons:
- No built-in test data generation — you manage fixtures yourself
- Limited conditional logic — not designed for complex branching scenarios
- No GUI — terminal-only workflow (some find this a pro)
- Smaller ecosystem compared to Postman or REST Assured
- No built-in parallelism for large test suites
CI Integration
Hurl fits naturally into any CI pipeline. Here is a GitHub Actions example:
name: API Tests
on: [push, pull_request]
jobs:
api-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Hurl
run: |
curl -LO https://github.com/Orange-OpenSource/hurl/releases/latest/download/hurl_linux_amd64.tar.gz
tar -xzf hurl_linux_amd64.tar.gz
sudo mv hurl /usr/local/bin/
- name: Run API tests
env:
API_HOST: ${{ secrets.STAGING_API_HOST }}
API_TOKEN: ${{ secrets.STAGING_API_TOKEN }}
run: |
hurl --variable host=$API_HOST \
--variable token=$API_TOKEN \
--report-junit results.xml \
tests/api/**/*.hurl
- name: Publish test results
uses: mikepenz/action-junit-report@v4
if: always()
with:
report_paths: results.xmlFor GitLab CI:
api-tests:
image: debian:bookworm-slim
before_script:
- apt-get update && apt-get install -y curl
- curl -LO https://github.com/Orange-OpenSource/hurl/releases/latest/download/hurl_linux_amd64.tar.gz
- tar -xzf hurl_linux_amd64.tar.gz && mv hurl /usr/local/bin/
script:
- hurl --variable host=$STAGING_HOST --report-junit report.xml tests/**/*.hurl
artifacts:
reports:
junit: report.xmlA useful pattern is to organize your .hurl files by feature or service, and run them in parallel as separate CI jobs. Since each file is stateless from CI's perspective, parallelism is trivial to add.
Where Hurl Fits in Your Stack
Hurl excels at smoke tests and integration tests against real HTTP endpoints. It is not designed for unit tests or for testing complex business logic that requires code-level assertions. The sweet spot is: "Does this API actually respond correctly when called the way a real client would call it?"
For teams building REST or GraphQL APIs, keeping a tests/api/ directory of .hurl files alongside the source code creates a lightweight contract that runs on every pull request. New developers can read the test files to understand the expected API behavior without running the application.
For teams that need broader end-to-end coverage — testing full user journeys through a UI, not just API calls — tools like HelpMeTest complement Hurl well. HelpMeTest uses AI-powered test generation with natural language inputs, running Robot Framework and Playwright tests in the cloud. You write what you want to verify in plain English, and the system generates and runs the tests. At $100/month for the Pro plan, it covers UI flows while Hurl handles your API layer.
Getting Started
Install Hurl:
# macOS
brew install hurl
<span class="hljs-comment"># Linux
curl -LO https://github.com/Orange-OpenSource/hurl/releases/latest/download/hurl_linux_amd64.tar.gz
tar -xzf hurl_linux_amd64.tar.gz
<span class="hljs-comment"># Windows
winget install Hurl.HurlWrite your first test file, point it at a real endpoint, and run:
hurl --test my-api.hurlThe --test flag switches Hurl to test mode, where it reports pass/fail per assertion rather than printing response bodies.
Conclusion
Hurl is one of those tools that does one thing well and gets out of your way. The plain-text format means your API tests live in version control, get reviewed in pull requests, and stay readable without specialized tooling. The single-binary distribution means there is no "works on my machine" problem in CI.
If your team has been putting off API test automation because existing tools felt too heavyweight, Hurl is worth a serious look. Start with a few smoke tests against your most critical endpoints, commit the .hurl files alongside your code, and wire them into CI. The feedback loop from write-to-run is short enough that the habit actually sticks.
For teams that want to go further — covering full user journeys, generating tests with AI assistance, and monitoring production continuously — explore what HelpMeTest adds on top of your HTTP-level testing foundation.