Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Calling Authenticated REST APIs and Parsing JSON in Python

Hello! In the previous lesson, your Python runner captured exit codes and diagnostics from local tools. This lesson adds another source of data: an HTTP API. You’ll send an authenticated request, distinguish an HTTP failure from a network failure, and turn a JSON response into a value your program can use. Allow about 40 minutes for the video, reading, and implementation.


From a command result to an API response

A compiler run can finish with a non-zero exit code. An API call has a similar distinction, but with two layers: the server may reply with an HTTP status such as 200 or 401, or your client may fail to receive a response at all because the connection times out. The response body is a third consideration. Even if it contains valid JSON, it may contain an error message rather than the data you requested.

Watch these selected passages from Tech With Tim’s Master Python Requests In 15 Minutes. Call Any API. The examples use public APIs; we’ll apply the same request and response mechanics to an authenticated call.

Master Python Requests In 15 Minutes. Call Any API

Tech With Tim introduces the parts of an HTTP exchange, demonstrates a GET request, and later adds error handling and a bearer token. Watch the selected passages for those mechanics rather than copying the video's project setup.

Start with request and response for methods, headers, status codes, and bodies. Then watch GET and JSON to see a response parsed into Python data. Finish with failure handling and bearer authentication. Pay attention to where the token is placed: in a request header, not in the printed output.

For this lesson, the request is a GET: you are retrieving account information, not modifying it. Its Authorization header carries a bearer token. The server decides whether that token permits access and returns a status and body. A token proves possession of a credential; your script must still check that the response is successful and has the expected structure.


Know what requests does—and what it does not

We’ll use the third-party Python package requests. In your isolated environment, install it if necessary and record the resolved version using the pinned-dependency approach from the first lesson. Check the active environment with python -m pip show requests.

Read the following parts of the Requests Quickstart. They clarify a subtle but important rule: decoding JSON does not establish that the API call succeeded.

Quickstart — Requests 2.34.2 documentation

Read the Requests documentation for headers, JSON decoding, status checks, and timeouts. These are the four operations the client below relies on.

In “JSON Response Content,” begin at the explanation of the builtin decoder. Read JSON and errors, including the examples of empty or invalid JSON and an error response containing valid JSON. Continue through the sentence beginning “To check that a request is successful” for its status-checking advice. In “Custom Headers,” read from “If you'd like to add HTTP headers” through the note ending “The headers are simply passed on into the final request”; notice the warning that other authentication settings can override an Authorization header. In “Response Status Codes,” read status checking. Finally, in “Timeouts,” read timeout behavior, then read the adjacent “Errors and Exceptions” subsection for the exception names.

The client will follow this order:

  1. Send the request with a token and a timeout.
  2. Check the HTTP status before treating the body as account data.
  3. Decode JSON and check the fields the application needs.

response.json() can return a dictionary, list, string, or other JSON value. In the example below, the application expects an object containing a string-valued login; it checks that assumption rather than indexing blindly.


Make an authenticated call without needing an account

First, create a small local practice API. It listens only on your machine, requires a dummy bearer token, and returns JSON. This is a teaching server, not a model for securing a production service. Save it as practice_api.py beside your previous Python scripts:

import json
from http.server import BaseHTTPRequestHandler, HTTPServer


class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path != "/user":
            self.send_error(404)
            return

        if self.headers.get("Authorization") == "Bearer local-demo-token":
            status = 200
            body = {"login": "demo-user", "role": "reader"}
        else:
            status = 401
            body = {"error": "unauthorized"}

        encoded = json.dumps(body).encode("utf-8")
        self.send_response(status)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(encoded)))
        self.end_headers()
        self.wfile.write(encoded)


if __name__ == "__main__":
    print("Practice API listening on 127.0.0.1:8765", flush=True)
    HTTPServer(("127.0.0.1", 8765), Handler).serve_forever()

Notice what the server checks: the header value must include the scheme Bearer followed by the token. On failure, it still sends valid JSON, but with status 401 and an error field. That is why parsing alone would be an insufficient success check.

In one terminal, start it:

python practice_api.py

Leave that terminal running. In another terminal, save this client as fetch_account.py:

import os
import sys

import requests


LOCAL_URL = "http://127.0.0.1:8765/user"
url = os.getenv("API_URL", LOCAL_URL)
token = os.getenv("API_TOKEN")

if not token:
    raise SystemExit("Set API_TOKEN before running.")
if url != LOCAL_URL and not url.startswith("https://"):
    raise SystemExit("Use HTTPS for a remote API.")

try:
    response = requests.get(
        url,
        headers={
            "Authorization": f"Bearer {token}",
            "Accept": "application/json",
        },
        timeout=(3, 10),
        allow_redirects=False,
    )
    response.raise_for_status()

    # raise_for_status() does not reject redirects or every unexpected status.
    if response.status_code != 200:
        raise ValueError(f"Expected HTTP 200, got {response.status_code}")

    data = response.json()
    if not isinstance(data, dict) or not isinstance(data.get("login"), str):
        raise ValueError("Expected a JSON object with a string 'login'")

except requests.exceptions.HTTPError as exc:
    print(f"API returned HTTP {exc.response.status_code}", file=sys.stderr)
    sys.exit(1)
except requests.exceptions.JSONDecodeError:
    print("API response was not valid JSON", file=sys.stderr)
    sys.exit(1)
except requests.exceptions.Timeout:
    print("API request timed out", file=sys.stderr)
    sys.exit(1)
except requests.exceptions.RequestException as exc:
    print(f"API request failed: {type(exc).__name__}", file=sys.stderr)
    sys.exit(1)
except ValueError as exc:
    print(f"Unexpected API response: {exc}", file=sys.stderr)
    sys.exit(1)

print(f"Authenticated account: {data['login']}")

Set the dummy token in the second terminal and run the client:

export API_TOKEN=local-demo-token
python fetch_account.py

You should see Authenticated account: demo-user. The output is derived from the parsed JSON dictionary, not from response text searched for a matching word.

The two timeout values bound waiting for a connection and waiting for socket activity, respectively; they are not a guaranteed limit on the complete download. allow_redirects=False also makes an unexpected redirect visible instead of silently following it. Those choices keep this small authenticated request easier to reason about.

For one quick failure check, set API_TOKEN to a different dummy value and rerun the client. It should report HTTP 401 rather than trying to read login from the error JSON. Restore the dummy token afterward. Stop the practice server with Ctrl+C when finished.


Transfer the pattern to a real API

The same client can query an authenticated account endpoint at https://api.github.com/user if you have a suitable personal access token. The real endpoint is optional; the local call above already exercises an authenticated HTTP request. For a real call, review the endpoint and its credential requirements, use a minimally privileged token, and set API_URL to that HTTPS address.

Avoid putting a real token in source code, a committed configuration file, a URL, or a shell command saved in history. For example, in a Bash terminal you can enter it without echoing its value:

export API_URL=https://api.github.com/user
read -r -s -p "API token: " API_TOKEN; echo
export API_TOKEN
python fetch_account.py

The endpoint’s successful response includes a login field, so the client’s shape check still applies. Other APIs may use a different authentication scheme, status, or JSON structure: consult that API’s contract and change those assumptions deliberately. A 401 or 403 is an HTTP response to investigate—often involving the credential or its permissions—not a JSON-decoding problem. Requests can also draw authentication from a .netrc file, which may override a manually supplied header; keep that in mind if an otherwise correct token seems to be ignored.

For future LLM automation, the returned dictionary might hold a model-generated answer, usage data, or a tool result rather than an account name. The discipline remains the same: protect the credential, check transport and HTTP outcomes, then validate the data before using it in a coding workflow.


Takeaways

An authenticated API call is more than requests.get() followed by .json(). Supply the token in the required header, set a timeout, distinguish a failed response from a failed connection, and verify both the HTTP status and the JSON fields your program relies on. Never print or commit a real token.

Next, you’ll write pytest tests that replace filesystem, subprocess, and HTTP interactions with controlled stand-ins, so checks like this client’s 401 handling can run without a live service.

Can't find a good explanation? Sign up and we'll make it for you

Sign up