Hurl Cheat Sheet
Overview
Hurl is a command-line tool that runs HTTP requests defined in a simple plain-text format. It can perform requests, capture values, evaluate queries on headers and body, and chain requests together. Hurl is excellent for testing HTTP APIs, validating responses, and creating integration test suites that are easy to read, write, and version control.
Built on top of libcurl, Hurl supports HTTP/1.1, HTTP/2, HTTP/3, TLS, cookies, redirects, multipart forms, and more. It provides a rich set of assertions for status codes, headers, body content, JSONPath, XPath, regex, and duration checks. Hurl files are plain text, making them ideal for documentation and CI/CD integration.
Installation
# macOS
brew install hurl
# Ubuntu/Debian
curl -LO https://github.com/Orange-OpenSource/hurl/releases/download/5.0.1/hurl_5.0.1_amd64.deb
sudo dpkg -i hurl_5.0.1_amd64.deb
# Arch Linux
pacman -S hurl
# Cargo (Rust)
cargo install hurl
# npm
npm install -g @orangeopensource/hurl
# Docker
docker pull ghcr.io/orange-opensource/hurl
# Verify installation
hurl --version
Core Commands
| Flag | Description |
|---|---|
hurl file.hurl | Execute a Hurl file |
--test | Run in test mode (summary output) |
--report-html dir | Generate HTML report |
--report-junit file.xml | Generate JUnit report |
--variable name=value | Set a variable |
--variables-file file | Load variables from file |
--verbose | Show request/response details |
--very-verbose | Show even more details including body |
--json | Output results as JSON |
--output file | Write response body to file |
--retry N | Retry failed requests N times |
--retry-interval MS | Wait MS between retries |
--max-time SECS | Maximum time for entire run |
--connect-timeout SECS | Connection timeout |
--color | Force colored output |
Basic Request Syntax
# Simple GET request
GET https://api.example.com/health
# GET with headers
GET https://api.example.com/users
Authorization: Bearer token123
Accept: application/json
# POST with JSON body
POST https://api.example.com/users
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com"
}
# PUT request
PUT https://api.example.com/users/123
Content-Type: application/json
{
"name": "Jane Doe"
}
# DELETE request
DELETE https://api.example.com/users/123
Authorization: Bearer token123
Assertions
# Status code assertion
GET https://api.example.com/health
HTTP 200
# Header assertions
GET https://api.example.com/users
HTTP 200
Content-Type: application/json
Cache-Control: no-cache
# Body assertions
GET https://api.example.com/health
HTTP 200
[Asserts]
status == 200
header "Content-Type" == "application/json"
body contains "healthy"
jsonpath "$.status" == "ok"
jsonpath "$.version" isString
jsonpath "$.uptime" > 0
duration < 500
JSONPath Assertions
GET https://api.example.com/users/123
HTTP 200
[Asserts]
jsonpath "$.id" == 123
jsonpath "$.name" == "John Doe"
jsonpath "$.email" matches "^[\\w.]+@[\\w.]+$"
jsonpath "$.active" == true
jsonpath "$.roles" count == 3
jsonpath "$.roles" includes "admin"
jsonpath "$.address.city" exists
jsonpath "$.deleted_at" not exists
jsonpath "$.tags[0]" == "premium"
jsonpath "$.metadata" isCollection
Capturing Values
# Login and capture token
POST https://api.example.com/auth/login
Content-Type: application/json
{
"username": "admin",
"password": "secret"
}
HTTP 200
[Captures]
auth_token: jsonpath "$.token"
user_id: jsonpath "$.user.id"
# Use captured values in subsequent requests
GET https://api.example.com/users/{{user_id}}
Authorization: Bearer {{auth_token}}
HTTP 200
[Asserts]
jsonpath "$.id" == {{user_id}}
Variables
# Using variables in requests
GET https://{{host}}/api/users/{{user_id}}
Authorization: Bearer {{api_token}}
HTTP 200
# Pass variables via command line
hurl --variable host=api.example.com \
--variable user_id=123 \
--variable api_token=mytoken \
test.hurl
# Variables file (vars.env)
# host=api.example.com
# user_id=123
# api_token=mytoken
hurl --variables-file vars.env test.hurl
Form and File Uploads
# URL-encoded form
POST https://api.example.com/login
[FormParams]
username: admin
password: secret123
# Multipart form with file upload
POST https://api.example.com/upload
[MultipartFormData]
file: file,avatar.png; image/png
description: Profile photo
user_id: 123
Cookies
# Send cookies
GET https://api.example.com/dashboard
[Cookies]
session_id: abc123
theme: dark
# Assert on response cookies
GET https://api.example.com/login
HTTP 200
[Asserts]
cookie "session_id" exists
cookie "session_id[Secure]" exists
cookie "session_id[HttpOnly]" exists
cookie "session_id[Max-Age]" > 3600
Chaining Requests
# Create a resource
POST https://api.example.com/products
Content-Type: application/json
{
"name": "Widget",
"price": 9.99
}
HTTP 201
[Captures]
product_id: jsonpath "$.id"
# Verify the resource exists
GET https://api.example.com/products/{{product_id}}
HTTP 200
[Asserts]
jsonpath "$.name" == "Widget"
jsonpath "$.price" == 9.99
# Update the resource
PUT https://api.example.com/products/{{product_id}}
Content-Type: application/json
{
"price": 12.99
}
HTTP 200
# Delete the resource
DELETE https://api.example.com/products/{{product_id}}
HTTP 204
# Verify deletion
GET https://api.example.com/products/{{product_id}}
HTTP 404
Advanced Usage
Retry and Polling
# Retry until condition is met
GET https://api.example.com/jobs/{{job_id}}/status
[Options]
retry: 10
retry-interval: 2000
HTTP 200
[Asserts]
jsonpath "$.status" == "completed"
XPath Assertions (HTML/XML)
GET https://example.com/page
HTTP 200
[Asserts]
xpath "//title" == "Welcome"
xpath "count(//div[@class='item'])" >= 5
xpath "//meta[@name='description']/@content" exists
Regex Assertions
GET https://api.example.com/version
HTTP 200
[Asserts]
body matches "^\\d+\\.\\d+\\.\\d+$"
header "X-Request-Id" matches "^[a-f0-9-]{36}$"
SSL/TLS Options
GET https://self-signed.example.com/api
[Options]
insecure: true
cacert: /path/to/ca-bundle.crt
Configuration
# Run in test mode with HTML report
hurl --test --report-html ./report tests/*.hurl
# JUnit report for CI
hurl --test --report-junit results.xml tests/*.hurl
# Glob pattern execution
hurl --test tests/**/*.hurl
# With timeout settings
hurl --connect-timeout 10 --max-time 120 test.hurl
Troubleshooting
| Issue | Solution |
|---|---|
Could not resolve host | Check URL and DNS; verify network connectivity |
| Assertion failed | Use --verbose to see actual response data |
| Variable not found | Check variable name spelling; ensure capture happens before use |
| SSL certificate error | Use --insecure for self-signed certs or provide CA with --cacert |
| Timeout errors | Increase --connect-timeout or --max-time values |
| JSON parsing error | Verify response is valid JSON; check Content-Type header |
| Retry not working | Ensure [Options] section with retry is correctly indented |
| File upload fails | Check file path is relative to current directory |