Getting started with HTTPie for API testing

Debug API clients with HTTPie, an easy-to-use command-line tool written in Python.
102 readers like this.
8 fun Raspberry Pi projects to try

Internet Archive Book Images. Modified by Opensource.com. CC BY-SA 4.0

HTTPie is a delightfully easy to use and easy to upgrade HTTP client. Pronounced "aitch-tee-tee-pie" and run as http, it is a command-line tool written in Python to access the web.

Since this how-to is about an HTTP client, you need an HTTP server to try it out; in this case, httpbin.org, a simple, open source HTTP request-and-response service. The httpbin.org site is a powerful way to test to test web API clients and carefully manage and show details in requests and responses, but for now we will focus on the power of HTTPie.

An alternative to Wget and cURL

You might have heard of the venerable Wget or the slightly newer cURL tools that allow you to access the web from the command line. They were written to access websites, whereas HTTPie is for accessing web APIs.

Website requests are designed to be between a computer and an end user who is reading and responding to what they see. This doesn't depend much on structured responses. However, API requests make structured calls between two computers. The human is not part of the picture, and the parameters of a command-line tool like HTTPie handle this effectively.

Install HTTPie

There are several ways to install HTTPie. You can probably get it as a package for your package manager, whether you use brew, apt, yum, or dnf. However, if you have configured virtualenvwrapper, you can own your own installation:

$ mkvirtualenv httpie
...
(httpie) $ pip install httpie
...
(httpie) $ deactivate 
$ alias http=~/.virtualenvs/httpie/bin/http
$ http -b GET https://httpbin.org/get
{
    "args": {},
    "headers": {
        "Accept": "*/*",
        "Accept-Encoding": "gzip, deflate",
        "Host": "httpbin.org",
        "User-Agent": "HTTPie/1.0.2"
    },
    "origin": "104.220.242.210, 104.220.242.210",
    "url": "https://httpbin.org/get"
}

By aliasing http directly to the command inside the virtual environment, you can run it even when the virtual environment is not active. You can put the alias command in .bash_profile or .bashrc so you can upgrade HTTPie with the command:

$ ~/.virtualenvs/httpie/bin/pip install -U httpie

Query a website with HTTPie

HTTPie can simplify querying and testing an API. One option for running it, -b (also known as --body), was used above. Without it, HTTPie will print the entire response, including the headers, by default:

$ http GET https://httpbin.org/get
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Encoding: gzip
Content-Length: 177
Content-Type: application/json
Date: Fri, 09 Aug 2019 20:19:47 GMT
Referrer-Policy: no-referrer-when-downgrade
Server: nginx
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block

{
    "args": {},
    "headers": {
        "Accept": "*/*",
        "Accept-Encoding": "gzip, deflate",
        "Host": "httpbin.org",
        "User-Agent": "HTTPie/1.0.2"
    },
    "origin": "104.220.242.210, 104.220.242.210",
    "url": "https://httpbin.org/get"
}

This is crucial when debugging an API service because a lot of information is sent in the headers. For example, it is often important to see which cookies are being sent. Httpbin.org provides options to set cookies (for testing purposes) through the URL path. The following sets a cookie titled opensource to the value awesome:

$ http GET https://httpbin.org/cookies/set/opensource/awesome 
HTTP/1.1 302 FOUND
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 223
Content-Type: text/html; charset=utf-8
Date: Fri, 09 Aug 2019 20:22:39 GMT
Location: /cookies
Referrer-Policy: no-referrer-when-downgrade
Server: nginx
Set-Cookie: opensource=awesome; Path=/
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to target URL:
<a href="https://opensource.com/cookies">/cookies</a>.  If not click the link.

Notice the Set-Cookie: opensource=awesome; Path=/ header. This shows the cookie you expected to be set is set correctly and with a / path. Also notice that, even though you got a 302 redirect, http did not follow it. If you want to follow redirects, you need to ask for it explicitly with the --follow flag:

$ http --follow GET https://httpbin.org/cookies/set/opensource/awesome
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Encoding: gzip
Content-Length: 66
Content-Type: application/json
Date: Sat, 10 Aug 2019 01:33:34 GMT
Referrer-Policy: no-referrer-when-downgrade
Server: nginx
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block

{
    "cookies": {
        "opensource": "awesome"
    }
}

But now you cannot see the original Set-Cookie header. In order to see intermediate replies, you need to use --all:

$ http --headers --all --follow \
GET https://httpbin.org/cookies/set/opensource/awesome
HTTP/1.1 302 FOUND
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Content-Type: text/html; charset=utf-8
Date: Sat, 10 Aug 2019 01:38:40 GMT
Location: /cookies
Referrer-Policy: no-referrer-when-downgrade
Server: nginx
Set-Cookie: opensource=awesome; Path=/
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Content-Length: 223
Connection: keep-alive

HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Content-Encoding: gzip
Content-Type: application/json
Date: Sat, 10 Aug 2019 01:38:41 GMT
Referrer-Policy: no-referrer-when-downgrade
Server: nginx
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Content-Length: 66
Connection: keep-alive

Printing the body is uninteresting because you are mostly interested in the cookies. If you want to see the headers from the intermediate request but the body from the final request, you can do that with:

$ http --print hb --history-print h --all --follow \ 
GET https://httpbin.org/cookies/set/opensource/awesome
HTTP/1.1 302 FOUND
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Content-Type: text/html; charset=utf-8
Date: Sat, 10 Aug 2019 01:40:56 GMT
Location: /cookies
Referrer-Policy: no-referrer-when-downgrade
Server: nginx
Set-Cookie: opensource=awesome; Path=/
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Content-Length: 223
Connection: keep-alive

HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Content-Encoding: gzip
Content-Type: application/json
Date: Sat, 10 Aug 2019 01:40:56 GMT
Referrer-Policy: no-referrer-when-downgrade
Server: nginx
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Content-Length: 66
Connection: keep-alive

{
  "cookies": {
    "opensource": "awesome"
  }
}

You can control exactly what is being printed with --print and override what is printed for intermediate requests with --history-print.

Download binary files with HTTPie

Sometimes the body is non-textual and needs to be sent to a file that can be opened by a different application:

$ http GET https://httpbin.org/image/jpeg
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 35588
Content-Type: image/jpeg
Date: Fri, 09 Aug 2019 20:25:49 GMT
Referrer-Policy: no-referrer-when-downgrade
Server: nginx
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block



+-----------------------------------------+
| NOTE: binary data not shown in terminal |
+-----------------------------------------+

To get the right image, you need to save it to a file:

$ http --download GET https://httpbin.org/image/jpeg
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 35588
Content-Type: image/jpeg
Date: Fri, 09 Aug 2019 20:28:13 GMT
Referrer-Policy: no-referrer-when-downgrade
Server: nginx
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block

Downloading 34.75 kB to "jpeg.jpe"
Done. 34.75 kB in 0.00068s (50.05 MB/s)

Try it! The picture is adorable.

Sending custom requests with HTTPie

You can also send specific headers. This is useful for custom web APIs that require a non-standard header:

$ http GET https://httpbin.org/headers X-Open-Source-Com:Awesome
{
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Host": "httpbin.org", 
    "User-Agent": "HTTPie/1.0.2", 
    "X-Open-Source-Com": "Awesome"
  }
}

Finally, if you want to send JSON fields (although it is possible to specify exact content), for many less-nested inputs, you can use a shortcut:

$ http --body PUT https://httpbin.org/anything open-source=awesome author=moshez
{
  "args": {}, 
  "data": "{\"open-source\": \"awesome\", \"author\": \"moshez\"}", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "application/json, */*", 
    "Accept-Encoding": "gzip, deflate", 
    "Content-Length": "46", 
    "Content-Type": "application/json", 
    "Host": "httpbin.org", 
    "User-Agent": "HTTPie/1.0.2"
  }, 
  "json": {
    "author": "moshez", 
    "open-source": "awesome"
  }, 
  "method": "PUT", 
  "origin": "73.162.254.113, 73.162.254.113", 
  "url": "https://httpbin.org/anything"
}

The next time you are debugging a web API, whether your own or someone else's, put down your cURL and reach for HTTPie, the command-line client for web APIs.

What to read next
Moshe sitting down, head slightly to the side. His t-shirt has Guardians of the Galaxy silhoutes against a background of sound visualization bars.
Moshe has been involved in the Linux community since 1998, helping in Linux "installation parties". He has been programming Python since 1999, and has contributed to the core Python interpreter. Moshe has been a DevOps/SRE since before those terms existed, caring deeply about software reliability, build reproducibility and other such things.

2 Comments

Thanks, nice write-up. Question, does HTTPie allow sending a JSON request body as part of a GET request? Some tools don't allow that and unfortunately I've found some web APIs that require it.

James, yes, HTTPie allows you to specify a request body for a GET request as well (for any any method, really). Just use one of the standard ways of sending a payload and specify GET as the method, for example, let's send some JSON:

$ http -v GET httpbin.org/get json_key=json_value

GET /get HTTP/1.1
Accept: application/json, */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 26
Content-Type: application/json
Host: httpbin.org
User-Agent: HTTPie/2.0.0-dev

{
"json_key": "json_value"
}

In reply to by JamesF

Creative Commons LicenseThis work is licensed under a Creative Commons Attribution-Share Alike 4.0 International License.