Created
March 26, 2026 03:58
-
-
Save drichardson/6a52f27593cd198fc293f398c56b12e5 to your computer and use it in GitHub Desktop.
Datadog GET /api/v2/metrics links.next cursor bug reproduction
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env -S uv run --with requests | |
| # /// script | |
| # requires-python = ">=3.12" | |
| # dependencies = ["requests"] | |
| # /// | |
| """ | |
| Demonstrates a bug in the Datadog GET /api/v2/metrics API where the cursor | |
| URL returned in links.next is not followable (returns 400 Bad Request). | |
| Requires DD_API_KEY and DD_APP_KEY environment variables. | |
| Usage: | |
| doppler run -- ./scripts/datadog_metrics_cursor_bug.py | |
| """ | |
| import os | |
| import sys | |
| import requests | |
| def main() -> None: | |
| api_key = os.environ.get("DD_API_KEY") | |
| app_key = os.environ.get("DD_APP_KEY") | |
| if not api_key or not app_key: | |
| print("ERROR: DD_API_KEY and DD_APP_KEY must be set", file=sys.stderr) | |
| sys.exit(1) | |
| headers = { | |
| "DD-API-KEY": api_key, | |
| "DD-APPLICATION-KEY": app_key, | |
| } | |
| base_url = "https://api.datadoghq.com" | |
| with requests.Session() as session: | |
| session.headers.update(headers) | |
| # Step 1: fetch first page | |
| url1 = f"{base_url}/api/v2/metrics?filter[configured]=false&page[size]=1000" | |
| print(f"Step 1: GET {url1}") | |
| r1 = session.get(url1) | |
| print(f" Status: {r1.status_code}") | |
| assert r1.status_code == 200, f"Expected 200, got {r1.status_code}" | |
| body1 = r1.json() | |
| page1_count = len(body1["data"]) | |
| next_url = body1["links"]["next"] | |
| cursor = body1["meta"]["pagination"]["next_cursor"] | |
| print(f" Items returned: {page1_count}") | |
| print(f" next_cursor length: {len(cursor)} chars") | |
| print(f" links.next length: {len(next_url)} chars") | |
| print() | |
| # Step 2: follow links.next exactly as returned by the API | |
| print(f"Step 2: GET {next_url}") | |
| r2 = session.get(next_url) | |
| print(f" Status: {r2.status_code}") | |
| if r2.status_code == 400: | |
| print() | |
| print("BUG REPRODUCED:") | |
| print(" GET /api/v2/metrics returned a links.next URL that the server") | |
| print(" refuses to follow (400 Bad Request).") | |
| print( | |
| f" The page[cursor] query parameter is {len(cursor)} characters long," | |
| ) | |
| print(" which appears to exceed the server's URL length limit.") | |
| sys.exit(1) | |
| elif r2.status_code == 200: | |
| print(" OK - pagination worked (bug not reproduced)") | |
| else: | |
| print(f" Unexpected status: {r2.status_code}") | |
| print(f" Body: {r2.text[:500]}") | |
| sys.exit(1) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment