klyrek-api
Status: built, not yet on PyPI (blocked on PyPI's new-project rate limit) — install from source
API surface discovery: OpenAPI/Swagger spec detection, common API-root probing, and GraphQL detection, plus REST-pattern classification for already-discovered URLs.
from klyrek_api.inventory import build_api_inventory
from klyrek_api.classify import is_rest_like
endpoints = build_api_inventory(client, "https://api.sandbox.klyrek.com/")
is_rest_like("https://api.sandbox.klyrek.com/api/v1/products") # True
build_api_inventory probes a curated list of common spec paths (/openapi.json, /swagger.json, /api-docs, /v2/api-docs, /v3/api-docs, /swagger/v1/swagger.json, /.well-known/openapi.json) and common API-root paths (/api, /api/v1, /api/v2, /rest, /rest/v1), and parses any OpenAPI/Swagger spec it finds into real Endpoint records via parse_openapi_paths.
The sandbox's own openapi.json (a hand-authored, valid OpenAPI 3.0 document) parses cleanly into exactly its three real routes when run through this exact function.
API reference
klyrek_api.discovery
Probe common paths where an OpenAPI/Swagger spec or a bare API root is often exposed.
discover_api_roots
discover_api_roots(client: KlyrekHTTPClient, base_url: str) -> list[Endpoint]
Probe common API root paths; a non-404 response suggests something lives there.
Source code in klyrek_api\discovery.py
| def discover_api_roots(client: KlyrekHTTPClient, base_url: str) -> list[Endpoint]:
"""Probe common API root paths; a non-404 response suggests something lives there."""
endpoints: list[Endpoint] = []
for path in COMMON_API_ROOT_PATHS:
url = urljoin(base_url, path)
if not client.scope.is_authorized(url):
continue
try:
response = client.get(url)
except Exception:
logger.debug("failed to probe api root %s", url, exc_info=True)
continue
if response.status_code == 404:
continue
endpoints.append(
Endpoint(
url=url,
method="GET",
source="api-discovery",
status_code=response.status_code,
content_type=response.headers.get("content-type"),
discovered_by="klyrek-api",
)
)
return endpoints
|
discover_openapi_spec
discover_openapi_spec(client: KlyrekHTTPClient, base_url: str) -> list[Endpoint]
Probe well-known paths for an OpenAPI/Swagger spec; parse the first one found.
Source code in klyrek_api\discovery.py
| def discover_openapi_spec(client: KlyrekHTTPClient, base_url: str) -> list[Endpoint]:
"""Probe well-known paths for an OpenAPI/Swagger spec; parse the first one found."""
for path in COMMON_SPEC_PATHS:
url = urljoin(base_url, path)
if not client.scope.is_authorized(url):
continue
try:
response = client.get(url)
except Exception:
logger.debug("failed to probe spec path %s", url, exc_info=True)
continue
if response.status_code != 200:
continue
try:
document = response.json()
except ValueError:
continue
if looks_like_openapi_spec(document):
return parse_openapi_paths(document, base_url)
return []
|
klyrek_api.inventory
Combine spec discovery, API-root probing, and GraphQL detection into one inventory.
build_api_inventory
build_api_inventory(client: KlyrekHTTPClient, base_url: str) -> list[Endpoint]
Probe a target for an OpenAPI/Swagger spec, common API roots, and a GraphQL endpoint.
Source code in klyrek_api\inventory.py
| def build_api_inventory(client: KlyrekHTTPClient, base_url: str) -> list[Endpoint]:
"""Probe a target for an OpenAPI/Swagger spec, common API roots, and a GraphQL endpoint."""
endpoints: list[Endpoint] = []
seen: set[tuple[str, str]] = set()
def add_all(discovered: list[Endpoint]) -> None:
for endpoint in discovered:
key = (endpoint.method, endpoint.url)
if key not in seen:
seen.add(key)
endpoints.append(endpoint)
add_all(discover_openapi_spec(client, base_url))
add_all(discover_api_roots(client, base_url))
add_all(discover_graphql(client, base_url))
return endpoints
|
klyrek_api.classify
Heuristics for tagging already-discovered URLs as API-shaped, without any new requests.
is_rest_like
is_rest_like(url: str) -> bool
Heuristic: does this URL's path look like a REST API route rather than a page?
Source code in klyrek_api\classify.py
| def is_rest_like(url: str) -> bool:
"""Heuristic: does this URL's path look like a REST API route rather than a page?"""
path = urlsplit(url).path
return bool(_API_PATH_PATTERN.search(path) or _NUMERIC_ID_SEGMENT.search(path))
|
klyrek_api.openapi
Parse OpenAPI/Swagger spec documents into Endpoint records.
looks_like_openapi_spec
looks_like_openapi_spec(document: Any) -> bool
Cheap structural check: does this parsed JSON body look like an OpenAPI/Swagger spec?
Source code in klyrek_api\openapi.py
| def looks_like_openapi_spec(document: Any) -> bool:
"""Cheap structural check: does this parsed JSON body look like an OpenAPI/Swagger spec?"""
return isinstance(document, dict) and ("openapi" in document or "swagger" in document)
|
parse_openapi_paths
parse_openapi_paths(spec: dict[str, Any], base_url: str) -> list[Endpoint]
Extract Endpoint records from an OpenAPI/Swagger spec's paths object.
Source code in klyrek_api\openapi.py
| def parse_openapi_paths(spec: dict[str, Any], base_url: str) -> list[Endpoint]:
"""Extract Endpoint records from an OpenAPI/Swagger spec's `paths` object."""
endpoints: list[Endpoint] = []
base_path = _base_path(spec)
for path, path_item in spec.get("paths", {}).items():
if not isinstance(path_item, dict):
continue
full_path = f"{base_path.rstrip('/')}/{path.lstrip('/')}" if base_path else path
url = urljoin(base_url, full_path)
shared_params = [
p["name"]
for p in path_item.get("parameters", [])
if isinstance(p, dict) and p.get("name")
]
for method, operation in path_item.items():
if method.lower() not in _HTTP_METHODS or not isinstance(operation, dict):
continue
params = [
*shared_params,
*(
p["name"]
for p in operation.get("parameters", [])
if isinstance(p, dict) and p.get("name")
),
]
endpoints.append(
Endpoint(
url=url,
method=method.upper(),
source="api-spec",
params=params,
discovered_by="klyrek-api",
)
)
return endpoints
|
klyrek_api.graphql
Detect a GraphQL endpoint via a minimal, introspection-free probe query.
discover_graphql
discover_graphql(client: KlyrekHTTPClient, base_url: str) -> list[Endpoint]
Probe common GraphQL paths; confirm by response shape, not just HTTP status.
Source code in klyrek_api\graphql.py
| def discover_graphql(client: KlyrekHTTPClient, base_url: str) -> list[Endpoint]:
"""Probe common GraphQL paths; confirm by response shape, not just HTTP status."""
endpoints: list[Endpoint] = []
for path in COMMON_GRAPHQL_PATHS:
url = urljoin(base_url, path)
if not client.scope.is_authorized(url):
continue
try:
response = client.post(url, json=_PROBE_QUERY)
except Exception:
logger.debug("failed to probe graphql path %s", url, exc_info=True)
continue
if response.status_code >= 500:
continue
try:
data = response.json()
except ValueError:
continue
if _looks_like_graphql_response(data):
endpoints.append(
Endpoint(
url=url,
method="POST",
source="api-graphql",
status_code=response.status_code,
discovered_by="klyrek-api",
)
)
return endpoints
|