Composio covers many services but not all. For an endpoint without a tool, you fall back to direct HTTP — the universal escape hatch. The library: requests.
import requests
r = requests.get("https://httpbin.org/get", timeout=10)
print(r.status_code)
print(r.json().get("url"))Three pieces: a method (get/post/put/delete), a URL, optional kwargs (timeout, headers, params, json).
Why timeout=10?
Default requests timeout is infinite — a script that hangs forever on a flaky endpoint. Always pass an explicit timeout. 10 seconds is a reasonable default for a single API call; tune up or down based on the endpoint's typical latency.
And r.json() — what's the difference from json.loads(r.text)?
r.json() does both: parses the body and decodes the encoding. It raises JSONDecodeError on non-JSON. json.loads(r.text) does the same but you'd need to handle encoding edge cases yourself. Reach for r.json() first; only drop down to r.text for non-JSON responses or debugging.
import requests
r = requests.get("https://api.example.com/items",
params={"page": 1},
headers={"Authorization": "Bearer ..."},
timeout=10)
r.raise_for_status() # raises HTTPError on 4xx/5xx
data = r.json() # parse body as JSONFour kwargs cover 95% of needs:
| Kwarg | Purpose |
|---|---|
params | Query string — ?key=value pairs |
headers | HTTP headers (auth, content-type, custom) |
json | Request body — auto-encoded as JSON, sets Content-Type |
timeout | Seconds before raising Timeout |
Default is no timeout. A flaky endpoint can hang your script indefinitely. Always set one:
requests.get(url, timeout=10) # bare-minimum hygiene
requests.get(url, timeout=(3, 10)) # (connect, read) — even betterr.status_code # int: 200, 404, 500
r.headers # dict-like: {'Content-Type': 'application/json', 'X-RateLimit-Remaining': '...'}
r.json() # parse body as JSON; raises on non-JSON
r.text # body as string
r.content # body as bytes (for binary)# Reflex 1: raise_for_status() turns 4xx/5xx into exceptions you can `except` on
r.raise_for_status()
# Reflex 2: handle JSON-decode errors
try:
data = r.json()
except ValueError: # JSONDecodeError is a subclass
print(f"non-JSON response: {r.text[:200]}")
raiser = requests.post(
"https://api.example.com/items",
json={"name": "foo", "value": 42}, # auto-encoded, sets Content-Type
headers={"Authorization": "Bearer ..."},
timeout=10,
)Use json= (auto-encoded) over data=json.dumps(...) — fewer ways to typo the content-type.
Direct requests calls don't run in the browser-Pyodide context — only in the Sandbox. This and every HTTP lesson is a sandbox-pro lesson; it won't run in the in-browser interpreter.
Composio covers many services but not all. For an endpoint without a tool, you fall back to direct HTTP — the universal escape hatch. The library: requests.
import requests
r = requests.get("https://httpbin.org/get", timeout=10)
print(r.status_code)
print(r.json().get("url"))Three pieces: a method (get/post/put/delete), a URL, optional kwargs (timeout, headers, params, json).
Why timeout=10?
Default requests timeout is infinite — a script that hangs forever on a flaky endpoint. Always pass an explicit timeout. 10 seconds is a reasonable default for a single API call; tune up or down based on the endpoint's typical latency.
And r.json() — what's the difference from json.loads(r.text)?
r.json() does both: parses the body and decodes the encoding. It raises JSONDecodeError on non-JSON. json.loads(r.text) does the same but you'd need to handle encoding edge cases yourself. Reach for r.json() first; only drop down to r.text for non-JSON responses or debugging.
import requests
r = requests.get("https://api.example.com/items",
params={"page": 1},
headers={"Authorization": "Bearer ..."},
timeout=10)
r.raise_for_status() # raises HTTPError on 4xx/5xx
data = r.json() # parse body as JSONFour kwargs cover 95% of needs:
| Kwarg | Purpose |
|---|---|
params | Query string — ?key=value pairs |
headers | HTTP headers (auth, content-type, custom) |
json | Request body — auto-encoded as JSON, sets Content-Type |
timeout | Seconds before raising Timeout |
Default is no timeout. A flaky endpoint can hang your script indefinitely. Always set one:
requests.get(url, timeout=10) # bare-minimum hygiene
requests.get(url, timeout=(3, 10)) # (connect, read) — even betterr.status_code # int: 200, 404, 500
r.headers # dict-like: {'Content-Type': 'application/json', 'X-RateLimit-Remaining': '...'}
r.json() # parse body as JSON; raises on non-JSON
r.text # body as string
r.content # body as bytes (for binary)# Reflex 1: raise_for_status() turns 4xx/5xx into exceptions you can `except` on
r.raise_for_status()
# Reflex 2: handle JSON-decode errors
try:
data = r.json()
except ValueError: # JSONDecodeError is a subclass
print(f"non-JSON response: {r.text[:200]}")
raiser = requests.post(
"https://api.example.com/items",
json={"name": "foo", "value": 42}, # auto-encoded, sets Content-Type
headers={"Authorization": "Bearer ..."},
timeout=10,
)Use json= (auto-encoded) over data=json.dumps(...) — fewer ways to typo the content-type.
Direct requests calls don't run in the browser-Pyodide context — only in the Sandbox. This and every HTTP lesson is a sandbox-pro lesson; it won't run in the in-browser interpreter.
Create a free account to get started. Paid plans unlock all tracks.