Skip to content

Guide

How to set a hard spending limit on the OpenAI API (per project, key or user)

OpenAI's only hard spending limit is monthly, per organization or per project: open Project settings → Limits → Edit spend limit, turn on Enforce a hard limit, and requests over it fail with 429 project_spend_limit_exceeded. For a cap per API key, per customer or per day there is no OpenAI setting, so you need a check in front of the API that reserves each call's worst-case cost before sending it.

By INFRO Editorial

Last updated

Last checked

On this page
  1. Set OpenAI's hard limit on a project
  2. What OpenAI's limit cannot express
  3. A cap per customer and per day, checked before the call
  4. When a limit is reached
  5. The same caps through INFRO
  6. Questions
  7. Sources

Set OpenAI's hard limit on a project

  1. In the OpenAI platform, open the project and go to Project settings → Limits.
  2. Under Spend, choose Edit spend limit and enter a monthly amount.
  3. Turn on Enforce a hard limit and save.

From then on, a request billed to that project over the limit fails with 429 and code project_spend_limit_exceeded. The organization-wide limit works the same way across every project and fails with organization_spend_limit_exceeded. Both reset with the next monthly cycle.

Read three details before relying on it:

  • It is not exact. OpenAI says enforcement is not instantaneous, so recorded spend can run slightly past the limit. Set it below the number you cannot exceed.
  • Alerts do not stop anything. Spend alerts notify you; only the enforced hard limit refuses requests.
  • Your SDK will retry it. The official SDKs retry every 429 twice by default, this one included. Handle project_spend_limit_exceeded yourself, as in the 429 guide.

What OpenAI's limit cannot express

You needOpenAI's built-in limitHow to get it
A cap for the whole companyYes, organization hard limit, monthlyBuilt in
A cap per team, service or environmentYes, if each has its own projectOne project per service; give each its own keys
A cap per API keyNo; limits are per projectOne project per key, or a check in your code
A cap per end customerNoA check in front of the API (below)
A daily or weekly capNo; monthly onlyA check in front of the API (below)
One cap across OpenAI and AnthropicNo; each provider caps only itselfA check in front of both
No overshoot under concurrencyNo; enforcement lagsReserve the worst case before each call
OpenAI's limits per its spend-limits guide. Anthropic's own spend limits behave differently again: a limit you set there answers 400 invalid_request_error, not 429.

Projects are the right first move for teams and environments. For anything finer than a project, the limit has to live in your code or in a gateway.

A cap per customer and per day, checked before the call

The pattern that holds under concurrency is reserve, call, settle:

  1. Reserve the most the call can cost (its prompt plus max_completion_tokens at your rates) against the customer's remaining budget. If it does not fit, refuse without calling OpenAI.
  2. Call OpenAI.
  3. Settle: replace the reservation with the real cost from usage.

Checking spend after the call instead lets ten concurrent requests all pass a check only one of them fits under. Reserving first closes that gap, which is the same gap OpenAI's own limit has.

import OpenAI, { APIError } from "openai";

// No SDK retries: it would retry the spend-limit 429 below too (see the 429 guide).
const client = new OpenAI({ maxRetries: 0 });
const MODEL = "gpt-5.4-mini";
// USD per 1M tokens. Use the rates your own contract carries.
const RATES = { input: 0.75, output: 4.5 };

const costUsd = (inputTokens: number, outputTokens: number) =>
  (inputTokens * RATES.input + outputTokens * RATES.output) / 1_000_000;

export class CapReached extends Error {}

/**
 * Spend per customer per UTC day. This keeps the ledger in memory, which is
 * correct for one process only: with several servers, keep it in Redis or
 * Postgres and make reserve() a single atomic increment-and-check.
 */
export class DailyCap {
  private spent = new Map<string, number>();
  private readonly limitUsd: number;

  constructor(limitUsd: number) {
    this.limitUsd = limitUsd;
  }

  private key(customer: string) {
    return `${customer}:${new Date().toISOString().slice(0, 10)}`;
  }

  reserve(customer: string, usd: number) {
    const used = this.spent.get(this.key(customer)) ?? 0;
    if (used + usd > this.limitUsd) {
      throw new CapReached(`not sent: ${customer} has used $${used.toFixed(4)} of its $${this.limitUsd} daily cap`);
    }
    this.spent.set(this.key(customer), used + usd);
  }

  // Replace the reservation with what the call actually cost.
  settle(customer: string, reservedUsd: number, actualUsd: number) {
    const used = this.spent.get(this.key(customer)) ?? 0;
    this.spent.set(this.key(customer), Math.max(0, used - reservedUsd + actualUsd));
  }
}

export async function answerFor(customer: string, prompt: string, cap: DailyCap, maxTokens = 500) {
  // Reserve the most this call can cost BEFORE making it, so ten concurrent
  // calls cannot all pass a check that only one of them fits under.
  // Two characters per token overestimates the prompt, on purpose.
  const reserved = costUsd(Math.ceil(prompt.length / 2), maxTokens);
  cap.reserve(customer, reserved);
  try {
    const res = await client.chat.completions.create({
      model: MODEL,
      max_completion_tokens: maxTokens,
      messages: [{ role: "user", content: prompt }],
    });
    cap.settle(customer, reserved, costUsd(res.usage?.prompt_tokens ?? 0, res.usage?.completion_tokens ?? 0));
    return res.choices[0]?.message.content ?? "";
  } catch (err) {
    cap.settle(customer, reserved, 0);
    // OpenAI's own project hard limit: stop, do not retry, tell a human.
    if (err instanceof APIError && err.code === "project_spend_limit_exceeded") {
      throw new CapReached("OpenAI project hard limit reached");
    }
    throw err;
  }
}

const cap = new DailyCap(0.005);
for (let i = 1; i <= 3; i++) {
  try {
    await answerFor("cus_123", "Write a one-line welcome for a new user.", cap);
    console.log(`call ${i}: served`);
  } catch (err) {
    if (!(err instanceof CapReached)) throw err;
    console.log(`call ${i}: refused, ${err.message}`);
  }
}
Tested: with a $0.005 cap the third call is refused before it is sent (two requests reach OpenAI); OpenAI's own project_spend_limit_exceeded is not retried.

The ledger here is in memory, which is correct only within one process. With more than one server, keep it in Redis or Postgres and make the reservation one atomic increment-and-check. Two more decisions to make on purpose:

  • Models you have no rate for. A cap cannot count what it cannot price. Refuse them while a cap applies, or give them a rate.
  • Your rates, not list price. If your contract carries a negotiated rate, cap on that, or your cap and your invoice will disagree.

When a limit is reached

Do not retry
if the error is project_spend_limit_exceeded, organization_spend_limit_exceeded or your own cap. The same request fails the same way until the limit resets or is raised.
Do not fail over to another provider
if a spend cap refused the request. Moving it to Anthropic spends around the limit you set.
Tell the user something specific
if a per-customer cap refused them: what happened and when it resets.
Alert a human
if the organization or project limit is reached; production traffic for that project has stopped.

Questions

Can I set a spending limit per API key in OpenAI?
Not directly. OpenAI's hard limits are per organization and per project. Give each key its own project, or check spend per key in your own code or a gateway before each call.
What error does OpenAI return when the hard limit is reached?
A 429 with code project_spend_limit_exceeded for a project limit, or organization_spend_limit_exceeded for the organization limit. Retrying will not succeed until the limit resets or is raised.
Does OpenAI's hard limit stop spend exactly at the limit?
No. OpenAI says enforcement is not instantaneous, so recorded spend can slightly exceed the configured amount. Set the limit below the figure you cannot pass.

Sources

Every statement about another company on this page comes from that company's own page, opened on the date shown. Prices and defaults change; check the source before you decide.

  1. 1.developers.openai.com/api/docs/guides/spend-limits (opens in a new tab) — checked Oct 24, 2026.
    • A project's monthly spend limit can be enforced as a hard limit under Project settings → Limits → Edit spend limit → Enforce a hard limit.
    • A request over an enforced project limit returns 429 with code project_spend_limit_exceeded; the organization limit returns organization_spend_limit_exceeded.
    • Enforcement is not instantaneous, so recorded spend can run slightly past the limit.
    • Spend limits are monthly and reset with the next monthly cycle.
    • Hard limits exist at two levels: the organization (all projects) and each project.
    • Spend alerts notify you but do not stop traffic.
  2. 2.developers.openai.com/api/docs/guides/rate-limits (opens in a new tab) — checked Oct 24, 2026.
    • OpenAI sets rate limits per organization and per project, not per user.
  3. 3.developers.openai.com/api/docs/guides/error-codes (opens in a new tab) — checked Oct 24, 2026.
    • OpenAI documents six distinct 429 errors: the plain rate limit, slow_down, and four billing or quota codes.
  4. 4.github.com/openai/openai-node/blob/master/src/client.ts (opens in a new tab) — checked Oct 24, 2026.
    • The SDKs retry every 429, whatever its code, unless the response carries x-should-retry: false.
  5. 5.platform.claude.com/docs/en/api/rate-limits (opens in a new tab) — checked Oct 24, 2026.
    • When an organization or workspace spend limit you set is reached, Anthropic returns 400 invalid_request_error, not 429.

The team building INFRO, a BYOK-first control plane for production AI. Articles are written from the engineering work on the gateway and cite their external sources.