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

Hi, I’m confused about how my gpt-image-1 API usage is being billed and tracked.

  • I manually added $10 credit on the Billing → Overview page (Pay-as-you-go, credit balance shows $10.00).

  • In Usage → API my spend shows $0.00, with 0 tokens / 0 requests for Images.

  • I also set a monthly usage limit of $10 in the Limits section (it shows as a $15/$10 bar because of the default cap, but I changed the soft limit to $10).

  • I am not using the ChatGPT web UI for images. All my images are generated via the Python API in my IDE, using client.images.generate(model="gpt-image-1", size="1024x1536", quality="high").

  • I’ve already generated well over 40–50 images this way, so at the documented pricing (~$0.25 per image given my token counts) I’d expect around $10+ of usage by now and possibly to hit my limit.

To verify, I:

  1. Created an admin API key and called the Costs / Usage API (GET /v1/organization/costs) using a small Python script.

  2. The script reports:

    • Today’s spend: $0.0000

    • Month-to-date spend: $0.0000

So for the organization that the admin key belongs to, OpenAI’s official billing API says I’ve spent exactly $0, even though I’m clearly getting real images from gpt-image-1 via the API.

My questions:

  1. Where are my image-generation charges actually going?

    • If they’re being deducted from some other org or account, how can I confirm that?
  2. Is it possible that my project key (used in the image script) and my admin key (used for the costs script) belong to different organizations?

    • If so, what’s the recommended way to verify that and align them?
  3. Will I ever be charged more than the $10 credit without seeing it reflected in the Usage/Casts pages first?

    • I want to be sure I won’t suddenly get billed beyond what I can see in the dashboard and the Costs API.

Any guidance from OpenAI staff or anyone who has seen this multi-org / zero-usage-but-images-working situation would be really appreciated.

1 Like

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.

Thank you so much for such an articulate answer !! Really appreciated !
However , your code also returned the following :
PS C:\Users\User\Downloads\images> python .\forum.py
INFO:httpx:HTTP Request: GET https:// api. openai. com/v1/organization/costs?start_time=1761955200&end_time=1763294882&limit=15&bucket_width=1d&group_by=line_item “HTTP/1.1 200 OK”

UTC Date Line Item Amount (USD)

Although this script was run with my admin key which i had to create newly . (it was telling me I had insufficient privileges)
For my image gen , I was using a project api key [sk-proj…] .
Please notice that in my usage dashboard , there are absolutely no bars / graphs indicating an API hit . But the limit on the right already suggested I have used up 22$ worth credit …

Even though I have generated over 50+ images at this point , my initial top up credit seems to be unaffected . Settings →Billing, still shows 10$ Credit Balance (Pay as you go), with my auto recharge option off , so that I don’t accidentally get charged more .

I am quite confused at this point , and I humbly apologize for being really ill knowledged about these stuff, as its my first time wandering into API generation territory and my head is full of idiotic questions, so I might be misinterpreting a lot of things ! TIA

That API will return empty “daily buckets” with no usage, not nothing.

If the usage page is also hanging, forever with a grey display, then I would have to say:

“Your organization is broken”.

Something wrong with the success of calls to the billing backend, for even periods of no usage at all will display empty bars in a bargraph there, and a total spend figure will be delivered.

Under “people”, you can make sure you are “owner” (aka, someone able to get admin keys, so this should be true).

Then – there are these new permissions that I haven’t encountered before, now with “Roles”:

Both Owner and Reader have been there forever, and they have permissions as “roles” now that can’t be edited. But you can now create a new role and have more granularity to the person’s rights. This is for inviting other users by email, and you would NEVER want to lose your account “owner” status of a primary organization with your billing by messing with this. This “roles” feature might not be generally-deployed, but could affect things.

So, outside of suggesting messing where you shouldn’t need to mess, like inviting another email, granting them a custom role with everything that can be given for looking at usage…

This is a problem that is squarely in OpenAI’s court and is a bug that must be flagged.

If it is permissions, you should be getting “no permission” anywhere in OpenAI’s platform where you don’t have rights - or OpenAI should have coded it correctly. You indicated you saw similar before, which is a clue.


As an alternate way to get at usage, you can look at your credit grants (payments) and see the remaining balance of each: https://platform.openai.com/settings/organization/billing/credit-grants (or maybe you can’t there either).

Forum interactions with other users like me can’t fix your account, and this will take a pretty high level of support to ensure it isn’t affecting other new accounts. For you, that is support@openai.com, emailed from your login method and with your org ID so they know you, and that this is a significant platform issue or database state corruption, “escalate to staff” right off the bat to any AI that answers emails.

Your account creation date or first payment date would be also good to know, even for my curiosity if you want to share (I don’t work for OpenAI), as I’ll see if I can get this issue flagged and noticed more broadly with you as an example to find in the inbox for details.

Thanks for hanging in there .. with another API issue!

Oh wow , thank you very much thn !
I dont exactly recall my exact account creation date, and as per as i understand through gpt there isnt any way to find that out exactly ig… although it should be somewhere around 2023. But my first GPT Pro sub was I think on Feb 26 this year , judging from my first invoice mail . And as for the API credit / pre-paid load, it was this Nov 13 , as this was my first ever topup. Sorry I dont suppose these are of much help, if you could kindly tell me what other things you need or how to acquire those , I am happy to provide. Actually , all I am really concerned about right now , is getting handed a big af bill for my usage :face_without_mouth: , even tho i tried making sure to keep within limits

Tips

1/5 the price: gpt-image-1-mini

Then, use the OpenAI “generate” and “edits” endpoint, so you aren’t paying to “chat” with an AI using a tool, but instead, directly send to the image creation AI model.