2025, Dec 25 17:00
Slack files.list returns empty array in Python requests? Send channel as query params, not JSON
Troubleshoot Slack API files.list empty results in Python requests. See why body data fails, use params with URL query parameters, and match Postman responses.
When a stable integration suddenly returns empty results, it’s rarely the API “going silent” and more often a subtle mismatch in how the request is shaped. That’s exactly what can happen with Slack’s files.list: the same token and channel return files in Postman, but Python’s requests yields an empty array. The culprit lies in how parameters are sent.
Reproducing the issue in Python
The following snippet demonstrates a request that used to work, but now returns an empty files array while Postman shows actual files for the same channel and credentials.
import requests
import json
base_url = "https://slack.com/api/"
bearer_token = "Bearer xoxb-<secret>"
payload = { "channel": "<obscured>" }
resp = requests.post(
base_url + "files.list",
headers={
"Authorization": bearer_token,
"Content-Type": "application/json; charset=utf-8"
},
data=payload
)
try:
parsed = json.loads(resp.text)
except:
print("Read error")
has_error = True
if ("files" not in parsed):
if ("error" in parsed):
print(parsed["error"])
if ("warning" in parsed):
print(parsed["warning"])
has_error = True
items = parsed["files"]
items.sort(key=lambda i: i["timestamp"])
count = len(items)
print(str(resp))
print(str(resp.request.body))
print(str(resp.request.headers["Content-Type"]))
print(str(resp.text))
The response is 200 OK with an empty list of files, while Postman returns three files for the same channel.
What’s really going on
The difference is not in the token or permissions, but in how parameters are sent. Using data= with requests sends a dictionary as form-encoded data, despite any Content-Type header you set manually. For this Slack method, the API expects parameters as URL query parameters. That’s why Postman works while the Python call doesn’t: the requests are not equivalent.
Switching to json= still produces the same empty result because the endpoint isn’t using a JSON body for these parameters. Putting the channel value in the URL, however, immediately returns the expected files.
Fixing the request shape
The correct approach is to provide the parameters in the URL. In requests, that’s done via the params argument. This keeps the code clean and mirrors the working Postman call.
import requests
import json
base_url = "https://slack.com/api/"
bearer_token = "Bearer xoxb-<secret>"
query = { "channel": "<obscured>" }
resp = requests.post(
base_url + "files.list",
headers={
"Authorization": bearer_token,
"Content-Type": "application/json"
},
params=query
)
# The rest of the logic remains the same
try:
parsed = json.loads(resp.text)
except:
print("Read error")
has_error = True
if ("files" not in parsed):
if ("error" in parsed):
print(parsed["error"])
if ("warning" in parsed):
print(parsed["warning"])
has_error = True
items = parsed["files"]
items.sort(key=lambda i: i["timestamp"])
count = len(items)
print(str(resp))
print(str(resp.request.body))
print(str(resp.request.headers["Content-Type"]))
print(str(resp.text))
If you prefer a quick confirmation, appending the query string directly to the URL also works: files.list?channel=<secret>. The params approach is simply more elegant and less error-prone.
Why this matters
Parity between tools is crucial. Postman and Python requests can send structurally different payloads even when they look similar at a glance. Here, data= produced form data, json= put everything in the body as JSON, while the endpoint expects URL parameters for channel. That subtle mismatch is enough to flip a working integration into returning empty results.
When troubleshooting, compare request bodies, headers, and the final URL. Printing resp.request.body, the Content-Type, and the full response helps reveal differences. Generating code directly from Postman or converting curl to Python can also save time and prevent silent mismatches.
Takeaways
Make sure the request shape matches what the Slack API expects for the method you’re calling. For files.list, pass parameters as query string using params= in requests.post. Avoid relying on content-type headers alone to change how a library encodes your payload. And when two clients disagree, reduce the problem to raw HTTP: align the URL, headers, and body until the requests are truly identical.