{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreihx5vlkbsie545kabj2vt6w6zq6juk2wiesrcheamo5e24bu4ievu",
"uri": "at://did:plc:lk3jfj3zq4k4wxnk474axylu/app.bsky.feed.post/3mphy7z7vten2"
},
"path": "/t/feature-request-include-api-key-name-or-group-by-api-key-name-in-the-usage-costs-api-responses/1385288#post_4",
"publishedAt": "2026-06-30T02:32:03.000Z",
"site": "https://community.openai.com",
"tags": [
"@dataclass",
"@asynccontextmanager",
"@app.get",
"@app.api_route"
],
"textContent": "### Currently the feature shape you request: API projects\n\nYou can use project as containers of a single product and a single API key. At least there, the ID is end-to-end transparent, can be named, and not some obfuscated version.\n\n### Keys are secret-shaped with an undocumented obfuscation layer\n\n#### Secret types of secret keys used by OpenAI\n\nOpenAI has no documentation about the internal or transformed non-employable API keys that they show in the usage on the Platform site or in line items or buckets in the usage. You are almost expected to see weird looking API keys, and then try to map them back yourself by making some calls and see what shows up in the billing. It needs foundational documentation of why they are showing a “key” you’ve never seen before.\n\nThis internal/repeatable ID should also be something reported on an API key in the platform site API key creation interface - to even make sense of stuff, to start with.\n\n#### Name aliases: yes, useful\n\nNaming API keys is a solid idea. It would probably have to be done at creation time in the platform site, and then there’s a bunch of API surfaces potentially consuming a name where this could not be refactored, but would have to be OpenAI placing a “data amender” to enhance database returns in API call responses.\n\n* * *\n\n### Code: one place to put your key:name and have output amended.\n\nI took your concern, (about 5x as much of my own specification-writing, and about 50x as much API documentation). Made it input to my coding coder AI.\nResult: a proxy for RESTful OpenAI endpoints, supporting /v1/organization/* that would amend responses with API keys with a parallel _key name_ - where you have to still intuit and provide in your own map, demonstrated as hard-coded. The console will report API key map matches or non-matches in usage data products it is repeating back and potentially amending.\n\nfastapi>=0.111,<1\nhttpx[http2]>=0.27,<1\nuvicorn[standard]>=0.30,<1\n\n\n # OpenAI organization usage/cost API key name proxy\n\n This is a minimal asyncio proxy for OpenAI organization usage and cost endpoints.\n It forwards requests shaped like:\n\n ```text\n /v1/organization/costs\n /v1/organization/usage/completions\n /v1/organization/usage/embeddings\n /v1/organization/usage/images\n /v1/organization/usage/audio_speeches\n /v1/organization/usage/audio_transcriptions\n /v1/organization/usage/moderations\n /v1/organization/usage/vector_stores\n /v1/organization/usage/code_interpreter_sessions\n /v1/organization/usage/file_search_calls\n /v1/organization/usage/web_search_calls\n ```\n\n The proxy does not store or mint bearer tokens. It forwards the caller's\n `Authorization` header to OpenAI and returns OpenAI's HTTP status code and error\n shape when OpenAI responds with an error.\n\n For JSON responses, every object containing `api_key_id` receives an additional\n field:\n\n ```json\n {\n \"api_key_id\": \"key_1234567890abcdef\",\n \"api_key_name\": \"checkout-service-production\"\n }\n ```\n\n If the `api_key_id` is absent from the proxy's demonstration map, the new field\n is set to `\"unknown\"` and the console log records the unmatched value.\n\n ## Install\n\n ```bash\n cd v1_organization_proxy\n python -m venv .venv\n . .venv/bin/activate\n pip install -r requirements.txt\n ```\n\n ## Run\n\n ```bash\n python app.py --host 127.0.0.1 --port 18080\n ```\n\n Environment variables are also supported:\n\n ```bash\n OPENAI_ORG_PROXY_HOST=0.0.0.0 \\\n OPENAI_ORG_PROXY_PORT=18080 \\\n OPENAI_ORG_PROXY_TIMEOUT_SECONDS=60 \\\n python app.py\n ```\n\n ## Call\n\n The endpoint path mirrors OpenAI's organization endpoints:\n\n ```bash\n curl \"http://127.0.0.1:18080/v1/organization/costs?start_time=1730419200&limit=1&group_by=api_key_id\" \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\" \\\n -H \"Content-Type: application/json\"\n ```\n\n The proxy also accepts `group_by=api_key_name` as a caller convenience and sends\n `group_by=api_key_id` upstream, because OpenAI currently groups by key ID:\n\n ```bash\n curl \"http://127.0.0.1:18080/v1/organization/costs?start_time=1730419200&group_by=api_key_name\" \\\n -H \"Authorization: Bearer $OPENAI_ADMIN_KEY\"\n ```\n\n ## Configure the internal map\n\n Edit `API_KEY_ID_TO_NAME` in `app.py`:\n\n ```python\n API_KEY_ID_TO_NAME = {\n \"key_1234567890abcdef\": \"checkout-service-production\",\n \"key_abcdef1234567890\": \"mobile-app-production\",\n }\n ```\n\n The identifiers are OpenAI organization usage/cost API key IDs, not secret key\n values. The mapping block is intentionally small and hard-coded for reference\n use. In a production deployment, replace it with a private configuration file,\n database, or internal service.\n\n ## Use from OpenAI SDK clients\n\n Configure the SDK base URL to the proxy's high port:\n\n ```python\n from openai import OpenAI\n\n client = OpenAI(\n api_key=\"admin-key-here\",\n base_url=\"http://127.0.0.1:18080/v1\",\n )\n ```\n\n Requests made by the SDK to `/organization/*` are forwarded to OpenAI and JSON\n responses are amended with `api_key_name`.\n\n\n\n import argparse\n import asyncio\n import json\n import logging\n import os\n import sys\n from collections.abc import AsyncIterator, Iterable\n from contextlib import asynccontextmanager\n from dataclasses import dataclass\n from urllib.parse import urlencode\n\n import httpx\n import uvicorn\n from fastapi import FastAPI, Request\n from fastapi.responses import JSONResponse, Response\n\n\n type JsonValue = None | bool | int | float | str | list[\"JsonValue\"] | dict[str, \"JsonValue\"]\n\n\n OPENAI_BASE_URL = \"https://api.openai.com\"\n PROXY_NAME_FIELD = \"api_key_name\"\n UPSTREAM_API_KEY_GROUP = \"api_key_id\"\n PROXY_API_KEY_GROUP = \"api_key_name\"\n API_KEY_ID_FIELD = \"api_key_id\"\n\n HOP_BY_HOP_HEADERS = {\n \"connection\",\n \"keep-alive\",\n \"proxy-authenticate\",\n \"proxy-authorization\",\n \"te\",\n \"trailer\",\n \"transfer-encoding\",\n \"upgrade\",\n }\n\n REQUEST_HEADERS_TO_DROP = HOP_BY_HOP_HEADERS | {\n \"host\",\n \"content-length\",\n }\n\n RESPONSE_HEADERS_TO_DROP = HOP_BY_HOP_HEADERS | {\n \"content-encoding\",\n \"content-length\",\n }\n\n # Demonstration map. Replace with an external file, database, or internal API.\n # The keys are OpenAI organization usage/cost API key identifiers, not secret\n # API key values.\n API_KEY_ID_TO_NAME = {\n \"key_1234567890abcdef\": \"checkout-service-production\",\n \"key_abcdef1234567890\": \"mobile-app-production\",\n \"key_1111222233334444\": \"internal-analytics\",\n }\n\n\n @dataclass(frozen=True)\n class ProxyConfig:\n host: str\n port: int\n openai_base_url: str\n request_timeout_seconds: float\n\n\n @dataclass(frozen=True)\n class RewriteStats:\n recognized: int\n named: int\n unknown: int\n\n\n class ApiKeyNameAppender:\n def __init__(self, api_key_names: dict[str, str], logger: logging.Logger) -> None:\n self._api_key_names = api_key_names\n self._logger = logger\n\n def append_names(self, payload: JsonValue, *, request_label: str) -> tuple[JsonValue, RewriteStats]:\n counters = {\"recognized\": 0, \"named\": 0, \"unknown\": 0}\n amended = self._append_names(payload, location=\"$\", request_label=request_label, counters=counters)\n stats = RewriteStats(\n recognized=counters[\"recognized\"],\n named=counters[\"named\"],\n unknown=counters[\"unknown\"],\n )\n return amended, stats\n\n def _append_names(\n self,\n value: JsonValue,\n *,\n location: str,\n request_label: str,\n counters: dict[str, int],\n ) -> JsonValue:\n if isinstance(value, list):\n return [\n self._append_names(item, location=f\"{location}[{index}]\", request_label=request_label, counters=counters)\n for index, item in enumerate(value)\n ]\n\n if isinstance(value, dict):\n amended: dict[str, JsonValue] = {\n key: self._append_names(\n child,\n location=f\"{location}.{key}\",\n request_label=request_label,\n counters=counters,\n )\n for key, child in value.items()\n }\n\n if API_KEY_ID_FIELD in value:\n counters[\"recognized\"] += 1\n api_key_id = value[API_KEY_ID_FIELD]\n api_key_name = self._api_key_names.get(api_key_id) if isinstance(api_key_id, str) else None\n\n if api_key_name:\n counters[\"named\"] += 1\n self._logger.info(\n \"%s: recognized %s at %s as %s\",\n request_label,\n API_KEY_ID_FIELD,\n location,\n api_key_name,\n )\n else:\n counters[\"unknown\"] += 1\n api_key_name = \"unknown\"\n self._logger.info(\n \"%s: recognized %s at %s but no internal name matched value %r\",\n request_label,\n API_KEY_ID_FIELD,\n location,\n api_key_id,\n )\n\n amended[PROXY_NAME_FIELD] = api_key_name\n\n return amended\n\n return value\n\n\n def create_app(config: ProxyConfig, api_key_names: dict[str, str] | None = None) -> FastAPI:\n logger = logging.getLogger(\"organization-usage-proxy\")\n appender = ApiKeyNameAppender(api_key_names or API_KEY_ID_TO_NAME, logger)\n\n @asynccontextmanager\n async def lifespan(app: FastAPI) -> AsyncIterator[None]:\n app.state.http_client = httpx.AsyncClient(\n base_url=config.openai_base_url,\n follow_redirects=False,\n http2=True,\n timeout=httpx.Timeout(config.request_timeout_seconds),\n )\n logger.info(\n \"proxy listening on http://%s:%s and forwarding /v1/organization/* to %s\",\n config.host,\n config.port,\n config.openai_base_url,\n )\n try:\n yield\n finally:\n client: httpx.AsyncClient = app.state.http_client\n await client.aclose()\n\n app = FastAPI(\n title=\"OpenAI organization usage/cost API key name proxy\",\n version=\"0.1.0\",\n docs_url=None,\n redoc_url=None,\n lifespan=lifespan,\n )\n\n @app.get(\"/health\")\n async def health() -> dict[str, str]:\n return {\"status\": \"ok\"}\n\n @app.api_route(\n \"/v1/organization/{organization_path:path}\",\n methods=[\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"OPTIONS\", \"HEAD\"],\n )\n async def proxy_organization_request(organization_path: str, request: Request) -> Response:\n client: httpx.AsyncClient = app.state.http_client\n request_label = f\"{request.method} /v1/organization/{organization_path}\"\n upstream_path = f\"/v1/organization/{organization_path}\"\n upstream_query = rewrite_group_by_query(request.query_params.multi_items())\n upstream_url = upstream_path if not upstream_query else f\"{upstream_path}?{upstream_query}\"\n\n try:\n upstream_response = await client.request(\n request.method,\n upstream_url,\n headers=forward_request_headers(request),\n content=await request.body(),\n )\n except httpx.TimeoutException:\n logger.warning(\"%s: upstream request timed out\", request_label)\n return openai_style_proxy_error(\n status_code=504,\n code=\"upstream_timeout\",\n message=\"The proxy timed out while waiting for OpenAI.\",\n )\n except httpx.TransportError as error:\n logger.warning(\"%s: upstream transport error: %s\", request_label, error)\n return openai_style_proxy_error(\n status_code=502,\n code=\"upstream_transport_error\",\n message=\"The proxy could not connect to OpenAI.\",\n )\n\n logger.info(\"%s: OpenAI returned HTTP %s\", request_label, upstream_response.status_code)\n\n if not is_json_response(upstream_response):\n return raw_upstream_response(upstream_response)\n\n try:\n upstream_payload = upstream_response.json()\n except json.JSONDecodeError:\n logger.warning(\"%s: OpenAI returned an invalid JSON body\", request_label)\n return raw_upstream_response(upstream_response)\n\n amended_payload, stats = appender.append_names(upstream_payload, request_label=request_label)\n if stats.recognized:\n logger.info(\n \"%s: appended %s to %s item(s): %s named, %s unknown\",\n request_label,\n PROXY_NAME_FIELD,\n stats.recognized,\n stats.named,\n stats.unknown,\n )\n else:\n logger.info(\"%s: no %s fields recognized in response\", request_label, API_KEY_ID_FIELD)\n\n return JSONResponse(\n status_code=upstream_response.status_code,\n content=amended_payload,\n headers=forward_response_headers(upstream_response),\n )\n\n return app\n\n\n def rewrite_group_by_query(query_items: Iterable[tuple[str, str]]) -> str:\n rewritten: list[tuple[str, str]] = []\n for key, value in query_items:\n if key in {\"group_by\", \"group_by[]\"}:\n rewritten.append((key, rewrite_group_by_value(value)))\n else:\n rewritten.append((key, value))\n\n return urlencode(rewritten, doseq=True)\n\n\n def rewrite_group_by_value(value: str) -> str:\n values = value.split(\",\")\n if len(values) == 1:\n return UPSTREAM_API_KEY_GROUP if value == PROXY_API_KEY_GROUP else value\n\n return \",\".join(UPSTREAM_API_KEY_GROUP if item == PROXY_API_KEY_GROUP else item for item in values)\n\n\n def forward_request_headers(request: Request) -> dict[str, str]:\n return {\n key: value\n for key, value in request.headers.items()\n if key.lower() not in REQUEST_HEADERS_TO_DROP\n }\n\n\n def forward_response_headers(response: httpx.Response) -> dict[str, str]:\n return {\n key: value\n for key, value in response.headers.items()\n if key.lower() not in RESPONSE_HEADERS_TO_DROP\n }\n\n\n def is_json_response(response: httpx.Response) -> bool:\n content_type = response.headers.get(\"content-type\", \"\")\n return \"application/json\" in content_type.lower()\n\n\n def raw_upstream_response(response: httpx.Response) -> Response:\n return Response(\n status_code=response.status_code,\n content=response.content,\n headers=forward_response_headers(response),\n media_type=response.headers.get(\"content-type\"),\n )\n\n\n def openai_style_proxy_error(*, status_code: int, code: str, message: str) -> JSONResponse:\n return JSONResponse(\n status_code=status_code,\n content={\n \"error\": {\n \"message\": message,\n \"type\": \"proxy_error\",\n \"param\": None,\n \"code\": code,\n }\n },\n )\n\n\n def load_config(argv: list[str] | None = None) -> ProxyConfig:\n parser = argparse.ArgumentParser(\n description=\"Proxy OpenAI /v1/organization/* calls and append API key names to JSON results.\",\n )\n parser.add_argument(\"--host\", default=os.getenv(\"OPENAI_ORG_PROXY_HOST\", \"127.0.0.1\"))\n parser.add_argument(\"--port\", type=int, default=int(os.getenv(\"OPENAI_ORG_PROXY_PORT\", \"18080\")))\n parser.add_argument(\"--openai-base-url\", default=os.getenv(\"OPENAI_BASE_URL\", OPENAI_BASE_URL))\n parser.add_argument(\n \"--timeout\",\n type=float,\n default=float(os.getenv(\"OPENAI_ORG_PROXY_TIMEOUT_SECONDS\", \"60\")),\n help=\"Upstream request timeout in seconds.\",\n )\n args = parser.parse_args(argv)\n\n return ProxyConfig(\n host=args.host,\n port=args.port,\n openai_base_url=args.openai_base_url.rstrip(\"/\"),\n request_timeout_seconds=args.timeout,\n )\n\n\n def configure_logging() -> None:\n logging.basicConfig(\n level=logging.INFO,\n stream=sys.stdout,\n format=\"%(asctime)s %(levelname)s %(name)s - %(message)s\",\n )\n\n\n async def run_server(config: ProxyConfig) -> None:\n server = uvicorn.Server(\n uvicorn.Config(\n create_app(config),\n host=config.host,\n port=config.port,\n log_config=None,\n access_log=False,\n )\n )\n await server.serve()\n\n\n def main(argv: list[str] | None = None) -> None:\n configure_logging()\n config = load_config(argv)\n asyncio.run(run_server(config))\n\n\n if __name__ == \"__main__\":\n main()\n\n\nNote: this key-namer proxy is for _inside_ an organization’s network, on a high port number, as an edge exit. It is not something to expose on a public IP: it repeats calls without its own authentication method.\nNote2: it does not wholly _replace_ OpenAI’s obfuscated API key in returns, but that could be something you do to make the proxy work with existing admin code.\nNote3: I invested under 10 minutes, you should invest much more.",
"title": "Feature Request: include API key name (or group_by=api_key_name) in the Usage & Costs API responses"
}