<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: chel.it852</title>
    <description>The latest articles on DEV Community by chel.it852 (@bwgit852_a0b6c1222ca6546).</description>
    <link>https://dev.to/bwgit852_a0b6c1222ca6546</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4238722%2Fd5d60928-0cf2-41c1-9ac5-6c9780471c51.png</url>
      <title>DEV Community: chel.it852</title>
      <link>https://dev.to/bwgit852_a0b6c1222ca6546</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bwgit852_a0b6c1222ca6546"/>
    <language>en</language>
    <item>
      <title>OpenAI 429s that retries won't fix: credit_balance_exhausted and spend limits</title>
      <dc:creator>chel.it852</dc:creator>
      <pubDate>Wed, 11 Nov 2026 09:23:04 +0000</pubDate>
      <link>https://dev.to/bwgit852_a0b6c1222ca6546/openai-429s-that-retries-wont-fix-creditbalanceexhausted-and-spend-limits-2jid</link>
      <guid>https://dev.to/bwgit852_a0b6c1222ca6546/openai-429s-that-retries-wont-fix-creditbalanceexhausted-and-spend-limits-2jid</guid>
      <description>&lt;p&gt;Most retry helpers treat every HTTP 429 from the OpenAI API the same way: wait, back off, try again. That is right for rate limits. It is wrong for billing errors, which also come back as 429. In the words of the &lt;a href="https://platform.openai.com/docs/guides/error-codes" rel="noopener noreferrer"&gt;error codes guide&lt;/a&gt;, retrying billing, spend or quota errors won't restore API access.&lt;/p&gt;

&lt;p&gt;This post shows how to tell them apart in code, and how the prepaid billing model behind them affects the way you top up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 429s you should retry
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;rate_limit_error&lt;/code&gt; (requests or tokens per minute) and &lt;code&gt;slow_down&lt;/code&gt; (your request rate rose too fast). Follow the &lt;code&gt;Retry-After&lt;/code&gt; header when it is present, then back off.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The 429s you should not retry
&lt;/h2&gt;

&lt;p&gt;These are billing errors. The guide says to inspect &lt;code&gt;error.code&lt;/code&gt; for the specific cause, while &lt;code&gt;error.type&lt;/code&gt; can still be the broader &lt;code&gt;insufficient_quota&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;credit_balance_exhausted&lt;/code&gt; — the organization has no prepaid credits left.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;organization_spend_limit_exceeded&lt;/code&gt; — the organization hit its own spend limit.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;project_spend_limit_exceeded&lt;/code&gt; — a project hit its spend limit.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;organization_usage_limit_exceeded&lt;/code&gt; — the organization reached its OpenAI-assigned usage limit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each one needs a human: add credits, raise a limit or ask for a higher tier. Until then every retry fails the same way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling it in Python
&lt;/h2&gt;

&lt;p&gt;With the official &lt;code&gt;openai&lt;/code&gt; package (v1+), all of these raise &lt;code&gt;RateLimitError&lt;/code&gt;, so look at the body before retrying:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;RateLimitError&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;BILLING_CODES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"credit_balance_exhausted"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"organization_spend_limit_exceeded"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"project_spend_limit_exceeded"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"organization_usage_limit_exceeded"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BillingBlocked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retries&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"gpt-4o-mini"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nc"&gt;RateLimitError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="no"&gt;BILLING_CODES&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"insufficient_quota"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# Not transient: stop the job and alert a human.&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;BillingBlocked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;"insufficient_quota"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
            &lt;span class="n"&gt;retry_after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"retry-after"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retry_after&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;retry_after&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"rate limited after retries"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A distinct exception lets a worker queue pause all jobs at once instead of every job retrying on its own, and lets you send one alert instead of thousands of identical log lines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why credits run out: prepaid billing
&lt;/h2&gt;

&lt;p&gt;API usage is paid from prepaid credits (&lt;a href="https://help.openai.com/en/articles/8264644" rel="noopener noreferrer"&gt;OpenAI help&lt;/a&gt;). Three details matter for code that runs unattended:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The minimum credit purchase is $5.&lt;/li&gt;
&lt;li&gt;Credits expire one year after purchase, so a large stock bought "just in case" can go to waste.&lt;/li&gt;
&lt;li&gt;Auto-recharge tops the balance up when it drops below a threshold. Each recharge is a separate card charge. If the card declines it, you may first learn about it from &lt;code&gt;credit_balance_exhausted&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Card fees change the math on small top-ups
&lt;/h2&gt;

&lt;p&gt;Many cards, especially prepaid and virtual ones, charge a fixed fee per purchase. On a $20 subscription, $0.80 is 4%. On a $5 credit top-up it is 16%. If auto-recharge buys $5 several times a month, the fees grow faster than you expect.&lt;/p&gt;

&lt;p&gt;Two practical rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set the auto-recharge amount so it fires once or twice a month, but not so high that credits sit unused toward the one-year expiry.&lt;/li&gt;
&lt;li&gt;If the card declines a charge, do not retry it many times in a row. Many issuers limit declined attempts. Find the reason first: billing address, 3DS confirmation or not enough money on the card.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Retry &lt;code&gt;rate_limit_error&lt;/code&gt; and &lt;code&gt;slow_down&lt;/code&gt;, honouring &lt;code&gt;Retry-After&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Treat the four billing codes and &lt;code&gt;insufficient_quota&lt;/code&gt; as a hard stop.&lt;/li&gt;
&lt;li&gt;Alert once and pause the queue.&lt;/li&gt;
&lt;li&gt;Keep a project spend limit so a bug cannot drain the balance.&lt;/li&gt;
&lt;li&gt;Size auto-recharge to your monthly usage and check what your card charges per purchase.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Disclosure: I work on Atlas, a service that issues virtual USD cards for paying international services from Russia. Our write-up on which cards work for API providers is in Russian, &lt;a href="https://atlaspay.life/karta-dlya-oplaty-api" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>openai</category>
      <category>api</category>
      <category>python</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
