Most developers have a grab-bag of curl commands saved somewhere — a notes file, a shell script, a README section — that represents the manual API tests they run when debugging. The problem with this is that it doesn’t scale, it doesn’t fail CI pipelines, and it’s not readable by anyone who didn’t write it.
Hurl solves this. It’s a command-line tool for defining and running HTTP requests in a plain text format that’s readable, diffable in pull requests, and designed to live in your source repository alongside your code. You can run the same file locally, in CI, and as a production health check without any adaptation.
What Hurl Looks Like
A .hurl file is straightforward. Here’s a basic GET with assertions:
GET https://api.example.com/users/123
HTTP 200
[Asserts]
jsonpath "$.name" == "Alice"
jsonpath "$.email" matches ".*@example.com"
header "Content-Type" contains "application/json"
That’s a GET request with assertions on the response status code, two JSON body fields, and a response header. Hurl runs it, checks every assertion, and exits with a non-zero code if anything fails. Drop it into a CI job and you have an API integration test with almost no overhead.
For POST with a JSON body:
POST https://api.example.com/users
Content-Type: application/json
{
"name": "Bob",
"email": "bob@example.com"
}
HTTP 201
[Captures]
user_id: jsonpath "$.id"
GET https://api.example.com/users/{{user_id}}
HTTP 200
[Asserts]
jsonpath "$.name" == "Bob"
Captures let you store values from one response and use them in the next request. This is how you test a create-then-read flow without hardcoding IDs or writing orchestration code. The syntax stays readable even for multi-step flows.
Where Hurl Sits Versus the Alternatives
curl is universal and powerful but asserting on responses requires piping through jq and writing bash conditionals. Maintainable it isn’t, particularly when someone other than the original author has to modify it six months later.
HTTPie improves on curl’s ergonomics considerably — cleaner output, sensible defaults, readable syntax — but doesn’t have first-class test assertions. It’s excellent for interactive API exploration; it’s not a testing framework.
Bruno is a strong API client and the right choice if you want a GUI for exploring and developing against APIs. Its collection format is JSON and lives in source control, which is an improvement over Postman. But it’s GUI-first, and its CI experience is less clean than Hurl’s native command-line workflow.
Postman and Newman work, but Postman collections are JSON blobs that are genuinely painful to write by hand and difficult to review in pull requests without a specialised tool.
Hurl’s specific niche: I want to write API tests by hand, have them reviewed in code review like any other text file, and run them in CI without a heavyweight framework or a GUI dependency.
Running Hurl in CI
GitHub Actions setup takes about four lines:
- name: Install Hurl
run: sudo apt-get install -y hurl
- name: Run API tests
run: hurl --test api-tests/*.hurl
The --test flag treats the file as a test suite rather than just a request runner — it reports pass/fail per assertion and exits non-zero on any failure. Add --report-junit results.xml for JUnit-format test results that most CI platforms can parse for test summaries and trend tracking.
Useful Features Worth Knowing
Environment variables handle API keys and base URLs cleanly:
GET {{base_url}}/health
Authorization: Bearer {{api_token}}
HTTP 200
Run with --variable base_url=https://staging.example.com --variable api_token=$MY_TOKEN or define them in a variables file for each environment. This makes it straightforward to run the same test file against staging and production without any modification.
Hurl handles cookies automatically across requests in a single file, which matters for testing authenticated sessions where a login response sets a session cookie that subsequent requests need.
The --delay flag adds a configurable sleep between requests — useful when you’re testing against an API with rate limiting and don’t want tests to fail intermittently because they hit a limit.
--continue-on-error runs all tests even when one fails, so a CI run gives you the full picture of what’s broken rather than stopping at the first failure.
Installation
Hurl is open source (written in Rust, MIT licensed) and actively maintained.
# macOS
brew install hurl
# Ubuntu/Debian
sudo apt-get install hurl
# Any platform with Cargo
cargo install hurl
Windows binaries are available from the GitHub releases page if you’re not on a Unix system.
When to Reach for Hurl
Hurl works well for API smoke tests against deployed services, integration tests that verify real HTTP responses rather than mocked ones, and API contract tests that should live alongside the code they’re testing.
It’s not a replacement for unit tests — it operates at the HTTP boundary, not the function level. It’s also not designed for load testing; reach for k6 or Grafana k6 if you need to simulate concurrent users.
The sweet spot is the test layer that often gets skipped because the tooling friction is too high: the “does this API endpoint actually work end to end in this environment” test. Writing that as curl assertions takes 15 minutes. Writing it in Hurl takes 2. That’s a difference that changes whether people write the test at all.