You are integrating the **eSahayak Stamp API** (bulk Indian e-stamp procurement) into my application. Follow this guide exactly; when in doubt, read the live spec. # Canonical references - Interactive docs: https://esahayak.io/docs/api - OpenAPI 3 spec (source of truth — fetch and follow it): https://esahayak.io/api/v1/openapi.json - Base URL: https://esahayak.io/api/v1 - Spec version: 1.0.0 # What this API does It procures government e-stamps in bulk. I fund a prepaid stamp-duty wallet by bank transfer, submit a batch of stamps, and receive verified stamps + certificates with an audit trail. Fulfilment is asynchronous (partners procure + OCR-verify over minutes). # Authentication - Send every request with header: `Authorization: Bearer `. - Keys come in two modes: `sk_test_...` = isolated SANDBOX (no real money/partners), `sk_live_...` = PRODUCTION. ALWAYS build and test against a `sk_test_` key first, then switch the key (no code change) to go live. - Never hardcode the key — read it from an environment variable. Never log it. # The wallet model (critical — get this right) - Stamp duty is prepaid via a wallet. `GET /wallet` returns `balance`, `held`, and `available` where `available = balance − held`. - Placing an order RESERVES the total face value (moves it to `held`). Fulfilment DEBITS it. Cancelling before fulfilment RELEASES the hold. - There is NO GST on wallet funding — stamp duty is a pass-through. eSahayak's service fee is invoiced separately (18% GST on the fee only); it does not move through the wallet. - If `available` is below an order's face value, create returns HTTP 402 INSUFFICIENT_BALANCE. Surface a clear "top up your wallet" message; do not retry blindly. # The happy path (implement in this order) 1. (Optional, recommended) `POST /stamp-orders/validate` with the batch. It returns per-row errors + non-blocking warnings + a price quote and creates NOTHING. Use it to catch bad rows and show the cost before committing. 2. `POST /stamp-orders` to commit. This RESERVES wallet funds. You MUST send a unique `Idempotency-Key` header per attempt; retrying with the same key returns the original order (never a duplicate). Returns 201 immediately with an order id. 3. Track fulfilment asynchronously — prefer webhooks over polling: - Webhooks (configure your URL + signing secret in the dashboard): you'll receive `stamp.verified`, `order.fulfilled`, `order.cancelled`, `partner_batch.assigned`, `wallet.low_balance`, `stamp.expiring`. They are signed (Standard Webhooks) — verify the signature with your signing secret before trusting the payload. - Or poll `GET /stamp-orders/{id}` and `GET /stamp-orders/{id}/stamps` and read the derived display status. 4. Once a stamp is verified, fetch its certificate via `GET /stamps/{id}/certificate`. 5. Need an auditable record? `GET /stamp-orders/{id}/export` returns a CSV/XLSX audit register. # Error handling (apply globally) - Every error body is `{ "error": { "code": "...", "message": "...", "requestId": "..." } }` with a stable machine-readable `code`. Branch on `code`, show `message`, and log `requestId` (also returned as the `x-request-id` response header) for support. - Codes you must handle: UNAUTHORIZED (401, bad/missing/revoked key), FORBIDDEN (403), VALIDATION_FAILED (422, fix the rows), INSUFFICIENT_BALANCE (402, top up), IDEMPOTENCY_REQUIRED (400, add the header), RATE_LIMITED (429 — back off using the rate-limit headers and retry), CONFLICT (409), NOT_FOUND (404), INTERNAL (500, retry idempotently). - Make all retries idempotent: reuse the same `Idempotency-Key` on create; cancel/replace are safe to repeat. # Reference data Build requests from `GET /states`, `GET /states/{code}/articles`, `GET /states/{code}/districts`, and `.../sros` rather than hardcoding state/article codes. # Deliverables I want from you - A small typed client (read the OpenAPI for exact request/response shapes), env-based key config, a validate→create flow with idempotency keys, a signed-webhook receiver for the events above, and robust error handling on the codes listed. - Default to the sandbox key in development. Start by fetching https://esahayak.io/api/v1/openapi.json and summarising the endpoints you'll use, then implement the validate→create→webhook flow.