# Idempotent ERP API Retry Wrapper (Python Generator)

> Source: https://netray.co/tools/idempotent-erp-api-retry-wrapper-python-generator
> Publisher: Netray (netray.co), AI and integration engineering for ERP and manufacturing systems.
> Section: Free Tools

## Short answer

Fill in your header names and limits to get a Python function that retries safely with backoff and sends an idempotency key on writes.

## Category

developer

## Audience

- Integration developer
- AI engineer
- ERP developer

## Tool

**Kind:** template

**Output:** code

**Language:** python

**Fields:**

#### Function name

**Key:** func_name

**Type:** text

**Default:** call_erp

#### ERP API base URL

**Key:** base_url

**Type:** text

**Default:** https://erp.example.com/api/v1

#### Environment variable holding the token

**Key:** token_env

**Type:** text

**Default:** ERP_API_TOKEN

#### Idempotency header name

**Key:** idem_header

**Type:** text

**Default:** Idempotency-Key

**Help:** Use the header your ERP or gateway documents. Many APIs have none.

#### Maximum attempts

**Key:** max_attempts

**Type:** number

**Default:** 5

#### Base delay seconds

**Key:** base_delay

**Type:** number

**Default:** 1

#### Maximum delay seconds

**Key:** max_delay

**Type:** number

**Default:** 30

#### Request timeout seconds

**Key:** timeout

**Type:** number

**Default:** 20

**Blocks:**

#### retry_wrapper.py

**Body:** import os
import random
import time
import uuid

import requests

BASE_URL = "{{base_url}}"
RETRY_STATUS = {429, 500, 502, 503, 504}
MAX_ATTEMPTS = {{max_attempts}}
BASE_DELAY = {{base_delay}}
MAX_DELAY = {{max_delay}}
TIMEOUT = {{timeout}}

def _sleep_seconds(attempt, response):
    # Honor Retry-After when the server sends a number of seconds
    if response is not None:
        value = response.headers.get("Retry-After")
        if value and value.isdigit():
            return min(int(value), MAX_DELAY)
    delay = min(MAX_DELAY, BASE_DELAY * (2 ** attempt))
    return random.uniform(0, delay)

def {{func_name}}(method, path, json_body=None, idempotency_key=None):
    url = BASE_URL + path
    headers = {
        "Authorization": "Bearer " + os.environ["{{token_env}}"],
        "Accept": "application/json",
    }
    is_write = method.upper() in ("POST", "PATCH", "PUT")
    if is_write:
        # Same key on every attempt so the server can drop duplicates
        headers["{{idem_header}}"] = idempotency_key or str(uuid.uuid4())

    last_error = None
    for attempt in range(MAX_ATTEMPTS):
        response = None
        try:
            response = requests.request(
                method, url, headers=headers, json=json_body, timeout=TIMEOUT
            )
            if response.status_code not in RETRY_STATUS:
                return response
            last_error = "HTTP " + str(response.status_code)
        except (requests.ConnectionError, requests.Timeout) as exc:
            last_error = str(exc)
        if attempt < MAX_ATTEMPTS - 1:
            time.sleep(_sleep_seconds(attempt, response))
    raise RuntimeError("Giving up after " + str(MAX_ATTEMPTS) + " attempts: " + str(last_error))

#### Usage example

**Body:** resp = {{func_name}}("GET", "/salesorders/41822")
resp.raise_for_status()
print(resp.json())

# Write: keep the key stable if you must re-run the same business action
key = "po-create-2026-10-20-ACME-0001"
resp = {{func_name}}("POST", "/purchaseorders", json_body={"vendor": "V1001"}, idempotency_key=key)

**Notes:** Only send an idempotency header if your ERP, API gateway or middleware actually honors it. If it does not, do not auto-retry writes on 5xx or timeouts: read the record first to see whether the first attempt committed. Requires the requests package. Verify header names and retry status codes against your vendor documentation.

## Content

**Intro:** A retry loop that resends a purchase order after a timeout can create two purchase orders. This generator outputs a small Python wrapper that retries on the right failures, waits with exponential backoff and jitter, honors Retry-After, and keeps one idempotency key across attempts.

**Sections:**

#### How to use it

**Body:** Set your base URL, the environment variable that holds the token, and the header name your API uses for idempotency. Choose attempt count and delays, then copy the code into your integration or agent tool. Call the function with the HTTP verb and path. For writes, pass your own stable key when the same business action might be triggered twice, such as a message processed again after a crash.

#### The method behind it

**Body:** Exponential backoff doubles the wait after each failure, capped at a maximum. Full jitter picks a random value between zero and that delay so many clients do not retry in lockstep against a recovering ERP server. Retry-After from the server overrides the computed delay. An idempotency key lets the server recognize a repeated write and return the original result instead of executing it again.

**Bullets:**

- Retry set: 429, 500, 502, 503, 504, plus connection errors and timeouts
- Never retry 400, 401, 403, 404, 409, 422 automatically
- Same key across all attempts of one business action
- Cap total attempts and surface the failure

#### When this is not enough

**Body:** Many ERP APIs have no idempotency support. In that case a timeout on a write leaves you not knowing whether the record exists. The safe pattern is to generate a client reference number, store it in a custom or external reference field, and query by it before retrying. Also consider a queue so a single worker owns each business action and concurrency stays below the ERP limits.

#### Common mistakes

**Body:** The usual errors are generating a new key per attempt, which defeats the purpose, retrying on 4xx errors that will never succeed, and using a fixed sleep that makes every worker hit the server together. Another is forgetting a timeout, so a hung connection blocks the agent forever.

**Cta:** Netray designs ERP integrations for AI agents where writes go through approval and safe retry. Contact us if you want your write path reviewed.

**Faq:**

#### What is an idempotency key?

**Answer:** It is a unique value the client sends with a write request so the server can detect a repeat of the same request and return the first result instead of performing the action twice. The key must stay the same across retries of one action.

#### Which ERPs support idempotency keys?

**Answer:** Support varies and is often absent on classic ERP APIs. Some gateways and payment style APIs honor it. Check your vendor documentation, and if there is no support use a client reference field and a lookup before retrying.

#### Why add jitter to backoff?

**Answer:** Without jitter, many clients that failed at the same moment retry at the same moment, which can overload a recovering server again. A random delay spreads the retries and reduces the repeated spikes.

#### Should an AI agent retry writes at all?

**Answer:** Only when duplicates are impossible or detectable. For most ERP writes we prefer approval before the call, one attempt, and a verification read if the outcome is unclear, rather than automatic repeated attempts.

## Search terms

python retry exponential backoff requests, idempotency key header python, erp api retry wrapper, retry after header python, safe retry post request, api retry with jitter python, duplicate order api retry, requests session retry

---
Talk to an engineer: https://netray.co/contact?intent=erp_ai | Machine catalog: https://netray.co/agent/catalog.json
