Skip to content

Diagnostic LLM APIs

fast mode billed at twice the rate and served as default

Somebody turned on Fast mode for the checkout assistant eight months ago, in the console, in an afternoon nobody wrote down. The latency graph looked better for a week. It does not look better now, and the team has spent two sprints on the retrieval step trying to work out why. The requests still carry the premium tier, the responses still return 200, and the field that says which tier actually served them is not the field anyone is logging.

Read-only key Python and Node.js Tests included
Camera studio set up
Photo by Alexander Dummer on Unsplash
The short answer

Two GETs with an organization admin key. GET /v1/organization/projects?limit=100 tells you which projects are configured for the premium tier. GET /v1/organization/costs?start_time={now-30d}&bucket_width=1d&limit=30&group_by=line_item&group_by=project_id tells you which tier the invoice says served them.

Then compare the two, in both directions. A project set to Fast whose spend sits on standard line items is being downgraded: the ramp limits tripped, you are getting default latency, and the speedup the team is planning around is not there. A project set to Standard carrying Fast or Priority line items is the mirror image: some code path is sending the parameter, and that traffic bills at twice the rate.

The reason this survives is that the request field and the response field have the same name and different meanings. service_tier in the body is what you asked for. service_tier in the response is what you got. Nothing raises when they differ, and almost nobody logs the second one.

The problem in plain words

It fails in the shape that is hardest to notice: everything keeps working. The requests succeed, the answers are fine, and the only thing that changed is a latency distribution that drifted back to where it was before anyone paid to move it. Two sprints of profiling later, the retrieval step is faster and the p95 is not, because the premium was never the thing being delivered.

The other direction is worse in dollars and quieter still. A project defaulted to the premium tier bills every request at twice the standard rate with no code change anywhere in the tree, no diff to review, and no line in a changelog. On GPT-5.6 Sol that is $8 per million input tokens instead of $4, and $40 per million output instead of $20. It is a checkbox in a console, and it is the only evidence that exists.

Request sendsfastservice_tier inthe bodyRamp limittripsno error, noheaderServed asdefaultthe responsefield, not yoursLogs record therequestwhat you asked forDashboard showsfastfor traffic thatwas not
Nothing in this chain returns an error. The request field and the response field have the same name and say different things.

Why it happens

The requested tier and the served tier are separate fields. You send service_tier and the response returns service_tier, and the API is under no obligation to make them equal. It reports what served the request. Every logging setup that records the request body and not the response envelope is blind to the difference by construction.

Downgrades are a documented behaviour, not an error. Fast mode carries ramp rate limits, and when they trigger the request is served on the default tier instead of failing. That is the right behaviour — a 429 would be worse — but it means the fallback is silent, and a fallback nobody can see is a fallback nobody manages.

The premium is real in both directions. Fast mode is priced at twice the standard rate for GPT-5.6 Sol: $8/$40 per million tokens short-context against $4/$20, and $16/$60 against $8/$30 long-context. If you are served the premium you pay for it. If you are downgraded you do not pay it, and you also do not get it, which is the case the latency work is chasing.

A project-level default needs no code at all. The Project Service Tier setting applies to every request the project makes, whether or not the request body mentions a tier. That is why the audit has to read the project object: grepping your source for service_tier can return nothing while every request you send is billing at 2x.

The line item is a label, not an enum. The cost report's line_item is a human-readable string such as "gpt-5.6-sol, input". Premium traffic is identifiable in it, but by substring rather than by a documented field, so a script that reads it should print the strings it matched rather than asking you to trust the match.

The fix, as a flow

Neither half of this is readable on its own. The project object says which tier was asked for and the cost report says which one was served, and the finding is the disagreement between them. It runs in both directions, which is why the fix is a sort rather than a test: a premium you are not getting and a premium you never asked for are opposite problems with opposite repairs.

Project tieragainst premium line itemsFast set, standard billeddowngraded, the speedup is not thereStandard set, fast billeda code path paying 2x unaskedFast set and fast billed2x, and somebody should want itStandard on both sidesnothing to reconcile
A delivered premium is an answer, not a gap in the detection. Only a disagreement between the two sides is a finding.

How to fix it

Get an organization admin key, provisioned read-only

Both calls live under /v1/organization/*, which rejects project keys outright. Use an sk-admin- key with read scopes. This script only issues GETs.

Read the configured tier off the project objects

GET /v1/organization/projects?limit=100, paginating on after. The Project Service Tier setting is the one place a premium can be switched on for every request in a project without a single line of code changing, which makes it the first thing to read and the last thing anyone remembers.

Split each project's spend into premium and standard line items

GET /v1/organization/costs?bucket_width=1d&limit=30&group_by=line_item&group_by=project_id. Grouping is what populates line_item and project_id at all — ungrouped, both come back null. Sum amount.value per project into the two halves.

Compare the two and keep both mismatches apart

Fast configured with standard spend is a downgrade and costs you latency. Standard configured with premium spend is an unbudgeted 2x and costs you money. They are opposite findings with opposite repairs, so a script that prints one sentence for both is not much use.

Then log the served tier, not the requested one

The permanent fix is one line in your client: record the service_tier from the response envelope alongside the model and the token counts. Once that is in your telemetry the downgrade rate is a number you watch rather than a thing you audit for, and this script becomes a monthly check on the project settings instead.

How to check it worked

Re-run after the project setting has been changed or the parameter dropped. The mismatch should be gone; the tier and the invoice should say the same thing.

python3 openai_fast_mode_tier_audit.py --days 30
# standard          proj_batch (nightly enrichment)  tier is standard and no premium line items
# 4 project(s) checked, 0 with a tier the invoice disagrees with

The full code

Two GETs against the organization endpoints, no writes, and an admin key that should be provisioned read-only. Four pure functions: the tier reader, which is deliberately lenient because a setting that is absent from the object is not the same as a setting that is off; the spend splitter, which keeps the matched line-item strings so the report can show its working; the override parser, for the case where the project object does not carry the field at all; and the classifier, which has to hold two opposite findings apart rather than reporting “mismatch” and leaving you to work out which one you have.

Get this script on GitHub Follow @allanninal Python and Node.js, with tests. Read only, it never writes. One of 24 LLM API fixes, free and open source.
openai_fast_mode_tier_audit.py
"""Report OpenAI projects whose configured service tier and invoice disagree.

Read only. Two GET requests against the organization endpoints and nothing
else. Those endpoints reject project keys, so this needs an organization admin
key (sk-admin-), which can and should be provisioned read-only.

The finding is a mismatch rather than a total. A project set to the premium tier
whose spend lands on standard line items is being downgraded and is not getting
what it configured; a project set to standard carrying premium line items has a
code path sending the parameter. Both are printed with the repair, and neither
repair is performed here.
"""
import argparse
import logging
import os
import sys
import time

import requests

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("openai_fast_mode_tier_audit")

API = "https://api.openai.com/v1"

# Fast mode is priced at twice the standard rate. The multiplier is here to
# describe the finding, not to price your traffic: the dollars come from the
# cost report, which does not go stale the way a typed-in price table does.
PREMIUM_MULTIPLIER = 2.0

# line_item is a human-readable label, not a documented enum, so premium traffic
# is matched by substring and every matched string is printed for you to check.
PREMIUM_WORDS = ("fast", "priority")

# What the project object calls the setting the console calls Project Service
# Tier. Read leniently and in this order; absent is reported as absent.
TIER_FIELDS = ("service_tier", "default_service_tier")

FINDINGS = ("downgraded", "partly-downgraded", "unrequested-premium")


def tier_of(project):
    """Read a project's configured service tier. Pure.

    Returns a lowercase string, or None when the object carries no such field.
    None is not "standard": a missing field means this script cannot see the
    setting, and reporting that as a configured default would turn every
    unreadable project into a false clean.
    """
    candidates = []
    for field in TIER_FIELDS:
        candidates.append(project.get(field))
    settings = project.get("settings")
    if isinstance(settings, dict):
        for field in TIER_FIELDS:
            candidates.append(settings.get(field))
    for value in candidates:
        if isinstance(value, str) and value.strip():
            return value.strip().lower()
    return None


def split_spend(buckets, project_id):
    """Split one project's spend into premium and standard dollars. Pure.

    Returns (premium, standard, labels) where labels are the distinct line_item
    strings that matched as premium. The strings come back so the report can
    show what it matched on rather than asking you to trust a substring test.
    """
    premium = 0.0
    standard = 0.0
    labels = set()
    for bucket in buckets or []:
        for result in bucket.get("results") or []:
            if str(result.get("project_id") or "") != str(project_id):
                continue
            label = str(result.get("line_item") or "")
            try:
                value = float((result.get("amount") or {}).get("value") or 0.0)
            except (TypeError, ValueError):
                continue
            low = label.lower()
            if any(word in low for word in PREMIUM_WORDS):
                premium += value
                if value:
                    labels.add(label)
            else:
                standard += value
    return (round(premium, 2), round(standard, 2), sorted(labels))


def overrides(pairs):
    """Parse --tier project_id=tier arguments into a dict. Pure.

    For organizations whose project objects do not carry the setting at all: you
    read it once in the console and hand it to the script, rather than the
    script guessing.
    """
    out = {}
    for pair in pairs or []:
        if "=" not in str(pair):
            continue
        name, _, value = str(pair).partition("=")
        name, value = name.strip(), value.strip().lower()
        if name and value:
            out[name] = value
    return out


def verdict(tier, premium, standard, min_spend=1.0, delivered=0.60):
    """Classify one project. Pure. Returns (state, detail).

    The two findings are opposite and are never collapsed. "downgraded" costs
    latency you thought you had bought; "unrequested-premium" costs money nobody
    budgeted. A script that printed "tier mismatch" for both would leave the
    reader to work out which of those they were looking at.
    """
    premium = max(0.0, float(premium or 0.0))
    standard = max(0.0, float(standard or 0.0))
    total = premium + standard
    tier = (tier or "").strip().lower() or None

    if total < min_spend:
        return ("no-spend",
                "$%.2f of spend in the window, too little to say anything about "
                "which tier served it" % total)

    share = premium / total

    if tier in ("fast", "priority"):
        if premium <= 0:
            return ("downgraded",
                    "configured for the %s tier and not one dollar of $%.2f in "
                    "spend is on a premium line item. Every request in the "
                    "window was served on the default tier."
                    % (tier, total))
        if share < delivered:
            return ("partly-downgraded",
                    "configured for the %s tier, and only %.0f%% of $%.2f in "
                    "spend is on premium line items. The rest was downgraded "
                    "and served at default latency." % (tier, share * 100, total))
        return ("premium-delivered",
                "configured for the %s tier and %.0f%% of $%.2f is billed at it. "
                "The premium is being delivered and charged at about %.1fx the "
                "standard rate, so somebody should still want it."
                % (tier, share * 100, total, PREMIUM_MULTIPLIER))

    if tier is None:
        if premium > 0:
            return ("unknown-tier-premium",
                    "the project object carries no readable service tier and "
                    "$%.2f of $%.2f is on premium line items. Read the setting "
                    "in the console and pass it with --tier."
                    % (premium, total))
        return ("unknown-tier",
                "the project object carries no readable service tier. No "
                "premium line items in $%.2f of spend, so nothing is being "
                "billed at the premium rate today." % total)

    if premium > 0:
        return ("unrequested-premium",
                "the project tier is %s and %.0f%% of $%.2f is on premium line "
                "items, so a code path is sending the tier in the request body. "
                "That traffic bills at about %.1fx the standard rate."
                % (tier, share * 100, total, PREMIUM_MULTIPLIER))
    return ("standard",
            "tier is %s and no premium line items in $%.2f of spend" % (tier, total))


def get(session, path, params):
    r = session.get(API + path, params=params, timeout=90)
    if r.status_code in (401, 403):
        raise SystemExit("%d from OpenAI: /v1/organization/* needs an "
                         "organization admin key (sk-admin-), not a project key"
                         % r.status_code)
    r.raise_for_status()
    return r.json()


def projects(session, page_size, max_pages):
    """Walk GET /v1/organization/projects, which paginates on the last id."""
    params = {"limit": page_size}
    for _ in range(max_pages):
        page = get(session, "/organization/projects", params)
        data = page.get("data") or []
        for project in data:
            yield project
        if not page.get("has_more") or not data:
            return
        params = {"limit": page_size, "after": data[-1].get("id")}


def cost_pages(session, params, max_pages=40):
    """Walk the cost report, which paginates on an opaque page cursor."""
    params = dict(params)
    for _ in range(max_pages):
        page = get(session, "/organization/costs", params)
        for bucket in page.get("data") or []:
            yield bucket
        if not page.get("has_more") or not page.get("next_page"):
            return
        params = dict(params)
        params["page"] = page["next_page"]


def main():
    ap = argparse.ArgumentParser(description=__doc__)
    ap.add_argument("--days", type=int, default=30,
                    help="days of daily cost buckets to read (default 30)")
    ap.add_argument("--min-spend", type=float, default=1.0,
                    help="ignore projects below this many dollars (default 1.0)")
    ap.add_argument("--delivered", type=float, default=0.60,
                    help="premium share above which the tier counts as "
                         "delivered (default 0.60)")
    ap.add_argument("--tier", action="append", default=[], metavar="ID=TIER",
                    help="supply a project's configured tier when the object "
                         "does not carry it, e.g. --tier proj_abc=fast")
    ap.add_argument("--show-all", action="store_true",
                    help="also print projects whose tier and invoice agree")
    args = ap.parse_args()

    key = os.environ.get("OPENAI_ADMIN_KEY") or os.environ.get("OPENAI_API_KEY")
    if not key:
        log.error("set OPENAI_ADMIN_KEY (an organization admin key, read-only "
                  "scopes are enough)")
        return 2

    session = requests.Session()
    session.headers.update({"Authorization": "Bearer " + key})

    told = overrides(args.tier)
    costs = list(cost_pages(session, {
        "start_time": int(time.time()) - args.days * 86400,
        "bucket_width": "1d",
        "limit": min(180, max(1, args.days)),
        "group_by": ["line_item", "project_id"],
    }))

    checked = 0
    found = 0
    for project in projects(session, 100, 20):
        project_id = str(project.get("id") or "")
        name = str(project.get("name") or project_id)
        tier = told.get(project_id) or tier_of(project)
        premium, standard, labels = split_spend(costs, project_id)
        state, detail = verdict(tier, premium, standard, args.min_spend,
                                args.delivered)
        checked += 1
        line = "%-21s %s (%s)  %s" % (state, project_id, name, detail)

        if state in FINDINGS:
            found += 1
            log.warning(line)
            if labels:
                log.warning("  matched premium line item(s): %s",
                            ", ".join(labels))
            if state == "unrequested-premium":
                log.warning("  repair: find the call site sending the tier in "
                            "the request body and drop it, or budget for it "
                            "deliberately. Nothing in the project settings asked "
                            "for this.")
            else:
                log.warning("  repair: either stop paying for a tier you are not "
                            "being served (set Project Service Tier back to "
                            "standard) or ask OpenAI to raise the ramp limits "
                            "that are downgrading you. Decide which, then log "
                            "the response envelope's service_tier so the "
                            "downgrade rate is a metric instead of an audit.")
        elif state in ("unknown-tier", "unknown-tier-premium"):
            log.warning(line)
        elif args.show_all:
            log.info(line)

    log.info("%d project(s) checked, %d with a tier the invoice disagrees with",
             checked, found)
    return 1 if found else 0


if __name__ == "__main__":
    sys.exit(main())
openai-fast-mode-tier-audit.mjs
/**
 * Report OpenAI projects whose configured service tier and invoice disagree.
 *
 * Read only. Two GET requests against the organization endpoints and nothing
 * else. Those endpoints reject project keys, so this needs an organization
 * admin key (sk-admin-), which can and should be provisioned read-only.
 */
const API = 'https://api.openai.com/v1';

// Fast mode is priced at twice the standard rate. The multiplier describes the
// finding; the dollars come from the cost report rather than a price table.
const PREMIUM_MULTIPLIER = 2.0;

// line_item is a human-readable label, not a documented enum.
const PREMIUM_WORDS = ['fast', 'priority'];

// What the project object calls the setting the console calls Project Service
// Tier. Read leniently and in this order; absent is reported as absent.
const TIER_FIELDS = ['service_tier', 'default_service_tier'];

const FINDINGS = ['downgraded', 'partly-downgraded', 'unrequested-premium'];

/**
 * Read a project's configured service tier. Pure. Returns a lowercase string,
 * or null when the object carries no such field. null is not "standard": a
 * missing field means the setting is unreadable here, and treating that as a
 * configured default would turn every unreadable project into a false clean.
 */
export function tierOf(project) {
  const candidates = TIER_FIELDS.map((f) => project[f]);
  const settings = project.settings;
  if (settings !== null && typeof settings === 'object' && !Array.isArray(settings)) {
    for (const f of TIER_FIELDS) candidates.push(settings[f]);
  }
  for (const value of candidates) {
    if (typeof value === 'string' && value.trim()) return value.trim().toLowerCase();
  }
  return null;
}

/**
 * Split one project's spend into premium and standard dollars. Pure. Returns
 * [premium, standard, labels]; the labels are the distinct line_item strings
 * that matched, so the report can show what the substring test caught.
 */
export function splitSpend(buckets, projectId) {
  let premium = 0;
  let standard = 0;
  const labels = new Set();
  for (const bucket of buckets ?? []) {
    for (const result of bucket.results ?? []) {
      if (String(result.project_id ?? '') !== String(projectId)) continue;
      const label = String(result.line_item ?? '');
      const value = Number(result.amount?.value ?? 0);
      if (!Number.isFinite(value)) continue;
      const low = label.toLowerCase();
      if (PREMIUM_WORDS.some((w) => low.includes(w))) {
        premium += value;
        if (value) labels.add(label);
      } else {
        standard += value;
      }
    }
  }
  return [Math.round(premium * 100) / 100, Math.round(standard * 100) / 100,
          [...labels].sort()];
}

/**
 * Parse --tier project_id=tier arguments into a Map. Pure. For organizations
 * whose project objects do not carry the setting: you read it once in the
 * console and hand it over, rather than the script guessing.
 */
export function overrides(pairs) {
  const out = new Map();
  for (const pair of pairs ?? []) {
    const text = String(pair);
    const at = text.indexOf('=');
    if (at < 0) continue;
    const name = text.slice(0, at).trim();
    const value = text.slice(at + 1).trim().toLowerCase();
    if (name && value) out.set(name, value);
  }
  return out;
}

/**
 * Classify one project. Pure. Returns [state, detail]. The two findings are
 * opposite and are never collapsed: one costs latency you thought you bought,
 * the other costs money nobody budgeted.
 */
export function verdict(tier, premium, standard, minSpend = 1.0, delivered = 0.60) {
  const prem = Math.max(0, Number(premium) || 0);
  const std = Math.max(0, Number(standard) || 0);
  const total = prem + std;
  const configured = (tier ?? '').trim().toLowerCase() || null;

  if (total < minSpend) {
    return ['no-spend',
      `$${total.toFixed(2)} of spend in the window, too little to say anything ` +
      'about which tier served it'];
  }

  const share = prem / total;
  const pct = Math.round(share * 100);

  if (configured === 'fast' || configured === 'priority') {
    if (prem <= 0) {
      return ['downgraded',
        `configured for the ${configured} tier and not one dollar of ` +
        `$${total.toFixed(2)} in spend is on a premium line item. Every ` +
        'request in the window was served on the default tier.'];
    }
    if (share < delivered) {
      return ['partly-downgraded',
        `configured for the ${configured} tier, and only ${pct}% of ` +
        `$${total.toFixed(2)} in spend is on premium line items. The rest was ` +
        'downgraded and served at default latency.'];
    }
    return ['premium-delivered',
      `configured for the ${configured} tier and ${pct}% of $${total.toFixed(2)} ` +
      `is billed at it. The premium is being delivered and charged at about ` +
      `${PREMIUM_MULTIPLIER.toFixed(1)}x the standard rate, so somebody should ` +
      'still want it.'];
  }

  if (configured === null) {
    if (prem > 0) {
      return ['unknown-tier-premium',
        `the project object carries no readable service tier and ` +
        `$${prem.toFixed(2)} of $${total.toFixed(2)} is on premium line items. ` +
        'Read the setting in the console and pass it with --tier.'];
    }
    return ['unknown-tier',
      'the project object carries no readable service tier. No premium line ' +
      `items in $${total.toFixed(2)} of spend, so nothing is being billed at ` +
      'the premium rate today.'];
  }

  if (prem > 0) {
    return ['unrequested-premium',
      `the project tier is ${configured} and ${pct}% of $${total.toFixed(2)} is ` +
      'on premium line items, so a code path is sending the tier in the request ' +
      `body. That traffic bills at about ${PREMIUM_MULTIPLIER.toFixed(1)}x the ` +
      'standard rate.'];
  }
  return ['standard',
    `tier is ${configured} and no premium line items in $${total.toFixed(2)} of spend`];
}

async function get(key, path, params) {
  const url = new URL(API + path);
  for (const [k, v] of Object.entries(params)) {
    if (Array.isArray(v)) v.forEach((one) => url.searchParams.append(k, String(one)));
    else if (v !== undefined && v !== null) url.searchParams.set(k, String(v));
  }
  const res = await fetch(url, { headers: { Authorization: `Bearer ${key}` } });
  if (res.status === 401 || res.status === 403) {
    throw new Error(`${res.status} from OpenAI: /v1/organization/* needs an ` +
                    'organization admin key (sk-admin-), not a project key');
  }
  if (!res.ok) throw new Error(`${res.status} from ${path}`);
  return res.json();
}

async function* walkProjects(key, pageSize, maxPages) {
  let params = { limit: pageSize };
  for (let i = 0; i < maxPages; i += 1) {
    const page = await get(key, '/organization/projects', params);
    const data = page.data ?? [];
    for (const project of data) yield project;
    if (!page.has_more || data.length === 0) return;
    params = { limit: pageSize, after: data[data.length - 1].id };
  }
}

async function costPages(key, params, maxPages = 40) {
  const out = [];
  let query = { ...params };
  for (let i = 0; i < maxPages; i += 1) {
    const page = await get(key, '/organization/costs', query);
    out.push(...(page.data ?? []));
    if (!page.has_more || !page.next_page) break;
    query = { ...params, page: page.next_page };
  }
  return out;
}

async function main() {
  const key = process.env.OPENAI_ADMIN_KEY ?? process.env.OPENAI_API_KEY;
  if (!key) {
    console.error('set OPENAI_ADMIN_KEY (an organization admin key, read-only ' +
                  'scopes are enough)');
    process.exitCode = 2;
    return;
  }

  const days = Number(process.env.DAYS ?? 30);
  const minSpend = Number(process.env.MIN_SPEND ?? 1.0);
  const delivered = Number(process.env.DELIVERED ?? 0.60);
  const showAll = process.argv.includes('--show-all');
  const told = overrides(process.argv
    .filter((a) => a.startsWith('--tier='))
    .map((a) => a.slice('--tier='.length)));

  const costs = await costPages(key, {
    start_time: Math.floor(Date.now() / 1000) - days * 86400,
    bucket_width: '1d',
    limit: Math.min(180, Math.max(1, days)),
    group_by: ['line_item', 'project_id'],
  });

  let checked = 0;
  let found = 0;
  for await (const project of walkProjects(key, 100, 20)) {
    const projectId = String(project.id ?? '');
    const name = String(project.name ?? projectId);
    const tier = told.get(projectId) ?? tierOf(project);
    const [premium, standard, labels] = splitSpend(costs, projectId);
    const [state, detail] = verdict(tier, premium, standard, minSpend, delivered);
    checked += 1;
    const line = `${state.padEnd(21)} ${projectId} (${name})  ${detail}`;

    if (FINDINGS.includes(state)) {
      found += 1;
      console.warn(line);
      if (labels.length) {
        console.warn(`  matched premium line item(s): ${labels.join(', ')}`);
      }
      if (state === 'unrequested-premium') {
        console.warn('  repair: find the call site sending the tier in the ' +
          'request body and drop it, or budget for it deliberately. Nothing in ' +
          'the project settings asked for this.');
      } else {
        console.warn('  repair: either stop paying for a tier you are not being ' +
          'served (set Project Service Tier back to standard) or ask OpenAI to ' +
          'raise the ramp limits that are downgrading you. Decide which, then ' +
          'log the response envelope\'s service_tier so the downgrade rate is a ' +
          'metric instead of an audit.');
      }
    } else if (state === 'unknown-tier' || state === 'unknown-tier-premium') {
      console.warn(line);
    } else if (showAll) {
      console.log(line);
    }
  }

  console.log(`${checked} project(s) checked, ${found} with a tier the invoice ` +
              'disagrees with');
  process.exitCode = found ? 1 : 0;
}

// Only run when invoked directly, so importing this module from the test file
// does not fire main() and fail on the missing key.
if (import.meta.url === `file://${process.argv[1]}`) {
  main().catch((err) => { console.error(err.message); process.exitCode = 2; });
}

Add a test

The two tests that carry the note are the two directions of the mismatch: premium configured with standard spend, and standard configured with premium spend. They must not produce the same state, because they do not have the same repair. The rest pin down the things a lenient reader gets wrong — a project object with no tier field is unreadable rather than standard, and a project with almost no spend is not evidence of anything either way.

test_openai_fast_mode_tier_audit.py
from openai_fast_mode_tier_audit import overrides, split_spend, tier_of, verdict


def cost(project="proj_a", line_item="gpt-5.6-sol, input", value=0.0):
    return {"project_id": project, "line_item": line_item,
            "amount": {"value": value, "currency": "usd"}}


def buckets(*results):
    return [{"start_time": 0, "end_time": 86400, "results": list(results)}]


def test_configured_fast_with_standard_spend_is_a_downgrade():
    # The whole note. Nothing errored, the tier was requested, and the invoice
    # says every request in the window was served on the default tier.
    state, detail = verdict("fast", premium=0.0, standard=420.0)
    assert state == "downgraded"
    assert "not one dollar" in detail
    assert "default tier" in detail


def test_configured_standard_with_premium_spend_is_the_opposite_finding():
    state, detail = verdict("standard", premium=300.0, standard=100.0)
    assert state == "unrequested-premium"
    assert "a code path is sending the tier" in detail
    assert "2.0x" in detail


def test_a_delivered_premium_is_not_reported_as_a_failure():
    state, detail = verdict("fast", premium=380.0, standard=20.0)
    assert state == "premium-delivered"
    assert "95%" in detail


def test_a_partial_downgrade_is_its_own_state():
    state, detail = verdict("fast", premium=100.0, standard=300.0)
    assert state == "partly-downgraded"
    assert "only 25%" in detail


def test_a_missing_tier_field_is_never_read_as_standard():
    assert tier_of({"id": "proj_a", "name": "web"}) is None
    assert tier_of({"id": "proj_a", "service_tier": "  Fast "}) == "fast"
    assert tier_of({"id": "proj_a", "settings": {"service_tier": "priority"}}) == "priority"
    assert tier_of({"id": "proj_a", "settings": "fast"}) is None
    assert verdict(None, premium=0.0, standard=99.0)[0] == "unknown-tier"
    assert verdict(None, premium=50.0, standard=49.0)[0] == "unknown-tier-premium"


def test_a_project_with_no_spend_is_not_evidence_of_anything():
    assert verdict("fast", premium=0.0, standard=0.0)[0] == "no-spend"
    assert verdict("standard", premium=0.2, standard=0.1)[0] == "no-spend"


def test_premium_line_items_are_matched_by_label_and_the_labels_come_back():
    rows = buckets(
        cost(line_item="gpt-5.6-sol, input", value=100.0),
        cost(line_item="gpt-5.6-sol, input (fast)", value=40.0),
        cost(line_item="gpt-5.6-sol, priority output", value=10.0),
        cost(project="proj_b", line_item="gpt-5.6-sol, input (fast)", value=999.0),
    )
    premium, standard, labels = split_spend(rows, "proj_a")
    assert premium == 50.0
    assert standard == 100.0
    assert labels == ["gpt-5.6-sol, input (fast)", "gpt-5.6-sol, priority output"]


def test_tier_overrides_are_parsed_and_junk_is_dropped():
    assert overrides(["proj_a=Fast", "proj_b = standard "]) == {
        "proj_a": "fast", "proj_b": "standard"}
    assert overrides(["nonsense", "=fast", "proj_c="]) == {}
    assert overrides(None) == {}
openai-fast-mode-tier-audit.test.mjs
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { overrides, splitSpend, tierOf, verdict }
  from './openai-fast-mode-tier-audit.mjs';

function cost({ project = 'proj_a', lineItem = 'gpt-5.6-sol, input',
                value = 0 } = {}) {
  return { project_id: project, line_item: lineItem,
           amount: { value, currency: 'usd' } };
}

function buckets(...results) {
  return [{ start_time: 0, end_time: 86400, results }];
}

test('configured fast with standard spend is a downgrade', () => {
  const [state, detail] = verdict('fast', 0, 420);
  assert.equal(state, 'downgraded');
  assert.match(detail, /not one dollar/);
  assert.match(detail, /default tier/);
});

test('configured standard with premium spend is the opposite finding', () => {
  const [state, detail] = verdict('standard', 300, 100);
  assert.equal(state, 'unrequested-premium');
  assert.match(detail, /a code path is sending the tier/);
  assert.match(detail, /2\.0x/);
});

test('a delivered premium is not reported as a failure', () => {
  const [state, detail] = verdict('fast', 380, 20);
  assert.equal(state, 'premium-delivered');
  assert.match(detail, /95%/);
});

test('a partial downgrade is its own state', () => {
  const [state, detail] = verdict('fast', 100, 300);
  assert.equal(state, 'partly-downgraded');
  assert.match(detail, /only 25%/);
});

test('a missing tier field is never read as standard', () => {
  assert.equal(tierOf({ id: 'proj_a', name: 'web' }), null);
  assert.equal(tierOf({ id: 'proj_a', service_tier: '  Fast ' }), 'fast');
  assert.equal(tierOf({ id: 'proj_a', settings: { service_tier: 'priority' } }),
               'priority');
  assert.equal(tierOf({ id: 'proj_a', settings: 'fast' }), null);
  assert.equal(verdict(null, 0, 99)[0], 'unknown-tier');
  assert.equal(verdict(null, 50, 49)[0], 'unknown-tier-premium');
});

test('a project with no spend is not evidence of anything', () => {
  assert.equal(verdict('fast', 0, 0)[0], 'no-spend');
  assert.equal(verdict('standard', 0.2, 0.1)[0], 'no-spend');
});

test('premium line items are matched by label and the labels come back', () => {
  const rows = buckets(
    cost({ lineItem: 'gpt-5.6-sol, input', value: 100 }),
    cost({ lineItem: 'gpt-5.6-sol, input (fast)', value: 40 }),
    cost({ lineItem: 'gpt-5.6-sol, priority output', value: 10 }),
    cost({ project: 'proj_b', lineItem: 'gpt-5.6-sol, input (fast)', value: 999 }),
  );
  const [premium, standard, labels] = splitSpend(rows, 'proj_a');
  assert.equal(premium, 50);
  assert.equal(standard, 100);
  assert.deepEqual(labels,
    ['gpt-5.6-sol, input (fast)', 'gpt-5.6-sol, priority output']);
});

test('tier overrides are parsed and junk is dropped', () => {
  assert.deepEqual([...overrides(['proj_a=Fast', 'proj_b = standard '])],
                   [['proj_a', 'fast'], ['proj_b', 'standard']]);
  assert.deepEqual([...overrides(['nonsense', '=fast', 'proj_c='])], []);
  assert.deepEqual([...overrides(null)], []);
});

FAQ

What is the difference between the service_tier I send and the one I get back?

The one you send is a request. The one in the response envelope is a statement of fact about how that request was served. Fast mode carries ramp rate limits, and when they trigger the request is served on the default tier rather than rejected, so the two fields differ with no error, no header and no warning. Log the response field.

How much does the premium tier actually cost?

Twice the standard rate on GPT-5.6 Sol: $8 per million input tokens and $40 per million output short-context, against $4 and $20 standard; $16 and $60 long-context against $8 and $30. Anthropic's fast mode is the same shape, running Claude Opus 5 at $10/$50 per MTok against the standard $5/$25, and it is a per-request parameter there rather than a project setting.

Can a read-only script see the served tier directly?

Not without making a real inference call, which spends money and is why this script does not. Neither provider exposes a request log, so there is no GET that returns the served tier for calls you already made. The invoice is the closest read-only proxy there is: what you were billed for is what you were served.

Why does the script report a project whose tier field it cannot read?

Because unreadable is not the same as standard, and folding the two together would turn every project the script cannot see into a clean one. If your project objects do not carry the setting, read it once in the console and pass it in with --tier, or make the response envelope's service_tier part of your telemetry and stop asking the project object at all.

Is a delivered premium a finding?

No, and the script says so rather than staying silent. Paying 2x for latency you need is a decision, not a bug. It is worth re-confirming annually, because the workload that needed sub-second responses in year one is often a batch job by year three, and nobody goes back to the checkbox.

Related field notes

Sources

Every figure in this note is traced to one of these. Prices are list rates and change — check them for your own region before acting.

Stuck on a tricky one?

If your setup is misbehaving in a way this note does not cover, message me on LinkedIn with what you are seeing.