# Why Requests Get Params Fail: URL vs. params Argument in Python

> Fix requests get params failures. Learn why Python requests silently omits query parameters when using the wrong argument and how to correctly send them in your API calls.

- Repository: [Python Software Foundation/requests](https://github.com/psf/requests)
- Tags: debugging-guide
- Published: 2026-02-16

---

**Passing a dictionary as the third positional argument to `requests.get()` causes query parameters to be silently omitted because the function interprets it as the `timeout` value, not the `params` mapping.**

When working with the `psf/requests` library, developers often expect `requests.get(url, {"key": "value"})` to automatically append query parameters to the URL. While this works when passed as the second positional argument, subtle mistakes in argument positioning or naming cause the `params` dictionary to be ignored entirely. This behavior stems from the method signature in [`src/requests/api.py`](https://github.com/psf/requests/blob/main/src/requests/api.py) and the URL preparation logic in [`src/requests/models.py`](https://github.com/psf/requests/blob/main/src/requests/models.py).

## How Requests Processes the params Argument

The `requests` library follows a strict two-step pipeline for assembling GET requests. Understanding this flow clarifies why parameters sometimes fail to appear in the final URL.

### The API Entry Point in api.py

The high-level `get()` function in [`src/requests/api.py`](https://github.com/psf/requests/blob/main/src/requests/api.py) (lines 62-74) defines `params` as the second positional argument:

```python
def get(url, params=None, **kwargs):
    return request("get", url, params=params, **kwargs)

```

When you call `requests.get("https://api.example.com", {"search": "python"})`, the dictionary correctly maps to the `params` parameter. However, if you add a third positional argument—such as `requests.get(url, {"search": "python"}, 5)`—Python raises a `TypeError` because `get()` accepts only two positional arguments (`url` and `params`), with all other options requiring keyword syntax.

### URL Preparation in models.py

Once the `params` dictionary reaches the `PreparedRequest` class in [`src/requests/models.py`](https://github.com/psf/requests/blob/main/src/requests/models.py), the `prepare_url()` method (lines 72-84) handles the actual encoding:

```python
def prepare_url(self, url, params):
    # ...

    enc_params = self._encode_params(params)
    if enc_params:
        if query:
            query = f"{query}&{enc_params}"
        else:
            query = enc_params
    # ...

    self.url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment]))

```

The `_encode_params` method converts dictionaries, lists of tuples, or raw strings into URL-encoded query strings. If the original URL already contains a query component (e.g., `https://api.com?existing=1`), the method appends the new parameters with an `&` separator. This ensures that `params` are **always** sent when provided correctly, regardless of whether the URL contains existing query strings.

## Common Mistakes That Cause Params to Disappear

Despite the robust internal handling, several caller-side errors result in empty query strings:

- **Passing `params` as a third positional argument**: Because `requests.get()` only accepts `url` and `params` positionally, adding a timeout value positionally (e.g., `requests.get(url, params, 5)`) triggers a `TypeError` or causes the dictionary to be misinterpreted depending on the library version.

- **Misspelling the keyword**: Using `param` (singular) instead of `params` bypasses the encoding logic entirely, as the function receives an unexpected keyword argument that gets swallowed by `**kwargs` but never processed as query parameters.

- **Passing `None` or an empty dictionary**: While technically valid, `params=None` or `params={}` results in no query string being appended, which can appear as a failure if the developer expects default behavior.

- **Custom `PreparedRequest` subclasses**: Overriding `prepare_url()` without calling `super().prepare_url()` or failing to handle the `params` argument in the subclass drops the encoded query string.

## Correct Implementation Examples

To ensure query parameters reach the server, use explicit keyword arguments or proper positional placement:

```python
import requests

# Method 1: Keyword argument (recommended)

resp = requests.get(
    "https://httpbin.org/get",
    params={"search": "python", "page": 2},
    timeout=5,
)
print(resp.url)

# → https://httpbin.org/get?search=python&page=2

# Method 2: Positional argument (valid but less readable)

resp = requests.get(
    "https://httpbin.org/get",
    {"search": "python", "page": 2},
)
print(resp.url)

# → https://httpbin.org/get?search=python&page=2

# Method 3: Combining with existing URL query strings

resp = requests.get(
    "https://httpbin.org/get?existing=1",
    params={"new": "2"},
)
print(resp.url)

# → https://httpbin.org/get?existing=1&new=2

```

Avoid ambiguous positional calls that include timeout values without keywords:

```python

# ❌ Incorrect: TypeError or misinterpretation

resp = requests.get(
    "https://httpbin.org/get",
    {"search": "python"},  # This is params

    5                      # This causes TypeError (unexpected positional arg)

)

```

## Summary

- The `requests.get()` function accepts `params` as its second positional argument or as an explicit keyword argument.
- In [`src/requests/models.py`](https://github.com/psf/requests/blob/main/src/requests/models.py), the `prepare_url()` method encodes the `params` dictionary and appends it to the base URL, merging with any existing query strings.
- Parameters fail to appear when passed as a third positional argument (causing a `TypeError`), when the keyword is misspelled (e.g., `param`), or when using a custom `PreparedRequest` that bypasses the encoding logic.
- Always use explicit keyword syntax (`params={...}`) alongside other options like `timeout` to avoid ambiguity.

## Frequently Asked Questions

### Why does this behave differently from Axios?

While both libraries use a `params` configuration object, **Axios** requires `params` to be nested inside a config object as the second argument (e.g., `axios.get(url, {params: {...}})`), whereas **Python requests** accepts `params` as a top-level argument (e.g., `requests.get(url, params={...})`). Passing a plain object as the second argument to Axios sends it as the request body or config, not as URL parameters, which explains the confusion between the two libraries.

### Can I pass params as the second positional argument safely?

Yes, `requests.get("https://api.com", {"key": "val"})` works correctly because the function signature `get(url, params=None, **kwargs)` assigns the dictionary to the `params` parameter. However, this approach is less readable and breaks if you add a third positional argument (such as a timeout value), which triggers a `TypeError`. For maintainability, always use the explicit keyword syntax `params={...}`.

### What happens if I mix URL query strings and the params argument?

The `prepare_url()` method in [`src/requests/models.py`](https://github.com/psf/requests/blob/main/src/requests/models.py) merges the two sources. If your URL already contains `?existing=1` and you pass `params={"new": "2"}`, the final URL becomes `?existing=1&new=2`. The library handles the `&` concatenation automatically, ensuring no query parameters are lost during the merge.

### Why are my params not URL-encoded correctly?

The `_encode_params()` method handles encoding via `urllib.parse.urlencode` (accessed through internal utilities in [`src/requests/_internal_utils.py`](https://github.com/psf/requests/blob/main/src/requests/_internal_utils.py)). If you pass a nested dictionary or a custom object that doesn't implement the mapping protocol, the encoding might fail or produce unexpected results. Ensure you pass flat dictionaries `{"key": "value"}` or lists of tuples `[("key", "value")]` for proper URL encoding.