Prepaid $10 for gpt-image-1, generated lots of images via API, but usage + costs still show $0

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.