Add hierarchical key-scoped catalog API for machine consumers
GET /catalog walks agents through progressive disclosure: available APIs with descriptions, then one API's tags, then a tag's endpoints, then full detail for a single operation with $refs resolved inline. Oversized tags (tag-poor upstreams) fall back to path-prefix groups, drillable with ?prefix= and compressed through single-child chains. Responses are filtered to the key's grants, carry ETags for cheap revalidation, and reuse the discovery spec cache. Key auth is shared with the docs portal via portal.resolve_api_key; 'catalog' and 'mcp' are now reserved slugs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
b90ecaf61c
commit
9a3feccc90
+11
-2
@@ -183,8 +183,9 @@ def build_key_spec(db: Session, api_key: ApiKey, gateway_url: str) -> dict:
|
||||
}
|
||||
|
||||
|
||||
@router.get("/openapi.json", include_in_schema=False)
|
||||
def key_scoped_openapi(request: Request, db: Session = Depends(get_db)):
|
||||
def resolve_api_key(request: Request, db: Session) -> ApiKey | JSONResponse:
|
||||
"""Authenticate a consumer-facing request by X-API-Key. Returns the key,
|
||||
or the error response to send back."""
|
||||
plain_key = request.headers.get(config.API_KEY_HEADER)
|
||||
if not plain_key:
|
||||
return _error(401, "missing_api_key",
|
||||
@@ -194,6 +195,14 @@ def key_scoped_openapi(request: Request, db: Session = Depends(get_db)):
|
||||
.one_or_none())
|
||||
if api_key is None or not api_key.is_active or not api_key.user.is_active:
|
||||
return _error(403, "invalid_api_key", "API key is unknown or has been revoked.")
|
||||
return api_key
|
||||
|
||||
|
||||
@router.get("/openapi.json", include_in_schema=False)
|
||||
def key_scoped_openapi(request: Request, db: Session = Depends(get_db)):
|
||||
api_key = resolve_api_key(request, db)
|
||||
if isinstance(api_key, JSONResponse):
|
||||
return api_key
|
||||
|
||||
spec = build_key_spec(db, api_key, str(request.base_url).rstrip("/"))
|
||||
return JSONResponse(spec, headers={"Cache-Control": "no-store"})
|
||||
|
||||
Reference in New Issue
Block a user