You will first want to go into the usage page: https://platform.openai.com/settings/organization/usage
Then near top, select “Group by” → “Line Item”.
That should show a bar graph display for the date range with the billed parts by model usage when you hover over a bar, and a legend at the bottom with the total for the period selected.
For your API call, maybe it is the “costs” endpoint not behaving like you expect. It is limited to 7 days by default, requiring pagination, not obeying a range. Then, that while giving buckets of days, the offset that you specify as start can be arbitrary start of 24 hours, and if you want to separate your costs by type, an array with line_item.
I made your first call there simple with this Python:
'''OpenAI admin framework – costs endpoint example
Environment:
export OPENAI_ADMIN_KEY=... # required “sk‑admin‑…” key
Dependencies:
pip install httpx
'''
import json
import logging
import os
from datetime import UTC, datetime, timedelta
from typing import Any, Dict, Tuple
import httpx
logger = logging.getLogger(__name__)
def admin_get(
path: str,
*,
params: Dict[str, Any] | None = None,
timeout: float = 120.0,
) -> Tuple[httpx.Response, Dict[str, str]]:
"""GET wrapper for query-only OpenAI endpoints (no body/JSON)"""
api_key = os.getenv("OPENAI_ADMIN_KEY")
if not api_key:
raise ValueError("Set OPENAI_ADMIN_KEY first (must be an *admin* key).")
try:
resp = httpx.get(
f"https://api.openai.com/v1/{path}",
headers={"Authorization": f"Bearer {api_key}"},
params=params, timeout=timeout,
)
hdrs = dict(resp.headers)
resp.raise_for_status()
return resp, hdrs
except httpx.HTTPStatusError as err:
body_text = err.response.text.strip() or "<no body>"
print(f"[Admin API] HTTP {err.response.status_code}: {body_text}", flush=True)
logger.error("HTTP %s: %s", err.response.status_code, body_text)
raise
except httpx.RequestError as err:
logger.error("Request error: %s", err)
raise
def get_recent_costs(
past_days: int = 14,
*,
debug: bool = False,
) -> Dict[str, Any]:
"""
Retrieve organisation‑level cost daily buckets up to today.
Parameters
----------
past_days
Number of 24‑hour buckets (1 – 180).
debug
• True - send **only** the required `start_time` and max `limit`
"""
past_days = max(1, min(past_days+1, 180))
now = datetime.now(tz=UTC)
start_dt = (now - timedelta(days=past_days)).replace(
hour=0, minute=0, second=0, microsecond=0
)
params: Dict[str, Any] = {"start_time": int(start_dt.timestamp())}
if not debug:
params.update(
{
"end_time": int(now.timestamp()), # also UNIX epoch time
"limit": past_days, # items before cursor pagination
"bucket_width": "1d", # only "1d"
"group_by": ["line_item"], # ["line_item" and/or "project_id"]
}
)
else:
params.update(
{
"limit": 180,
}
)
resp, _ = admin_get("organization/costs", params=params)
costs: Dict[str, Any] = resp.json()
for bucket in costs["data"]:
start_dt = datetime.fromtimestamp(bucket["start_time"], tz=UTC)
bucket["start_datetime"] = start_dt.isoformat().replace("+00:00", "Z")
return costs
def printable_costs(costs: Dict[str, Any]) -> str:
"""
Format organisation cost buckets into a Markdown-compatible table.
Parameters
----------
costs
The dict returned by ``get_recent_costs()``.
Returns
-------
str
A Markdown table showing daily costs per line item.
"""
headers = ("UTC Date", "Line Item", "Amount (USD)")
# Track rows as tuples of (date, line_item, amount_str)
rows: list[tuple[str, str, str]] = []
# Initialise column widths from header labels
widths = [len(h) for h in headers]
for bucket in costs.get("data", []):
results = bucket.get("results") or []
if not results:
continue
# Full UTC datetime string (already added in get_recent_costs)
date_str = bucket.get("start_datetime", "")
first_for_day = True
for result in results:
line_item = result.get("line_item", "")
amount_value = result.get("amount", {}).get("value", 0.0)
# Format amount to 4 decimal places
amount_str = f"{float(amount_value):.4f}"
# Only show the date once per day block
date_cell = date_str if first_for_day else ""
first_for_day = False
row = (date_cell, line_item, amount_str)
rows.append(row)
# Update column widths based on this row
widths[0] = max(widths[0], len(row[0]))
widths[1] = max(widths[1], len(row[1]))
widths[2] = max(widths[2], len(row[2]))
# Build Markdown table lines
lines: list[str] = []
# Header row
header_row = "| " + " | ".join(
headers[i].ljust(widths[i]) for i in range(len(headers))
) + " |"
lines.append(header_row)
# Separator row, width-aligned with header/body
separator_row = "| " + " | ".join(
"-" * widths[i] for i in range(len(headers))
) + " |"
lines.append(separator_row)
# Data rows
for date_cell, line_item, amount_str in rows:
line = "| " + " | ".join(
[
date_cell.ljust(widths[0]),
line_item.ljust(widths[1]),
amount_str.rjust(widths[2]),
]
) + " |"
lines.append(line)
return "\n".join(lines)
# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------
def main() -> None:
costs = get_recent_costs(past_days=14)
#print(json.dumps(costs, indent=2))
print(printable_costs(costs))
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
main()
httpx will already be there if you have the openai module.
It requests recent API usage by a days count, see the main() at the bottom for the past_days=14 parameter being passed.
You can uncomment the JSON printing at the bottom, and you’ll see I also added a date you can understand to the return:
{
"object": "bucket",
"start_time": 1763164800,
"end_time": 1763251200,
"results": [],
"start_datetime": "2025-11-15T00:00:00Z"
}
but this is the format you will see when running, employing a function to make a table of results:
| UTC Date | Line Item | Amount (USD) |
| -------------------- | ------------------------------------------- | ------------ |
| 2025-11-04T00:00:00Z | gpt-image-1-mini text, input | 0.0004 |
| | gpt-image-1-mini image, input | 0.0008 |
| | gpt-image-1-mini image, output | 0.0333 |
| 2025-11-05T00:00:00Z | code interpreter | 0.0300 |
...
Or even just paste your console output to the forum, the printout being markdown format:
| UTC Date |
Line Item |
Amount (USD) |
| 2025-11-04T00:00:00Z |
gpt-image-1-mini text, input |
0.0004 |
|
gpt-image-1-mini image, input |
0.0008 |
|
gpt-image-1-mini image, output |
0.0333 |
This code does not take an environment variable OPENAI_PROJECT, nor group by project, so nothing should be missed, unless the API is bugged.