1"use strict";(self.webpackChunkankr_docs_v2=self.webpackChunkankr_docs_v2||[]).push([["1606"],{38502(e,t,r){r.r(t),r.d(t,{metadata:()=>n,default:()=>h,frontMatter:()=>o,contentTitle:()=>a,toc:()=>c,assets:()=>d});var n=JSON.parse('{"id":"rpc-service/errors/retry-strategies","title":"Retry Strategies & Rate Limits","description":"How to handle transient RPC errors and rate limits \u2014 which codes are safe to retry, exponential backoff with jitter in JavaScript, Python, and Go, idempotency, and connection management.","source":"@site/docs/rpc-service/errors/retry-strategies.mdx","sourceDirName":"rpc-service/errors","slug":"/rpc-service/errors/retry-strategies","permalink":"/docs/rpc-service/errors/retry-strategies","draft":false,"unlisted":false,"editUrl":"https://github.com/w3tech/ankr-docs/tree/main/docs/rpc-service/errors/retry-strategies.mdx","tags":[],"version":"current","lastUpdatedAt":1790023853000,"sidebarPosition":10,"frontMatter":{"title":"Retry Strategies & Rate Limits","description":"How to handle transient RPC errors and rate limits \u2014 which codes are safe to retry, exponential backoff with jitter in JavaScript, Python, and Go, idempotency, and connection management.","sidebar_position":10,"faq":true},"sidebar":"tutorialSidebar","previous":{"title":"Error Reference","permalink":"/docs/rpc-service/errors/overview"},"next":{"title":"Per-Chain & Method Quirks","permalink":"/docs/rpc-service/errors/per-chain-quirks"}}'),s=r(74848),i=r(28453);let o={title:"Retry Strategies & Rate Limits",description:"How to handle transient RPC errors and rate limits \u2014 which codes are safe to retry, exponential backoff with jitter in JavaScript, Python, and Go, idempotency, and connection management.",sidebar_position:10,faq:!0},a="Retry Strategies & Rate Limits",d={},c=[{value:"What to retry, what to fix",id:"what-to-retry-what-to-fix",level:2},{value:"Exponential backoff with jitter",id:"exponential-backoff-with-jitter",level:2},{value:"Honor <code>429</code> and <code>Retry-After</code>
1",id:"honor-429-and-retry-after",level:2},{value:"Idempotency \u2014 what's safe to resend",id:"idempotency--whats-safe-to-resend",level:2},{value:"Connection management",id:"connection-management",level:2},{value:"FAQ",id:"faq",level:2},{value:"How many times should I retry?",id:"how-many-times-should-i-retry",level:3},{value:"Is it safe to retry a transaction send?",id:"is-it-safe-to-retry-a-transaction-send",level:3},{value:"Why am I getting <code>429</code> even under my request limit?",id:"why-am-i-getting-429-even-under-my-request-limit",level:3}];function l(e){let t={a:"a",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,i.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(t.header,{children:(0,s.jsx)(t.h1,{id:"retry-strategies--rate-limits",children:"Retry Strategies & Rate Limits"})}),"\n",(0,s.jsxs)(t.p,{children:["Transient errors are normal in distributed infrastructure. A resilient client ",(0,s.jsx)(t.strong,{children:"retries the right errors with backoff"})," and ",(0,s.jsx)(t.strong,{children:"fixes \u2014 not retries \u2014 the rest"}),". This page shows which is which and how to back off."]}),"\n",(0,s.jsx)(t.h2,{id:"what-to-retry-what-to-fix",children:"What to retry, what to fix"}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsxs)(t.th,{children:["Retry with backoff (server-side, ",(0,s.jsx)(t.code,{children:"5xx"}),")"]}),(0,s.jsxs)(t.th,{children:["Fix first \u2014 don't retry (client-side, ",(0,s.jsx)(t.code,{children:"4xx"}),")"]})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"-32055"})," No nodes available"]}),(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"-32049/-32050/-32051/-32060"})," API-key problems"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"-32056"})," Proxy error"]}),(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"-32052/-32053/-32054"})," key not allowed (chain/method/IP)"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"-32057"})," Node non-success status"]}),(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"-32602"})," Invalid params"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"-32059"})," Failed to dial node"]}),(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"-32601"})," Method not found"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"-32061"})," No archive nodes available"]}),(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"-32062"})," Request / block range / batch too large"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"-32063"})," Node returned unexpected error"]}),(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"-32079/-32080"})," origin / contract not allowed"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"-32064"})," Retry failed"]}),(0,s.jsx)(t.td,{})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"-32071"})," Request timeout"]}),(0,s.jsx)(t.td,{})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"-32076"})," Invalid response"]}),(0,s.jsx)(t.td,{})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"-32085"}),"\u2013",(0,s.jsx)(t.code,{children:"-32089"})," No alive WS nodes"]}),(0,s.jsx)(t.td,{})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"-32090"})," Too many requests ",(0,s.jsx)(t.strong,{children:"(429 \u2014 back off)"})]}),(0,s.jsx)(t.td,{})]})]})]}),"\n",(0,s.jsxs)(t.p,{children:["Rule of thumb: ",(0,s.jsxs)(t.strong,{children:["HTTP ",(0,s.jsx)(t.code,{children:"5xx"})," \u2192 retry; HTTP ",(0,s.jsx)(t.code,{children:"4xx"})," \u2192 fix."]})," The one ",(0,s.jsx)(t.code,{children:"4xx"})," you ",(0,s.jsx)(t.em,{children:"do"})," retry is ",(0,s.jsx)(t.code,{children:"429"})," (",(0,s.jsx)(t.code,{children:"-32090"}),") \u2014 but only with backoff, never in a tight loop. See the full list in the ",(0,s.jsx)(t.a,{href:"/rpc-service/errors/overview/",children:"Error Reference"}),"."]}),"\n",(0,s.jsx)(t.h2,{id:"exponential-backoff-with-jitter",children:"Exponential backoff with jitter"}),"\n",(0,s.jsxs)(t.p,{children:["Back off exponentially and add ",(0,s.jsx)(t.strong,{children:"jitter"})," so many clients don't retry in lockstep. Cap the delay and the attempt count."]}),"\n",(0,s.jsx)(t.p,{children:(0,s.jsx)(t.strong,{children:"JavaScript"})}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-js",children:'const RETRYABLE = new Set([-32055, -32056, -32057, -32059, -32061, -32063, -32064, -32071, -32076, -32090]);\n\nasync function rpc(url, body, { maxRetries = 5, baseMs = 200, capMs = 10_000 } = {}) {\n for (let attempt = 0; ; attempt++) {\n const res = await fetch(url, {\n method: "POST",\n headers: { "Content-Type": "application/json" },\n body: JSON.stringify(body),\n });\n\n if (res.status === 429 || res.status >= 500) {\n if (attempt >= maxRetries) throw new Error(`giving up after ${attempt} retries (HTTP ${res.status})`);\n await sleepBackoff(attempt, baseMs, capMs);\n continue;\n }\n\n const json = await res.json();\n if (json.error && RETRYABLE.has(json.error.code) && attempt < maxRetries) {\n await sleepBackoff(attempt, baseMs, capMs);\n continue;\n }\n return json; // success, or a non-retryable error to handle\n }\n}
1\n\nfunction sleepBackoff(attempt, baseMs, capMs) {\n const exp = Math.min(capMs, baseMs * 2 ** attempt);\n const delay = Math.random() * exp; // full jitter\n return new Promise((r) => setTimeout(r, delay));\n}\n'})}),"\n",(0,s.jsx)(t.p,{children:(0,s.jsx)(t.strong,{children:"Python"})}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-python",children:'import random, time, requests\n\nRETRYABLE = {-32055, -32056, -32057, -32059, -32061, -32063, -32064, -32071, -32076, -32090}\n\ndef rpc(url, body, max_retries=5, base=0.2, cap=10.0):\n for attempt in range(max_retries + 1):\n res = requests.post(url, json=body)\n if res.status_code == 429 or res.status_code >= 500:\n if attempt == max_retries:\n res.raise_for_status()\n time.sleep(random.uniform(0, min(cap, base * 2 ** attempt)))\n continue\n data = res.json()\n err = data.get("error")\n if err and err.get("code") in RETRYABLE and attempt < max_retries:\n time.sleep(random.uniform(0, min(cap, base * 2 ** attempt)))\n continue\n return data\n'})}),"\n",(0,s.jsx)(t.p,{children:(0,s.jsx)(t.strong,{children:"Go"})}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-go",children:"var retryable = map[int]bool{\n -32055: true, -32056: true, -32057: true, -32059: true, -32061: true,\n -32063: true, -32064: true, -32071: true, -32076: true, -32090: true,\n}\n\nfunc backoff(attempt int) time.Duration {\n exp := math.Min(10_000, 200*math.Pow(2, float64(attempt))) // ms, capped at 10s\n return time.Duration(rand.Float64()*exp) * time.Millisecond // full jitter\n}\n"})}),"\n",(0,s.jsxs)(t.h2,{id:"honor-429-and-retry-after",children:["Honor ",(0,s.jsx)(t.code,{children:"429"})," and ",(0,s.jsx)(t.code,{children:"Retry-After"})]}),"\n",(0,s.jsxs)(t.p,{children:["A ",(0,s.jsx)(t.code,{children:"429"})," / ",(0,s.jsx)(t.code,{children:"-32090"})," means you're over your plan's request rate. Don't hammer \u2014 back off, and if a ",(0,s.jsx)(t.code,{children:"Retry-After"})," header is present, wait at least that long. Sustained ",(0,s.jsx)(t.code,{children:"429"}),"s mean it's time to batch requests or ",(0,s.jsx)(t.a,{href:"/rpc-service/pricing/",children:"upgrade your plan"}),"."]}),"\n",(0,s.jsx)(t.h2,{id:"idempotency--whats-safe-to-resend",children:"Idempotency \u2014 what's safe to resend"}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:"Reads"})," (",(0,s.jsx)(t.code,{children:"eth_call"}),", ",(0,s.jsx)(t.code,{children:"eth_getLogs"}),", ",(0,s.jsx)(t.code,{children:"eth_getBlockByNumber"}),", \u2026) are idempotent \u2014 always safe to retry."]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.strong,{children:(0,s.jsx)(t.code,{children:"eth_sendRawTransaction"})})," is safe to resend: the transaction is already signed, so resending the same bytes yields the ",(0,s.jsx)(t.strong,{children:"same transaction hash"})," \u2014 it can't double-spend. Treat ",(0,s.jsx)(t.code,{children:'"already known"'})," / ",(0,s.jsx)(t.code,{children:'"nonce too low"'})," on a retry as success (the first attempt landed)."]}),"\n",(0,s.jsxs)(t.li,{children:["Always propagate a fresh request and keep the returned ",(0,s.jsx)(t.strong,{children:(0,s.jsx)(t.code,{children:"trace_id"})})," from any error for support."]}),"\n"]}),"\n",(0,s.jsx)(t.h2,{id:"connection-management",children:"Connection management"}),"\n",(0,s.jsxs)(t.p,{children:["The connection-limit errors (",(0,s.jsx)(t.code,{children:"-32067"}),", ",(0,s.jsx)(t.code,{children:"-32068"}),", ",(0,s.jsx)(t.code,{children:"-32069"}),", ",(0,s.jsx)(t.code,{children:"-32077"}),") mean too many ",(0,s.jsx)(t.em,{children:"concurrent"})," connections, not too many requests. Reuse a connection pool / keep-alive instead of opening a socket per call, and for WebSockets reconnect with the same backoff after ",(0,s.jsx)(t.code,{children:"-32084"})," (IO error) or a dropped subscription."]}),"\n",(0,s.jsx)(t.h2,{id:"faq",children:"FAQ"}),"\n",(0,s.jsx)(t.h3,{id:"how-many-times-should-i-retry",children:"How many times should I retry?"}),"\n",(0,s.jsxs)(t.p,{children:["Cap at ~5 attempts with exponential backoff (base ~200 ms, max delay ~10 s). Beyond that, surface the error with its ",(0,s.jsx)(t.code,{children:"trace_id"}),"."]}),"\n",(0,s.jsx)(t.h3,{id:"is-it-safe-to-retry-a-transaction-send",children:"Is it safe to retry a transaction send?"}),"\n",(0,s.jsxs)(t.p,{children:["Yes. ",(0,s.jsx)(t.code,{children:"eth_sendRawTransaction"})," carries a signed transaction with a fixed hash, so resending c
1an't create a duplicate. Handle ",(0,s.jsx)(t.code,{children:'"already known"'}),"/",(0,s.jsx)(t.code,{children:'"nonce too low"'})," as confirmation the original was accepted."]}),"\n",(0,s.jsxs)(t.h3,{id:"why-am-i-getting-429-even-under-my-request-limit",children:["Why am I getting ",(0,s.jsx)(t.code,{children:"429"})," even under my request limit?"]}),"\n",(0,s.jsxs)(t.p,{children:["Check the connection-limit codes (",(0,s.jsx)(t.code,{children:"-32067"}),"\u2013",(0,s.jsx)(t.code,{children:"-32077"}),") \u2014 those cap ",(0,s.jsx)(t.em,{children:"concurrent connections"}),", separately from request rate. Pool and reuse connections rather than opening one per call."]})]})}function h(e={}){let{wrapper:t}={...(0,i.R)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(l,{...e})}):l(e)}},28453(e,t,r){r.d(t,{R:()=>o,x:()=>a});var n=r(96540);let s={},i=n.createContext(s);function o(e){let t=n.useContext(i);return n.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function a(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:o(e.components),n.createElement(i.Provider,{value:t},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.