🏫 The School›🏢 APIs›⚠️ धडा 07 — Errors आणि idempotency: नम्र नकार आणि पावती क्रमांक
🖼️ See the drawing + lab 🏠 Course home 🌿 Branch on GitHub ✏️ View source
🖼️ आकृती आणि labThe drawing + lab पूर्ण पानावर उघडा ↗Open full page ↗

⚠️ धडा 07 — Errors आणि idempotency: नम्र नकार आणि पावती क्रमांक

📍 तुम्ही इथे आहात: 12 पैकी धडा 07 — भाग 2 सुरू! · मागे: lesson-06-auth · पुढे: lesson-08-pagination-filtering


📦 या ब्रँचमध्ये काय आहे

धडे 01–06, आणि त्यासोबत लोक विश्वास ठेवतात असा counter आणि लोक शिव्या देतात असा counter यांना वेगळ्या करणाऱ्या दोन सवयी: नेहमी एकच error आकार, आणि POST retry करणे सुरक्षित करणारा पावती क्रमांक (Idempotency-Key).

🧒 5 वर्षांच्या मुलाला समजावल्यासारखे

Clerk slip नाकारतो तेव्हा नकार नेहमी त्याच पद्धतीने लिहिलेला असतो: app वाचू शकेल असा code (validation_failed), माणूस वाचू शकेल असा message ("The form has problems."), असेल तेव्हा एक hint, आणि forms साठी, field-वार चुकांची यादी. प्रत्येक नकार, तीच रचना — म्हणून प्रत्येक app प्रत्येक नकार त्याच code ने हाताळू शकते.

आता retry ची समस्या. तुम्ही "Zoya चे नाव नोंदवा" देता आणि शिक्का परत येण्याआधीच line तुटते. ते नोंदले गेले का? पुन्हा दिले तर कदाचित दोन Zoya नोंदल्या जातील. म्हणून counter slip वर एक पावती क्रमांक स्वीकारतो — Idempotency-Key: enrol-zoya-1. त्या क्रमांकाची slip आधीच हाताळली गेली असेल तर clerk तेच शिक्का मारलेले उत्तर परत देतो आणि काहीही नवीन नोंदवत नाही (Idempotent-Replay: true). आता retry करणे नेहमी सुरक्षित आहे.

आणि सभ्य client पाळतो ते retry चे ढोबळ नियम:

🗺️ आकृती

sequenceDiagram
    participant C as 🧑 client
    participant S as 🏢 school-api
    C->>S: 1 POST /v1/students  Idempotency-Key: enrol-zoya-1  {"name":"Zoya",…}
    S-->>C: 2 201 Created · Location: /v1/students/6   (reply lost on the network ✂️)
    C->>S: 3 POST … Idempotency-Key: enrol-zoya-1  (retry)
    S-->>C: 4 201 Created · Idempotent-Replay: true · the SAME body, nothing new filed
    C->>S: 5 POST … {"name":"","class":"9Z"}
    S-->>C: 6 400 {"error":{"code":"validation_failed","message":…,"problems":[…]}}
    Note over C,S: 4xx = fix the slip · 429/5xx/timeout = retry with backoff (+ a receipt number for POST)

❓ काय

🤔 का

कारण clients तुमच्या यशांइतकेच तुमच्या errors च्या आधारावर लिहिले जातात: checkout page ला कळायला हवे की "तुमचा card नंबर दुरुस्त करा" म्हणायचे की "एका मिनिटाने पुन्हा प्रयत्न करा". एकच आकार आणि प्रामाणिक status codes यामुळे ते तीन ओळींचे switch बनते; मनमानी errors मुळे तो अंदाजांचा खेळ बनतो. आणि पावती क्रमांकांमुळेच payment APIs दोनदा पैसे कापणे टाळतात — ही पद्धत REST पेक्षा जुनी आहे आणि त्याच्यानंतरही टिकेल.

🔧 कसे (या repo मध्ये)

school_api.py मधले error() हे एकमेव नकार लिहिणारे आहे. do_POST मध्ये, IDEMPOTENCY[key] = (201, student) उत्तर लक्षात ठेवते; सुरुवातीची replay शाखा body वाचण्याच्याही आधी ते Idempotent-Replay: true सह परत देते.

🧪 करून पाहा

K='X-API-Key: hall-pass-123'; B=http://127.0.0.1:8080/v1; J='Content-Type: application/json'
curl -si -X POST $B/students -H "$K" -H "$J" -H 'Idempotency-Key: enrol-zoya-1' -d '{"name":"Zoya","class":"3B"}' | sed -n '1p;/Location/p;$p'
curl -si -X POST $B/students -H "$K" -H "$J" -H 'Idempotency-Key: enrol-zoya-1' -d '{"name":"Zoya","class":"3B"}' | sed -n '1p;/Idempotent-Replay/p;$p'
curl -s "$B/students?limit=20" | grep -o '"name": "Zoya"' | wc -l      # 1, not 2
curl -s "$B/students?sort=age"                                         # 400 bad_sort — same shape

✅ तपासा — तुम्हाला काय दिसायला हवे

त्याच id सह दोन एकसारखे 201; दुसऱ्यावर Idempotent-Replay: true असते; यादीत एकच Zoya असते. sort=age च्या नकाराचा आकार smoke test मधल्या इतर प्रत्येक नकारासारखाच {"error": {...}} असतो.

🏁 तुम्ही आत्ताच काय सिद्ध केले

हरवलेले उत्तर आता धोकादायक नाही: पावती क्रमांकासह, retry केलेला POST एकदाच नोंद करतो. आणि तुमचे API करत असलेला प्रत्येक नकार client code च्या एकाच तुकड्याने हाताळता येतो.

⚠️ नेहमीच्या चुका

🏭 प्रत्यक्ष वापरात हे का महत्त्वाचे: Stripe, Adyen आणि प्रत्येक गंभीर payment API POST वर idempotency key मागते, आणि कोणत्याही API forum वरची सर्वात मोठी तक्रार म्हणजे विसंगत errors. गरज पडण्याआधीच या दोन्ही सवयी उचला.

⏭️ पुढे

"पुढचे 20, कृपया" — कोणालाही न गमावता pagination, filtering आणि sorting.

git checkout lesson-08-pagination-filtering

⚠️ Lesson 07 — Errors & idempotency: polite refusals and receipt numbers

📍 You are here: Lesson 07 of 12 — Part 2 begins! · Previous: lesson-06-auth · Next: lesson-08-pagination-filtering


📦 What's in this branch

Lessons 01–06, plus the two habits that separate a counter people trust from one they curse: one error shape, always, and the receipt number (Idempotency-Key) that makes retrying a POST safe.

🧒 Explain like I'm 5

When the clerk refuses a slip, the refusal is always written the same way: a code the app can read (validation_failed), a message a human can read ("The form has problems."), a hint when there is one, and for forms, the list of problems field by field. Every refusal, same layout — so every app can handle every refusal with the same code.

Now the retry problem. You hand in "enrol Zoya" and the line goes dead before the stamp comes back. Did it get filed? If you hand it in again, you might enrol two Zoyas. So the counter accepts a receipt number on the slip — Idempotency-Key: enrol-zoya-1. If a slip with that number was already processed, the clerk hands back the same stamped answer and files nothing new (Idempotent-Replay: true). Now retrying is always safe.

And the retry rules of thumb the polite client follows:

🗺️ Diagram

sequenceDiagram
    participant C as 🧑 client
    participant S as 🏢 school-api
    C->>S: 1 POST /v1/students  Idempotency-Key: enrol-zoya-1  {"name":"Zoya",…}
    S-->>C: 2 201 Created · Location: /v1/students/6   (reply lost on the network ✂️)
    C->>S: 3 POST … Idempotency-Key: enrol-zoya-1  (retry)
    S-->>C: 4 201 Created · Idempotent-Replay: true · the SAME body, nothing new filed
    C->>S: 5 POST … {"name":"","class":"9Z"}
    S-->>C: 6 400 {"error":{"code":"validation_failed","message":…,"problems":[…]}}
    Note over C,S: 4xx = fix the slip · 429/5xx/timeout = retry with backoff (+ a receipt number for POST)

❓ What

🤔 Why

Because clients are written against your errors as much as your successes: a checkout page needs to know whether to say "fix your card number" or "try again in a minute". One shape and honest status codes make that a three-line switch; ad-hoc errors make it a guessing game. And receipt numbers are how payment APIs avoid charging twice — the pattern is older than REST and will outlive it.

🔧 How (in this repo)

error() in school_api.py is the single refusal writer. In do_POST, IDEMPOTENCY[key] = (201, student) remembers the answer; the replay branch at the top returns it with Idempotent-Replay: true before reading the body at all.

🧪 Try it

K='X-API-Key: hall-pass-123'; B=http://127.0.0.1:8080/v1; J='Content-Type: application/json'
curl -si -X POST $B/students -H "$K" -H "$J" -H 'Idempotency-Key: enrol-zoya-1' -d '{"name":"Zoya","class":"3B"}' | sed -n '1p;/Location/p;$p'
curl -si -X POST $B/students -H "$K" -H "$J" -H 'Idempotency-Key: enrol-zoya-1' -d '{"name":"Zoya","class":"3B"}' | sed -n '1p;/Idempotent-Replay/p;$p'
curl -s "$B/students?limit=20" | grep -o '"name": "Zoya"' | wc -l      # 1, not 2
curl -s "$B/students?sort=age"                                         # 400 bad_sort — same shape

✅ Verify — what you should see

Two identical 201s with the same id; the second carries Idempotent-Replay: true; the list contains one Zoya. The sort=age refusal has the same {"error": {...}} shape as every other refusal in the smoke test.

🏁 What you just proved

A dropped reply is no longer dangerous: with a receipt number, a retried POST files once. And every refusal your API makes can be handled by one piece of client code.

⚠️ Common mistakes

🏭 Why this matters in production: Stripe, Adyen and every serious payment API require an idempotency key on POST, and the top complaint in any API forum is inconsistent errors. Copy both habits before you need them.

⏭️ Next

"The next 20, please" — pagination, filtering and sorting without losing anyone.

git checkout lesson-08-pagination-filtering
← PreviousauthNext →pagination filtering

This page is the lesson's README from the lesson-07-errors-idempotency branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.