{"openapi":"3.0.3","info":{"title":"eSahayak Stamp API","version":"2.2.0","description":"Procure government e-stamps at scale over a REST API — submit many stamps for a state in one call (one order = one state), fund duty once via a prepaid wallet, and receive verified stamps + certificates with a full audit trail.\n\n## Quickstart\n1. **Get a key.** Create a sandbox key (`sk_test_…`) from the dashboard → **Stamp API**. (Minting keys *via* the API needs an existing live key, so bootstrap your first key from the dashboard.)\n2. **Validate.** `POST /stamp-orders/validate` with your stamps → per-row errors + a price quote. Creates nothing, reserves nothing.\n3. **Create.** `POST /stamp-orders` with an `Idempotency-Key`. This RESERVES the face value from your wallet and returns immediately. One order = one state — split multi-state work into one order per state.\n4. **Await fulfilment.** Stamps are procured + OCR-verified asynchronously. Poll `GET /stamp-orders/{id}/stamps`, or subscribe to processing-started webhooks for cancellation lock and `order.fulfilled` / `stamp.verified` webhooks for completion.\n5. **Fetch the certificate.** Once a stamp is `VERIFIED`, `GET /stamps/{id}/certificate` returns a short-lived signed document URL.\n\nIn sandbox, advance a test stamp instantly with `POST /sandbox/stamps/{id}/simulate` instead of waiting for real fulfilment.\n\n## Authentication\n`Authorization: Bearer <key>`. `sk_test_…` hits an isolated **sandbox** (no real money/partners); `sk_live_…` is production. Keys are mode-scoped — a test key never sees live data, and vice-versa.\n\n**Sandbox never touches real wallets.** Test-mode orders neither reserve nor require any balance, so you do NOT need to fund a wallet to test the full create→fulfil flow. `GET /wallet` with an `sk_test_` key returns a **simulated unlimited balance** (`simulated: true`, `mode: \"test\"`) so a \"check balance, then order\" integration passes in sandbox — do not treat the sandbox balance as real money. Live keys report the true wallet (`simulated: false`); fund the STAMP_DUTY wallet by NEFT before going live.\n\n## Core concepts\n- **Prepaid stamp-duty wallet (no GST on funding).** Fund it by NEFT. Placing an order **RESERVE**s the face value (moves it to *held*); fulfilment **DEBIT**s it; cancellation **RELEASE**s the hold. `available = balance − held`. Stamp duty is a pure pass-through and never bears GST.\n- **Service-fee wallet (prepaid, optional).** Once your workspace enables the prepaid service-fee wallet (fund it from the dashboard — a GST **tax invoice is issued at every top-up**), each order also RESERVEs the fee **gross** (fee + 18%, summed per stamp — see `serviceWalletDebitTotal` in the quote) and settles it on fulfilment. No post-pay fee invoices are raised — tax was settled at top-up. Without this wallet, fees accrue and are invoiced separately (legacy). `GET /wallet` reports both balances.\n- **Validate before you commit.** `/stamp-orders/validate` returns per-row errors + a quote and reserves nothing.\n- **Idempotency.** Create requires an `Idempotency-Key`; replaying the same key returns the original order (namespaced by live/test) — safe to retry.\n- **Per-state rules.** Each state has its own article catalogue, fixed denominations, and required fields (SRO, cess, property area, payer gender…). Discover them via `/states/{code}` and validate up-front.\n- **One order = one state.** Every stamp in an order must be for the same state. Mixed-state payloads are rejected with per-row error code `MIXED_STATE` (inside a 422 `VALIDATION_FAILED`) — place one order per state instead. An order may still mix `e_stamp` and `traditional_stamp` where the state offers both (Maharashtra, West Bengal). Order responses carry the order's `state`.\n- **Live state availability.** States can be temporarily switched off by our ops team (e.g. government portal downtime). `GET /states/availability` lists which states are accepting orders right now, and `GET /states/{code}` echoes the same `active` flag. Validate/create reject rows for inactive states with per-row error code `STATE_INACTIVE` (inside a 422 `VALIDATION_FAILED`), so check availability before submitting. Applies to sandbox and live alike.\n\n## Order & stamp lifecycle\nAn order moves **PLACED → PARTIAL → FULFILLED** (or **CANCELLED**). Each stamp moves: `PLACED` (reserved) → `ASSIGNED` (to a partner) → `GENERATING` (being procured) → `VERIFYING` (scan uploaded, OCR running) → `VERIFIED` (certificate ready), or `CANCELLED`. Fulfilment takes minutes, not milliseconds — poll or use webhooks rather than blocking.\n\n## Errors\nEvery error is `{ error: { code, message, requestId } }` with a stable `code`; quote `requestId` (also on the `x-request-id` header) in support.\n\n| Code | HTTP | When |\n|---|---|---|\n| `UNAUTHORIZED` | 401 | Missing / invalid / expired / revoked key |\n| `FORBIDDEN` | 403 | Key lacks permission (e.g. minting a key from a sandbox key) |\n| `NOT_FOUND` | 404 | No such resource in this workspace + mode |\n| `VALIDATION_FAILED` | 422 | Bad input; `details` carries per-row issues (e.g. `STATE_INACTIVE`, `MIXED_STATE`, `AMOUNT_NOT_MULTIPLE`) |\n| `WARNINGS_NOT_ACKNOWLEDGED` | 422 | Resend with `acknowledgeWarnings: true` |\n| `INSUFFICIENT_BALANCE` | 402 | A wallet `available` is short; `details` has available/required + `wallet` (`STAMP_DUTY` or `SERVICE_FEE`) |\n| `CREDIT_LIMIT_EXCEEDED` | 402 | Net-terms workspaces only (service fee accrual; stamp duty always prepays from the wallet): `details` has creditLimit/exposure/required — settle outstanding invoices or ask for a limit review |\n| `BILLING_ACCOUNT_ON_HOLD` | 402 | Net-terms account is on hold — new credit paused until outstanding invoices settle |\n| `BILLING_ACCOUNT_SUSPENDED` | 403 | Net-terms account suspended — bulk orders not accepted |\n| `RATE_LIMITED` | 429 | Too many requests; see `Retry-After` + `X-RateLimit-*` |\n| `IDEMPOTENCY_REQUIRED` | 400 | Create called without an `Idempotency-Key` |\n| `INVALID_STATE` | 400 | Operation not allowed in the resource's current state |\n| `CONFLICT` | 409 | e.g. cancelling an already-terminal stamp |\n| `INTERNAL` | 500 | Unexpected server error — safe to retry idempotent calls |\n\n## Rate limits\nPer API key: a burst of **120** with a sustained **20 req/s** refill. Every response carries `X-RateLimit-Limit` and `X-RateLimit-Remaining`; a 429 adds `Retry-After`. (Enforced per compute instance — pilot-grade — so real throughput may be higher.)\n\n## Webhooks\nSubscribe instead of polling. Set an endpoint + signing secret in the dashboard. Events are **signed (Standard Webhooks)** — verify the `webhook-signature` header against your secret before trusting the body. Delivery is at-least-once; dedupe on the event id. The **Webhooks** section lists the event catalogue + payloads and lets you replay any delivery.\n\n## Changelog\n- **2.2.0** — Uttar Pradesh stamps are sold only in multiples of ₹10 (its portal sells nothing else): an off-step denomination fails with per-row code `AMOUNT_NOT_MULTIPLE` (422 `VALIDATION_FAILED`). The step is published as `amountMultipleOf` on the state views and `denomination.multipleOf` in `/states/{code}/requirements`.\n- **2.0.0** — One order = one state: mixed-state payloads are rejected with per-row code `MIXED_STATE` (422 `VALIDATION_FAILED`). Order responses now include `state`. No change needed for integrations that already send single-state orders.\n- **1.2.0** — Live state availability (`GET /states/availability`, `STATE_INACTIVE` enforcement); sandbox wallet reports a simulated unlimited balance.","contact":{"name":"eSahayak Support","url":"https://wa.me/+919056929505"}},"servers":[{"url":"/api/v1","description":"Production + sandbox (key selects)"}],"security":[{"bearerApiKey":[]}],"tags":[{"name":"Stamp orders","description":"An order groups many stamps **for one state** under one reference (one order = one state; split multi-state work into separate orders). The lifecycle is validate (optional, free) → create (reserves wallet funds) → poll/await webhooks → fulfilled. Cancel before fulfilment to release held funds."},{"name":"Stamps","description":"Individual stamps inside an order. Read a single stamp's status, fetch its certificate once verified, or cancel/replace one stamp without touching the rest of the order."},{"name":"Reference data","description":"The catalogue you build requests from — supported states and their articles, districts, and sub-registrar offices. Look these up to build a valid request and validate inputs client-side before submitting."},{"name":"Wallet","description":"Your prepaid stamp-duty balance. Funded by NEFT (no GST). `balance` is total funded, `held` is reserved for in-flight orders, `available` is what new orders can draw. Set a low-balance threshold to get a webhook alert."},{"name":"Webhooks","description":"Get notified without polling. Each event is delivered as `{ type, timestamp, data }`, signed via Standard Webhooks (verify the `webhook-signature` header against your signing secret before trusting the body). Delivery is at-least-once — dedupe on the event id; replay any delivery from `GET /webhooks`.\n\n**Events**\n- `stamp.processing_started` — a partner accepted the batch containing this stamp, so this stamp can no longer be cancelled directly. Use this to disable per-stamp cancellation. `data`: `{ stampId, bulkOrderId, clientReference, partnerBatchId, acceptedAt }`.\n- `order.processing_started` — the first partner batch for this bulk order was accepted, so full-order cancellation is no longer available. Use this to disable parent-order cancellation. `data`: `{ bulkOrderId, partnerBatchId, acceptedAt, clientReferences, stamps }`, where `stamps` is `[{ stampId, clientReference }]` for the first accepted batch.\n- `stamp.verified` — per-stamp completion event. Use this to download and reconcile each stamp as soon as it is ready. `data`: `{ stampId, bulkOrderId, clientReference, certificateNumber, documentUrl, documentUrlExpiresInSeconds, certificateUrl }`. `clientReference` is your per-stamp id from the create request. `documentUrl` is a short-lived signed link to the stamp paper; `certificateUrl` is the durable API endpoint to fetch a fresh signed link later.\n- `order.fulfilled` — order-level terminal event after every stamp in the bulk order is either verified or cancelled. Use this to close the parent order in your system. `data`: `{ bulkOrderId, clientReferences, stamps }`, where `stamps` is `[{ stampId, clientReference }]`.\n- `stamp.cancelled` — one stamp was cancelled and its hold released. Use this to mark the individual stamp failed/cancelled. `data`: `{ stampId, bulkOrderId, clientReference }`.\n- `order.cancelled` — the full bulk order was cancelled. Use this to close the parent order and release expectations for all remaining stamps. `data`: `{ bulkOrderId, cancelledStamps }`.\n- `wallet.low_balance` — a wallet's available balance dropped below your configured threshold. Use this to trigger top-up workflows before new orders fail for insufficient balance. `data`: `{ walletId, walletType, available, threshold }` (`walletType`: `STAMP_DUTY` or `SERVICE_FEE`).\n\n**Webhook payload examples**\n\nEvery webhook delivery is a signed POST with this outer shape:\n\n```json\n{\n  \"type\": \"stamp.processing_started\",\n  \"timestamp\": \"2026-06-16T05:10:01.000Z\",\n  \"data\": {}\n}\n```\n\n`stamp.processing_started` data:\n\n```json\n{\n  \"stampId\": \"019ecebf-36ef-70b3-bb1e-cb4bf6caf566\",\n  \"bulkOrderId\": \"019ecebf-36ac-7d02-a18e-367ad311e704\",\n  \"clientReference\": \"po-7-1\",\n  \"partnerBatchId\": \"019ecebf-4865-79cb-bd12-d3a22a1356bd\",\n  \"acceptedAt\": \"2026-06-16T05:10:00.000Z\"\n}\n```\n\n`order.processing_started` data:\n\n```json\n{\n  \"bulkOrderId\": \"019ecebf-36ac-7d02-a18e-367ad311e704\",\n  \"partnerBatchId\": \"019ecebf-4865-79cb-bd12-d3a22a1356bd\",\n  \"acceptedAt\": \"2026-06-16T05:10:00.000Z\",\n  \"stamps\": [\n    {\n      \"stampId\": \"019ecebf-36ef-70b3-bb1e-cb4bf6caf566\",\n      \"clientReference\": \"po-7-1\"\n    }\n  ],\n  \"clientReferences\": [\"po-7-1\"]\n}\n```\n\n`stamp.verified` data:\n\n```json\n{\n  \"stampId\": \"019ecebf-36ef-70b3-bb1e-cb4bf6caf566\",\n  \"bulkOrderId\": \"019ecebf-36ac-7d02-a18e-367ad311e704\",\n  \"clientReference\": \"po-7-1\",\n  \"certificateNumber\": \"IN-MH12345678901234X\",\n  \"documentUrl\": \"https://storage.example.com/signed/stamp-paper.png\",\n  \"documentUrlExpiresInSeconds\": 900,\n  \"certificateUrl\": \"https://esahayak.io/api/v1/stamps/019ecebf-36ef-70b3-bb1e-cb4bf6caf566/certificate\"\n}\n```\n\n`order.fulfilled` data:\n\n```json\n{\n  \"bulkOrderId\": \"019ecebf-36ac-7d02-a18e-367ad311e704\",\n  \"stamps\": [\n    {\n      \"stampId\": \"019ecebf-36ef-70b3-bb1e-cb4bf6caf566\",\n      \"clientReference\": \"po-7-1\"\n    }\n  ],\n  \"clientReferences\": [\"po-7-1\"]\n}\n```\n\n`stamp.cancelled` data:\n\n```json\n{\n  \"stampId\": \"019ecebf-36ef-70b3-bb1e-cb4bf6caf566\",\n  \"bulkOrderId\": \"019ecebf-36ac-7d02-a18e-367ad311e704\",\n  \"clientReference\": \"po-7-1\"\n}\n```\n\n`order.cancelled` data:\n\n```json\n{\n  \"bulkOrderId\": \"019ecebf-36ac-7d02-a18e-367ad311e704\",\n  \"cancelledStamps\": 1\n}\n```\n\n`wallet.low_balance` data:\n\n```json\n{\n  \"walletId\": \"019ecebf-72e1-7fac-92be-17d5e02ff5d8\",\n  \"available\": 750,\n  \"threshold\": 1000\n}\n```"},{"name":"Sandbox","description":"Test-mode-only helpers (require an `sk_test_` key). Start a sandbox order processing, or advance a sandbox stamp through its lifecycle instantly — verify or fail it — so you can exercise your polling + webhook handling without waiting for real fulfilment. No-ops on live keys."},{"name":"API keys","description":"Programmatic key management. Minting a key here requires an existing LIVE key, so generate your FIRST key from the dashboard (Stamp API). Revoking supports a 30-day grace window for zero-downtime rotation."}],"paths":{"/stamp-orders":{"get":{"tags":["Stamp orders"],"summary":"List stamp orders","responses":{"200":{"description":"Paged orders (display status derived).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"tags":["Stamp orders"],"summary":"Create a stamp order (one state per order)","description":"Commits an order of stamps for ONE state. This RESERVES the total face value from your stamp-duty wallet — and, when your workspace is prepaid on fees, the gross service fee from your service-fee wallet — failing with 402 (see `details.wallet`) if either `available` is short. **All stamps in the order must be for the same state** — mixed-state payloads are rejected with per-row error code `MIXED_STATE` (the create fails fast on that check alone; run `/validate` to see the full per-row picture in one call). Split multi-state work into one order per state, each with its own `Idempotency-Key`. Call `/validate` first to catch row errors and see the quote without spending. Rows in states whose live availability switch is off (see `GET /states/availability`) are rejected with per-row error code `STATE_INACTIVE`. The `Idempotency-Key` is mandatory: a retry with the same key returns the original order instead of creating a duplicate. Returns immediately — fulfilment is async (poll the order or subscribe to webhooks).\n\n**Webhook payload examples**\n\nEvery webhook delivery is a signed POST with this outer shape:\n\n```json\n{\n  \"type\": \"stamp.processing_started\",\n  \"timestamp\": \"2026-06-16T05:10:01.000Z\",\n  \"data\": {}\n}\n```\n\n`stamp.processing_started` data:\n\n```json\n{\n  \"stampId\": \"019ecebf-36ef-70b3-bb1e-cb4bf6caf566\",\n  \"bulkOrderId\": \"019ecebf-36ac-7d02-a18e-367ad311e704\",\n  \"clientReference\": \"po-7-1\",\n  \"partnerBatchId\": \"019ecebf-4865-79cb-bd12-d3a22a1356bd\",\n  \"acceptedAt\": \"2026-06-16T05:10:00.000Z\"\n}\n```\n\n`order.processing_started` data:\n\n```json\n{\n  \"bulkOrderId\": \"019ecebf-36ac-7d02-a18e-367ad311e704\",\n  \"partnerBatchId\": \"019ecebf-4865-79cb-bd12-d3a22a1356bd\",\n  \"acceptedAt\": \"2026-06-16T05:10:00.000Z\",\n  \"stamps\": [\n    {\n      \"stampId\": \"019ecebf-36ef-70b3-bb1e-cb4bf6caf566\",\n      \"clientReference\": \"po-7-1\"\n    }\n  ],\n  \"clientReferences\": [\"po-7-1\"]\n}\n```\n\n`stamp.verified` data:\n\n```json\n{\n  \"stampId\": \"019ecebf-36ef-70b3-bb1e-cb4bf6caf566\",\n  \"bulkOrderId\": \"019ecebf-36ac-7d02-a18e-367ad311e704\",\n  \"clientReference\": \"po-7-1\",\n  \"certificateNumber\": \"IN-MH12345678901234X\",\n  \"documentUrl\": \"https://storage.example.com/signed/stamp-paper.png\",\n  \"documentUrlExpiresInSeconds\": 900,\n  \"certificateUrl\": \"https://esahayak.io/api/v1/stamps/019ecebf-36ef-70b3-bb1e-cb4bf6caf566/certificate\"\n}\n```\n\n`order.fulfilled` data:\n\n```json\n{\n  \"bulkOrderId\": \"019ecebf-36ac-7d02-a18e-367ad311e704\",\n  \"stamps\": [\n    {\n      \"stampId\": \"019ecebf-36ef-70b3-bb1e-cb4bf6caf566\",\n      \"clientReference\": \"po-7-1\"\n    }\n  ],\n  \"clientReferences\": [\"po-7-1\"]\n}\n```\n\n`stamp.cancelled` data:\n\n```json\n{\n  \"stampId\": \"019ecebf-36ef-70b3-bb1e-cb4bf6caf566\",\n  \"bulkOrderId\": \"019ecebf-36ac-7d02-a18e-367ad311e704\",\n  \"clientReference\": \"po-7-1\"\n}\n```\n\n`order.cancelled` data:\n\n```json\n{\n  \"bulkOrderId\": \"019ecebf-36ac-7d02-a18e-367ad311e704\",\n  \"cancelledStamps\": 1\n}\n```\n\n`wallet.low_balance` data:\n\n```json\n{\n  \"walletId\": \"019ecebf-72e1-7fac-92be-17d5e02ff5d8\",\n  \"available\": 750,\n  \"threshold\": 1000\n}\n```","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string"},"description":"Unique key per create attempt. Re-sending the same key returns the original order (namespaced by live/test)."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string","description":"Optional. Your parent order / PO reference."},"acknowledgeWarnings":{"type":"boolean","default":false,"description":"Optional. Set true only after /stamp-orders/validate returns non-blocking warnings you accept."},"stamps":{"type":"array","minItems":1,"maxItems":5000,"description":"Required. One or more stamp lines to procure — all for the SAME state (one order = one state). Each line can set quantity to create multiple identical stamps.","items":{"$ref":"#/components/schemas/StampInput"}}},"required":["stamps"]}}}},"responses":{"201":{"description":"Order created. Includes the created stamp IDs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderCreated"}}}},"400":{"description":"Idempotency-Key required"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/InsufficientBalance"},"422":{"description":"Validation failed / warnings not acknowledged"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/stamp-orders/validate":{"post":{"tags":["Stamp orders"],"summary":"Validate + quote (no order created)","description":"Dry-run an order: returns per-row blocking errors and non-blocking warnings plus a price quote (face value + service fee + 18% GST on the fee). Creates nothing and reserves nothing. Always call this before `create` so you never commit an order with bad rows or insufficient balance. Mixed-state payloads report the blocking per-row code `MIXED_STATE` (one order = one state). Rows in states whose live availability switch is off (see `GET /states/availability`) report the blocking error code `STATE_INACTIVE`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"stamps":{"type":"array","items":{"$ref":"#/components/schemas/StampInput"}}},"required":["stamps"]}}}},"responses":{"200":{"description":"Per-row errors/warnings + a quote (face value + service fee + 18% GST on fee)."},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/stamp-orders/{id}":{"get":{"tags":["Stamp orders"],"summary":"Get an order (display status)","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"The order.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/stamp-orders/{id}/stamps":{"get":{"tags":["Stamp orders"],"summary":"List the stamps in an order","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Paged stamps in the order.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StampList"}}}}}}},"/stamp-orders/{id}/cancel":{"post":{"tags":["Stamp orders"],"summary":"Cancel an order (releases unspent holds)","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Cancelled"},"409":{"description":"Already in a terminal state"}}}},"/stamp-orders/{id}/export":{"get":{"tags":["Stamp orders"],"summary":"Audit register export (CSV/XLSX)","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"File"}}}},"/stamps/{id}":{"get":{"tags":["Stamps"],"summary":"Get a stamp","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"The stamp with its certificate details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Certificate"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/stamps/{id}/cancel":{"post":{"tags":["Stamps"],"summary":"Cancel a single stamp","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Cancelled"}}}},"/stamps/{id}/replace":{"post":{"tags":["Stamps"],"summary":"Request a replacement stamp","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Replacement queued"}}}},"/stamps/{id}/certificate":{"get":{"tags":["Stamps"],"summary":"Signed certificate document URL","parameters":[{"$ref":"#/components/parameters/Id"},{"name":"format","in":"query","required":false,"schema":{"type":"string","enum":["image","pdf"],"default":"image"},"description":"Requested stamp paper format. `image` returns the stored stamp image. `pdf` returns a PDF; image-backed stamps are converted to PDF on demand."}],"responses":{"200":{"description":"The stamp plus a short-lived signed documentUrl.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Certificate"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/sandbox/stamps/{id}/simulate":{"post":{"tags":["Sandbox"],"summary":"Simulate a stamp outcome (test mode only)","description":"Fast-forward a sandbox stamp to a terminal state so you can test polling + webhooks without waiting for real fulfilment. `verified` issues a synthetic certificate and emits `stamp.verified` (plus `order.fulfilled` once the whole order is terminal); `failed` cancels the stamp (releases the hold) and emits `stamp.cancelled`. Requires an `sk_test_` key — returns 403 on a live key.","parameters":[{"$ref":"#/components/parameters/Id"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"outcome":{"type":"string","enum":["verified","failed"],"default":"verified"}}}}}},"responses":{"200":{"description":"Simulated.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["VERIFIED","CANCELLED"]},"simulated":{"type":"boolean"}}}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/sandbox/stamp-orders/{id}/simulate-accept":{"post":{"tags":["Sandbox"],"summary":"Start a sandbox order processing (test mode only)","description":"Originates the `order.processing_started` webhook (once) plus one `stamp.processing_started` webhook per still-pending stamp — exactly as a live partner-batch acceptance would, with payloads identical to production. Use it to exercise your processing_started handlers without waiting for real fulfilment. Idempotent (re-running never double-delivers) and order-safe (already verified/cancelled stamps are skipped). The stamp's polled status is unchanged — the transition is delivered purely as webhooks. Requires an `sk_test_` key — returns 403 on a live key.","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Processing started; webhooks emitted.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"The bulk order id."},"simulated":{"type":"boolean"},"stampsStarted":{"type":"integer","description":"Number of stamp.processing_started events emitted."}},"required":["id","simulated","stampsStarted"]}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/wallet":{"get":{"tags":["Wallet"],"summary":"Wallet balance (balance / held / available)","responses":{"200":{"description":"Current wallet balance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Wallet"}}}}}},"patch":{"tags":["Wallet"],"summary":"Set a low-balance alert threshold","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"lowBalanceThreshold":{"type":"number","nullable":true},"wallet":{"type":"string","enum":["STAMP_DUTY","SERVICE_FEE"],"default":"STAMP_DUTY","description":"Which wallet's threshold to set."}}}}}},"responses":{"200":{"description":"OK"}}}},"/wallet/transactions":{"get":{"tags":["Wallet"],"summary":"Wallet ledger","parameters":[{"name":"wallet","in":"query","required":false,"schema":{"type":"string","enum":["STAMP_DUTY","SERVICE_FEE"],"default":"STAMP_DUTY"},"description":"Which wallet's ledger to page through."}],"responses":{"200":{"description":"Paged results.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WalletTransactionList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/states":{"get":{"tags":["Reference data"],"summary":"List supported states","responses":{"200":{"description":"All configured states with their option flags.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/StateListItem"}},"count":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/states/availability":{"get":{"tags":["Reference data"],"summary":"Live state availability","description":"Which states are accepting new stamp orders right now. `active` reflects the live operational switch (the same toggle gating the consumer checkout) on top of static serviceability — a state can be temporarily disabled, e.g. during government portal downtime. Rows for inactive states are rejected by validate/create with per-row error code `STATE_INACTIVE`, so check here (or the `active` flag on `GET /states/{code}`) before submitting. Loads no per-state config, so it is cheap to call often; the switch applies to sandbox and live modes alike.","parameters":[{"name":"active","in":"query","required":false,"schema":{"type":"boolean"},"description":"Pass `true` to return only states currently accepting orders."}],"responses":{"200":{"description":"Per-state live availability.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/StateAvailability"}},"count":{"type":"integer","description":"Number of states returned (after the `active` filter)."},"activeCount":{"type":"integer","description":"Total states currently active (independent of the filter)."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/states/{code}":{"get":{"tags":["Reference data"],"summary":"State config","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","example":"HR"}}],"responses":{"200":{"description":"State configuration.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StateDetail"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/states/{code}/articles":{"get":{"tags":["Reference data"],"summary":"Article catalogue for a state","description":"Returns the list of stamp article types available in this state, with minimum amounts and allowed denominations. States without a fixed catalogue return an empty list.","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","example":"HR"}}],"responses":{"200":{"description":"Article list.","content":{"application/json":{"schema":{"type":"object","properties":{"stateCode":{"type":"string","example":"HR"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ArticleItem"}},"count":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/states/{code}/requirements":{"get":{"tags":["Reference data"],"summary":"Required & optional fields for a stamp combination","description":"Machine-readable required/optional-field spec for a (state, article, denomination, category) combination — the discovery counterpart to POST /stamp-orders/validate. `articleCode`, `denomination` and `category` are optional; when supplied, conditional rules (payerGender over ₹500, second-party-not-applicable, etc.) resolve to concrete `required` booleans instead of `requiredWhen` rules. The fields marked `required` here are exactly what /validate and /create reject when blank.","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","example":"DL"}},{"name":"articleCode","in":"query","required":false,"schema":{"type":"string","example":"DL-RG-31"}},{"name":"denomination","in":"query","required":false,"schema":{"type":"number","example":500}},{"name":"category","in":"query","required":false,"schema":{"type":"string","enum":["e_stamp","traditional_stamp"],"example":"e_stamp"}}],"responses":{"200":{"description":"Resolved requirement spec.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StampRequirements"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/states/{code}/districts":{"get":{"tags":["Reference data"],"summary":"Districts for a state","description":"Returns districts for states that require an SRO. States without SRO districts return an empty `data` array (not a 404).","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","example":"PB"}}],"responses":{"200":{"description":"District list (empty if this state has no SRO districts).","content":{"application/json":{"schema":{"type":"object","properties":{"stateCode":{"type":"string"},"data":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","example":"PB-AS"},"name":{"type":"string","example":"Amritsar"}}}},"count":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/states/{code}/districts/{districtCode}/sros":{"get":{"tags":["Reference data"],"summary":"Sub-registrar offices for a district","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","example":"PB"}},{"name":"districtCode","in":"path","required":true,"schema":{"type":"string","example":"PB-AS"}}],"responses":{"200":{"description":"SRO list.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","example":"PB-AMR"},"name":{"type":"string","example":"Amritsar"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api-keys":{"get":{"tags":["API keys"],"summary":"List keys","responses":{"200":{"description":"OK"}}},"post":{"tags":["API keys"],"summary":"Create a key (requires an existing LIVE key)","description":"Mints a new key. The plaintext secret is returned ONCE in this response and never again (only a hash is stored) — capture it now. This endpoint is gated to LIVE keys, so it can't bootstrap your first key; create that from the dashboard. Use it for programmatic rotation once you already hold a live key.","responses":{"201":{"description":"Created — secret shown once"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/api-keys/{id}":{"delete":{"tags":["API keys"],"summary":"Revoke a key (optional ?graceDays for rotation)","parameters":[{"$ref":"#/components/parameters/Id"},{"name":"graceDays","in":"query","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Revoked"}}}},"/webhooks":{"get":{"tags":["Webhooks"],"summary":"List webhook deliveries","responses":{"200":{"description":"Paged results.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/webhooks/{id}/replay":{"post":{"tags":["Webhooks"],"summary":"Replay a webhook delivery","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Re-enqueued"}}}}},"components":{"securitySchemes":{"bearerApiKey":{"type":"http","scheme":"bearer","description":"Bearer API key — `sk_live_…` (production) or `sk_test_…` (sandbox)."}},"parameters":{"Id":{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}},"responses":{"Unauthorized":{"description":"Missing / invalid / expired / revoked key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["UNAUTHORIZED","FORBIDDEN","NOT_FOUND","VALIDATION_FAILED","WARNINGS_NOT_ACKNOWLEDGED","INSUFFICIENT_BALANCE","RATE_LIMITED","IDEMPOTENCY_REQUIRED","INVALID_STATE","CONFLICT","INTERNAL"]},"message":{"type":"string"},"requestId":{"type":"string","example":"req_2f9c…"},"details":{}},"required":["code","message","requestId"]}}}}}},"Forbidden":{"description":"Not allowed (e.g. creating a key from a sandbox key)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["UNAUTHORIZED","FORBIDDEN","NOT_FOUND","VALIDATION_FAILED","WARNINGS_NOT_ACKNOWLEDGED","INSUFFICIENT_BALANCE","RATE_LIMITED","IDEMPOTENCY_REQUIRED","INVALID_STATE","CONFLICT","INTERNAL"]},"message":{"type":"string"},"requestId":{"type":"string","example":"req_2f9c…"},"details":{}},"required":["code","message","requestId"]}}}}}},"NotFound":{"description":"Not found (workspace + is_test scoped)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["UNAUTHORIZED","FORBIDDEN","NOT_FOUND","VALIDATION_FAILED","WARNINGS_NOT_ACKNOWLEDGED","INSUFFICIENT_BALANCE","RATE_LIMITED","IDEMPOTENCY_REQUIRED","INVALID_STATE","CONFLICT","INTERNAL"]},"message":{"type":"string"},"requestId":{"type":"string","example":"req_2f9c…"},"details":{}},"required":["code","message","requestId"]}}}}}},"Conflict":{"description":"State conflict — the resource can't make the requested transition (e.g. already terminal, or nothing left to simulate)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["UNAUTHORIZED","FORBIDDEN","NOT_FOUND","VALIDATION_FAILED","WARNINGS_NOT_ACKNOWLEDGED","INSUFFICIENT_BALANCE","RATE_LIMITED","IDEMPOTENCY_REQUIRED","INVALID_STATE","CONFLICT","INTERNAL"]},"message":{"type":"string"},"requestId":{"type":"string","example":"req_2f9c…"},"details":{}},"required":["code","message","requestId"]}}}}}},"InsufficientBalance":{"description":"Wallet available balance is below the order face value","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["UNAUTHORIZED","FORBIDDEN","NOT_FOUND","VALIDATION_FAILED","WARNINGS_NOT_ACKNOWLEDGED","INSUFFICIENT_BALANCE","RATE_LIMITED","IDEMPOTENCY_REQUIRED","INVALID_STATE","CONFLICT","INTERNAL"]},"message":{"type":"string"},"requestId":{"type":"string","example":"req_2f9c…"},"details":{}},"required":["code","message","requestId"]}}}}}},"RateLimited":{"description":"Rate limit exceeded. Limits are per API key: a burst of **120 requests** with a sustained refill of **20 requests/second**. The `Retry-After` header tells you how many seconds to wait before retrying. Limits are enforced per compute instance (pilot-grade), so real-world throughput may be higher.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer","example":1}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["UNAUTHORIZED","FORBIDDEN","NOT_FOUND","VALIDATION_FAILED","WARNINGS_NOT_ACKNOWLEDGED","INSUFFICIENT_BALANCE","RATE_LIMITED","IDEMPOTENCY_REQUIRED","INVALID_STATE","CONFLICT","INTERNAL"]},"message":{"type":"string"},"requestId":{"type":"string","example":"req_2f9c…"},"details":{}},"required":["code","message","requestId"]}}}}}}},"schemas":{"StateListItem":{"type":"object","description":"A state returned by GET /states. Field names are camelCase.","properties":{"name":{"type":"string","example":"Haryana"},"code":{"type":"string","example":"HR"},"minAmount":{"type":"number","nullable":true,"example":101},"maxAmount":{"type":"number","nullable":true},"amountMultipleOf":{"type":"number","nullable":true,"example":10,"description":"The state sells stamps only in whole multiples of this face value (₹), e.g. 10 for Uttar Pradesh — ₹100 or ₹110, never ₹101. Null when any whole amount is accepted. Off-step denominations fail /validate + /create with AMOUNT_NOT_MULTIPLE."},"eStampAvailable":{"type":"boolean","nullable":true},"traditionalAvailable":{"type":"boolean","nullable":true},"sroRequired":{"type":"boolean"},"cessRequired":{"type":"boolean"},"propertyAreaRequired":{"type":"boolean"},"fatherHusbandNameRequired":{"type":"boolean","nullable":true},"firstPartyAddressRequired":{"type":"boolean","nullable":true},"panCardImageRequired":{"type":"boolean","nullable":true},"payerGenderRequiredAboveAmount":{"type":"number","description":"Payer gender (payerGender) is required when the denomination exceeds this face value in catalogue states. For the full per-combination requirement set use GET /states/{code}/requirements.","example":500}},"required":["name","code"]},"StateDetail":{"type":"object","description":"Per-state configuration returned by GET /states/{code}.","properties":{"code":{"type":"string","example":"HR"},"name":{"type":"string","example":"Haryana"},"active":{"type":"boolean","description":"Live availability switch — false means the state is temporarily not accepting new stamp orders (rows would fail with STATE_INACTIVE)."},"eStampAvailable":{"type":"boolean"},"traditionalAvailable":{"type":"boolean"},"minAmount":{"type":"number","nullable":true},"maxAmount":{"type":"number","nullable":true},"amountMultipleOf":{"type":"number","nullable":true,"example":10,"description":"The state sells stamps only in whole multiples of this face value (₹), e.g. 10 for Uttar Pradesh — ₹100 or ₹110, never ₹101. Null when any whole amount is accepted. Off-step denominations fail /validate + /create with AMOUNT_NOT_MULTIPLE."},"cessRequired":{"type":"boolean","nullable":true},"propertyAreaRequired":{"type":"boolean","nullable":true},"sroRequired":{"type":"boolean","nullable":true},"fatherHusbandNameRequired":{"type":"boolean","nullable":true},"firstPartyAddressRequired":{"type":"boolean","nullable":true},"panCardImageRequired":{"type":"boolean","nullable":true},"payerGenderRequiredAboveAmount":{"type":"number","description":"payerGender is required when the denomination exceeds this face value in catalogue states.","example":500}},"required":["code","name","active","eStampAvailable","traditionalAvailable"]},"StateAvailability":{"type":"object","description":"Live per-state order acceptance from GET /states/availability. `active` reflects the operational switch on top of static serviceability (`eStampAvailable` / `traditionalAvailable`).","properties":{"code":{"type":"string","example":"HR"},"name":{"type":"string","example":"Haryana"},"active":{"type":"boolean","description":"True when the state is currently accepting new stamp orders."},"eStampAvailable":{"type":"boolean"},"traditionalAvailable":{"type":"boolean"}},"required":["code","name","active","eStampAvailable","traditionalAvailable"],"example":{"code":"HR","name":"Haryana","active":true,"eStampAvailable":true,"traditionalAvailable":false}},"ArticleItem":{"type":"object","description":"A stamp article from GET /states/{code}/articles.","properties":{"code":{"type":"string","example":"Affidavit"},"description":{"type":"string","example":"Affidavit"},"popular":{"type":"boolean"},"minimumAmount":{"type":"number","example":101},"allowedAmounts":{"type":"array","items":{"type":"number"},"nullable":true},"isSecondPartyNA":{"type":"boolean","description":"If true, second party is not required for this article."}},"required":["code","description","minimumAmount"]},"FieldRequirement":{"type":"object","description":"One field's requirement for the requested combination.","properties":{"field":{"type":"string","example":"payerGender"},"required":{"type":"boolean","description":"True when the bulk API rejects the order if this field is blank for the requested inputs."},"requiredWhen":{"type":"string","description":"The rule that makes this field required, when it could not be resolved to a boolean (e.g. denomination not supplied)."},"recommended":{"type":"boolean","description":"Not hard-rejected by the bulk API, but the state/provider expects it."},"allowedValues":{"type":"array","items":{"type":"string"}},"valueValidated":{"type":"boolean","description":"The value is validated against a catalogue when provided."},"discoverVia":{"type":"string","description":"Endpoint that lists valid values for this field."},"enforcedByBulkApi":{"type":"boolean"},"note":{"type":"string"}},"required":["field","required","enforcedByBulkApi"]},"StampRequirements":{"type":"object","description":"Required & optional field spec for a stamp combination (GET /states/{code}/requirements). Discovery counterpart to /stamp-orders/validate — fields marked `required` here are exactly what /validate + /create reject when blank.","properties":{"state":{"type":"object","properties":{"code":{"type":"string","example":"DL"},"name":{"type":"string","example":"Delhi"},"active":{"type":"boolean"},"categoriesSupported":{"type":"array","items":{"type":"string","enum":["e_stamp","traditional_stamp"]}},"minAmount":{"type":"number","nullable":true,"example":10},"maxAmount":{"type":"number","nullable":true,"example":500},"amountMultipleOf":{"type":"number","nullable":true,"example":10,"description":"The state sells stamps only in whole multiples of this face value (₹), e.g. 10 for Uttar Pradesh — ₹100 or ₹110, never ₹101. Null when any whole amount is accepted. Off-step denominations fail /validate + /create with AMOUNT_NOT_MULTIPLE."}}},"category":{"type":"string","enum":["e_stamp","traditional_stamp"]},"article":{"type":"object","nullable":true,"properties":{"code":{"type":"string","example":"DL-RG-31"},"description":{"type":"string","nullable":true},"valid":{"type":"boolean"},"minimumAmount":{"type":"number","nullable":true},"allowedAmounts":{"type":"array","items":{"type":"number"},"nullable":true},"isSecondPartyNA":{"type":"boolean"}}},"denomination":{"type":"object","properties":{"provided":{"type":"number","nullable":true},"min":{"type":"number","nullable":true,"description":"Effective minimum = max(state min, article minimum)."},"max":{"type":"number","nullable":true,"description":"State maximum (null = uncapped)."},"allowedAmounts":{"type":"array","items":{"type":"number"},"nullable":true},"multipleOf":{"type":"number","nullable":true,"description":"The denomination must be a whole multiple of this (₹), e.g. 10 for Uttar Pradesh. Null = no step."}}},"fields":{"type":"array","items":{"$ref":"#/components/schemas/FieldRequirement"}},"limits":{"type":"object","properties":{"maxStampsPerRequest":{"type":"integer","example":5000},"maxQuantityPerLine":{"type":"integer","example":1000},"maxStampsPerAccountPerDay":{"type":"integer","nullable":true}}},"preflight":{"type":"object","description":"Account-level gates enforced only at POST /stamp-orders (not surfaced by /validate).","properties":{"idempotencyKeyRequired":{"type":"boolean"},"requiresSufficientWalletBalance":{"type":"boolean"}}},"meta":{"type":"object","properties":{"hasCatalogue":{"type":"boolean"},"payerGenderRequiredAboveAmount":{"type":"number","example":500}}}},"required":["state","category","fields"]},"StampParty":{"type":"object","description":"A party to the instrument. `name` is required; address / father-or-husband name / gender are required only in states whose article requires them (see /states/{code}).","properties":{"name":{"type":"string","example":"Acme Pvt Ltd"},"address":{"type":"string","description":"Party address. Required where isFirstPartyAddressRequired; optional (recommended) elsewhere — e.g. Haryana. Stored on the stamp and shown to the fulfilling partner."},"phone":{"type":"string"},"fatherHusbandName":{"type":"string","description":"Required where the state requires it."},"gender":{"type":"string","enum":["MALE","FEMALE","OTHER"],"description":"Payer gender — required over ₹500 in catalogue states."}},"required":["name"]},"StampInput":{"type":"object","description":"One stamp to procure. Per-state requirements (article catalogue, fixed denominations, SRO/cess/property-area/address/gender) are enforced on create AND on /stamp-orders/validate. Discover a state's catalogue via /states/{code}/articles, districts and SROs.","properties":{"clientReference":{"type":"string","description":"Optional. Your per-stamp id, echoed in stamp/order responses and webhooks."},"state":{"type":"string","example":"Maharashtra","description":"Required. State name or code. Must be the SAME state on every stamp in the order (one order = one state) — differing rows are rejected with per-row code `MIXED_STATE`."},"category":{"type":"string","enum":["e_stamp","traditional_stamp"],"default":"e_stamp","description":"Optional. Defaults to `e_stamp`."},"articleCode":{"type":"string","example":"5","description":"Required. Validated against the state's article catalogue where one exists; free-text otherwise."},"denomination":{"type":"number","example":500,"description":"Required. Face value (₹). Validated vs the state min/max, the article's minimum / fixed allowedAmounts, and the state's amountMultipleOf (Uttar Pradesh: multiples of ₹10 — an off-step value fails with AMOUNT_NOT_MULTIPLE)."},"stampDutyPayer":{"type":"string","example":"Acme Pvt Ltd","description":"Optional. The party paying stamp duty: the first or the second party's name (case, spacing and punctuation such as . , - & ( ) / _ ignored; other characters must match), or `FIRST_PARTY` / `SECOND_PARTY`. Defaults to the first party; stored as that party's name. In states where the sheet is endorsed to the first party (Telangana), the payer is always the first party. A name that is neither party, or `SECOND_PARTY` on a stamp with no second party, is rejected with per-row code `PAYER_NOT_A_PARTY`."},"payerPhone":{"type":"string","description":"Optional payer phone number."},"payerGender":{"type":"string","enum":["MALE","FEMALE","OTHER"],"description":"Required over ₹500 in catalogue states."},"firstParty":{"$ref":"#/components/schemas/StampParty"},"secondParty":{"allOf":[{"$ref":"#/components/schemas/StampParty"}],"description":"Required for catalogue articles that are not single-party (e.g. not an affidavit)."},"purpose":{"type":"string","example":"Leave & licence","description":"Optional. Purpose / instrument description."},"district":{"type":"string","example":"HR-GRG","description":"Optional district code. Strongly recommended in sroRequired states (e.g. Haryana, Himachal Pradesh) — the partner procures from this jurisdiction. Validated against /states/{code}/districts where the state has a district catalogue; free-text otherwise."},"sro":{"type":"string","example":"HR-GRG-SOHNA","description":"Optional sub-registrar office / tehsil code. Send together with `district`; validated against /states/{code}/districts/{districtCode}/sros where the district has an SRO catalogue."},"cessAmount":{"type":"number","description":"Required in isCessRequired states."},"propertyArea":{"type":"object","description":"Optional property area details. If you send a unit other than `Not Applicable`, also send a positive value.","properties":{"unit":{"type":"string","enum":["Square Feet","Square Yard","Square Meters","Not Applicable"]},"value":{"type":"number"}}},"costCentre":{"type":"string","example":"CC-MUMBAI","description":"Optional. Your internal cost centre for reconciliation."},"businessUnit":{"type":"string","example":"Legal","description":"Optional. Your internal business unit for reconciliation."},"linkedDocRef":{"type":"string","description":"Optional. Your reference to the underlying document."},"quantity":{"type":"integer","minimum":1,"maximum":1000,"default":1,"description":"Optional. Mint N identical stamps with distinct client references. Defaults to 1."}},"required":["state","articleCode","denomination","firstParty"]},"Order":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"reference":{"type":"string","nullable":true,"description":"Your PO / reference"},"state":{"type":"string","nullable":true,"example":"Haryana","description":"The order's single state (display name) — one order = one state. null on orders created before this contract."},"status":{"type":"string","enum":["PLACED","PARTIAL","FULFILLED","CANCELLED"],"description":"Derived display status (never the raw DB status)."},"isTest":{"type":"boolean"},"counts":{"type":"object","description":"Per-stage tallies across the order's stamps.","properties":{"requested":{"type":"integer"},"allocated":{"type":"integer"},"generated":{"type":"integer"},"verified":{"type":"integer"},"delivered":{"type":"integer"},"cancelled":{"type":"integer"}}},"createdAt":{"type":"string","format":"date-time"}},"example":{"id":"0190…","reference":"PO-7","state":"Haryana","status":"PARTIAL","isTest":true,"counts":{"requested":10,"allocated":10,"generated":6,"verified":4,"delivered":0,"cancelled":0},"createdAt":"2026-06-08T10:00:00.000Z"}},"OrderCreated":{"allOf":[{"$ref":"#/components/schemas/Order"},{"type":"object","properties":{"stampCount":{"type":"integer","description":"Number of stamps in the order."},"stamps":{"type":"array","description":"Every stamp created for this order, in creation order. Use these `id`s directly with /stamps/{id}/*, /stamps/{id}/certificate, and /sandbox/stamps/{id}/simulate — no follow-up list call needed. NB: a stamp id is NOT the order id.","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"clientReference":{"type":"string","nullable":true}}}}}}],"example":{"id":"019ecebf-36ac-7d02-a18e-367ad311e704","reference":"PO-7","state":"Haryana","status":"PLACED","isTest":true,"counts":{"requested":1,"allocated":0,"generated":0,"verified":0,"delivered":0,"cancelled":0},"createdAt":"2026-06-16T04:44:58.311Z","stampCount":1,"stamps":[{"id":"019ecebf-36ef-70b3-bb1e-cb4bf6caf566","clientReference":"po-7-1"}]}},"Stamp":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"clientReference":{"type":"string","nullable":true},"bulkOrderId":{"type":"string","format":"uuid","nullable":true,"description":"The bulk order ID from POST /stamp-orders. Use this to correlate stamps back to their parent order."},"orderId":{"type":"string","format":"uuid","description":"Internal order reference — prefer bulkOrderId for correlation."},"status":{"type":"string","enum":["PLACED","ASSIGNED","GENERATING","VERIFYING","VERIFIED","CANCELLED"]},"verified":{"type":"boolean"},"state":{"type":"string"},"category":{"type":"string","enum":["e_stamp","traditional_stamp"]},"amount":{"type":"number","example":500},"uin":{"type":"string","nullable":true,"description":"Certificate / unique identification number, once issued."},"costCentre":{"type":"string","nullable":true},"businessUnit":{"type":"string","nullable":true},"purpose":{"type":"string","nullable":true}}},"Certificate":{"type":"object","description":"A stamp with its full certificate projection. `documentUrl` is populated only on GET /stamps/{id}/certificate.","properties":{"id":{"type":"string","format":"uuid"},"clientReference":{"type":"string","nullable":true},"bulkOrderId":{"type":"string","format":"uuid","nullable":true,"description":"The bulk order ID from POST /stamp-orders."},"orderId":{"type":"string","format":"uuid","description":"Internal order reference — prefer bulkOrderId for correlation."},"status":{"type":"string","enum":["PLACED","ASSIGNED","GENERATING","VERIFYING","VERIFIED","CANCELLED"]},"verified":{"type":"boolean"},"state":{"type":"string"},"category":{"type":"string","enum":["e_stamp","traditional_stamp"]},"certificate":{"type":"object","properties":{"certificateNumber":{"type":"string","nullable":true},"uin":{"type":"string","nullable":true},"issuedDate":{"type":"string","nullable":true},"issuedBy":{"type":"string","nullable":true},"articleDescription":{"type":"string","nullable":true},"stampAmount":{"type":"string","example":"500.00"},"considerationAmount":{"type":"string","example":"0.00"},"stampDutyPaidBy":{"type":"string","nullable":true},"firstPartyName":{"type":"string","nullable":true},"secondPartyName":{"type":"string","nullable":true},"purpose":{"type":"string","nullable":true}}},"costCentre":{"type":"string","nullable":true},"businessUnit":{"type":"string","nullable":true},"format":{"type":"string","enum":["image","pdf"],"description":"Format of the returned `documentUrl` when requested via /stamps/{id}/certificate."},"documentUrl":{"type":"string","nullable":true,"description":"Short-lived signed document URL (~5 min). Re-fetch when it expires."}}},"Wallet":{"type":"object","description":"Top-level fields are the STAMP_DUTY (face value) wallet; `serviceFee` is the prepaid service-fee wallet (zeros when not enabled). With a `sk_test_` key this is a SIMULATED sandbox wallet (`simulated: true`) with an unlimited balance — sandbox orders never consume it.","properties":{"mode":{"type":"string","enum":["test","live"],"description":"Which key mode produced this response."},"simulated":{"type":"boolean","description":"true for sandbox (`sk_test_`) keys: the balance is a fake unlimited figure, not real money. Never gate a live flow on a simulated balance."},"balance":{"type":"number","description":"Total funded."},"held":{"type":"number","description":"Reserved for in-flight orders."},"available":{"type":"number","description":"balance − held; what new orders can draw."},"lowBalanceThreshold":{"type":"number","nullable":true},"currency":{"type":"string","example":"INR"},"serviceFee":{"type":"object","description":"Prepaid service-fee wallet (gross, GST settled at top-up).","properties":{"balance":{"type":"number"},"held":{"type":"number"},"available":{"type":"number"},"lowBalanceThreshold":{"type":"number","nullable":true}}}}},"WalletTransaction":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["CREDIT","DEBIT","RESERVE","RELEASE","REFUND"]},"amount":{"type":"number"},"bulkOrderId":{"type":"string","nullable":true},"partnerBatchId":{"type":"string","nullable":true},"externalRef":{"type":"string","nullable":true,"description":"NEFT UTR for CREDIT; idempotency anchor."},"createdAt":{"type":"string","format":"date-time"}}},"WebhookDelivery":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"eventType":{"type":"string"},"eventId":{"type":"string"},"status":{"type":"string","description":"delivered / failed / pending"},"attempts":{"type":"integer"},"responseStatus":{"type":"integer","nullable":true},"createdAt":{"type":"string","format":"date-time"},"lastAttemptAt":{"type":"string","format":"date-time","nullable":true}}},"WebhookStampRef":{"type":"object","properties":{"stampId":{"type":"string","format":"uuid"},"clientReference":{"type":"string","nullable":true}},"required":["stampId","clientReference"]},"StampProcessingStartedPayload":{"type":"object","description":"`data` for `stamp.processing_started`.","properties":{"stampId":{"type":"string","format":"uuid"},"bulkOrderId":{"type":"string","format":"uuid"},"clientReference":{"type":"string","nullable":true},"partnerBatchId":{"type":"string","format":"uuid"},"acceptedAt":{"type":"string","format":"date-time"}},"required":["stampId","bulkOrderId","clientReference","partnerBatchId","acceptedAt"],"example":{"stampId":"019ecebf-36ef-70b3-bb1e-cb4bf6caf566","bulkOrderId":"019ecebf-36ac-7d02-a18e-367ad311e704","clientReference":"po-7-1","partnerBatchId":"019ecebf-4865-79cb-bd12-d3a22a1356bd","acceptedAt":"2026-06-16T05:10:00.000Z"}},"OrderProcessingStartedPayload":{"type":"object","description":"`data` for `order.processing_started`. Fired once for the bulk order when the first partner batch is accepted. `stamps` and `clientReferences` cover that first accepted batch.","properties":{"bulkOrderId":{"type":"string","format":"uuid"},"partnerBatchId":{"type":"string","format":"uuid"},"acceptedAt":{"type":"string","format":"date-time"},"stamps":{"type":"array","items":{"$ref":"#/components/schemas/WebhookStampRef"}},"clientReferences":{"type":"array","items":{"type":"string"}}},"required":["bulkOrderId","partnerBatchId","acceptedAt","stamps","clientReferences"],"example":{"bulkOrderId":"019ecebf-36ac-7d02-a18e-367ad311e704","partnerBatchId":"019ecebf-4865-79cb-bd12-d3a22a1356bd","acceptedAt":"2026-06-16T05:10:00.000Z","stamps":[{"stampId":"019ecebf-36ef-70b3-bb1e-cb4bf6caf566","clientReference":"po-7-1"},{"stampId":"019ecebf-36f0-7b86-93af-eafdb172cbfb","clientReference":"po-7-2"}],"clientReferences":["po-7-1","po-7-2"]}},"StampVerifiedPayload":{"type":"object","description":"`data` for `stamp.verified`. `certificateNumber`, `documentUrl`, and `certificateUrl` are added during delivery when enrichment succeeds; if enrichment degrades, the identifier fields still deliver.","properties":{"stampId":{"type":"string","format":"uuid"},"bulkOrderId":{"type":"string","format":"uuid"},"clientReference":{"type":"string","nullable":true},"certificateNumber":{"type":"string","nullable":true},"documentUrl":{"type":"string","description":"Short-lived signed URL to the stamp paper. Re-fetch via certificateUrl when expired."},"documentUrlExpiresInSeconds":{"type":"integer","nullable":true,"example":900},"certificateUrl":{"type":"string","description":"Durable API endpoint that returns a fresh signed documentUrl."}},"required":["stampId","bulkOrderId","clientReference"],"example":{"stampId":"019ecebf-36ef-70b3-bb1e-cb4bf6caf566","bulkOrderId":"019ecebf-36ac-7d02-a18e-367ad311e704","clientReference":"po-7-1","certificateNumber":"IN-MH12345678901234X","documentUrl":"https://storage.example.com/signed/stamp-paper.png","documentUrlExpiresInSeconds":900,"certificateUrl":"https://esahayak.io/api/v1/stamps/019ecebf-36ef-70b3-bb1e-cb4bf6caf566/certificate"}},"OrderFulfilledPayload":{"type":"object","description":"`data` for `order.fulfilled`.","properties":{"bulkOrderId":{"type":"string","format":"uuid"},"stamps":{"type":"array","items":{"$ref":"#/components/schemas/WebhookStampRef"}},"clientReferences":{"type":"array","items":{"type":"string"}}},"required":["bulkOrderId","stamps","clientReferences"],"example":{"bulkOrderId":"019ecebf-36ac-7d02-a18e-367ad311e704","stamps":[{"stampId":"019ecebf-36ef-70b3-bb1e-cb4bf6caf566","clientReference":"po-7-1"},{"stampId":"019ecebf-36f0-7b86-93af-eafdb172cbfb","clientReference":"po-7-2"}],"clientReferences":["po-7-1","po-7-2"]}},"StampCancelledPayload":{"type":"object","description":"`data` for `stamp.cancelled`.","properties":{"stampId":{"type":"string","format":"uuid"},"bulkOrderId":{"type":"string","format":"uuid"},"clientReference":{"type":"string","nullable":true}},"required":["stampId","bulkOrderId","clientReference"],"example":{"stampId":"019ecebf-36ef-70b3-bb1e-cb4bf6caf566","bulkOrderId":"019ecebf-36ac-7d02-a18e-367ad311e704","clientReference":"po-7-1"}},"OrderCancelledPayload":{"type":"object","description":"`data` for `order.cancelled`.","properties":{"bulkOrderId":{"type":"string","format":"uuid"},"cancelledStamps":{"type":"integer"}},"required":["bulkOrderId","cancelledStamps"],"example":{"bulkOrderId":"019ecebf-36ac-7d02-a18e-367ad311e704","cancelledStamps":2}},"WalletLowBalancePayload":{"type":"object","description":"`data` for `wallet.low_balance`.","properties":{"walletId":{"type":"string","format":"uuid"},"available":{"type":"number"},"threshold":{"type":"number"}},"required":["walletId","available","threshold"],"example":{"walletId":"019ecebf-72e1-7fac-92be-17d5e02ff5d8","available":750,"threshold":1000}},"WebhookEvent":{"type":"object","description":"The signed JSON body POSTed to your endpoint. The `webhook-id` header is the stable event id for dedupe/replay.","properties":{"type":{"type":"string","enum":["order.processing_started","stamp.processing_started","order.fulfilled","stamp.verified","stamp.cancelled","order.cancelled","wallet.low_balance"]},"timestamp":{"type":"string","format":"date-time"},"data":{"description":"Event-specific payload. Match this with `type`.","anyOf":[{"$ref":"#/components/schemas/OrderProcessingStartedPayload"},{"$ref":"#/components/schemas/StampProcessingStartedPayload"},{"$ref":"#/components/schemas/OrderFulfilledPayload"},{"$ref":"#/components/schemas/StampVerifiedPayload"},{"$ref":"#/components/schemas/StampCancelledPayload"},{"$ref":"#/components/schemas/OrderCancelledPayload"},{"$ref":"#/components/schemas/WalletLowBalancePayload"}]}},"required":["type","timestamp","data"],"example":{"type":"stamp.processing_started","timestamp":"2026-06-16T05:10:01.000Z","data":{"stampId":"019ecebf-36ef-70b3-bb1e-cb4bf6caf566","bulkOrderId":"019ecebf-36ac-7d02-a18e-367ad311e704","clientReference":"po-7-1","partnerBatchId":"019ecebf-4865-79cb-bd12-d3a22a1356bd","acceptedAt":"2026-06-16T05:10:00.000Z"}}},"Pagination":{"type":"object","properties":{"data":{"type":"array","items":{}},"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer","description":"Unfiltered count for the same query."},"hasMore":{"type":"boolean","description":"True when more rows exist beyond this page."}},"required":["data","limit","offset","total","hasMore"]},"OrderList":{"allOf":[{"$ref":"#/components/schemas/Pagination"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Order"}}}}]},"StampList":{"allOf":[{"$ref":"#/components/schemas/Pagination"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Stamp"}}}}]},"WalletTransactionList":{"allOf":[{"$ref":"#/components/schemas/Pagination"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WalletTransaction"}}}}]},"WebhookDeliveryList":{"allOf":[{"$ref":"#/components/schemas/Pagination"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}}}}]},"ErrorEnvelope":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["UNAUTHORIZED","FORBIDDEN","NOT_FOUND","VALIDATION_FAILED","WARNINGS_NOT_ACKNOWLEDGED","INSUFFICIENT_BALANCE","RATE_LIMITED","IDEMPOTENCY_REQUIRED","INVALID_STATE","CONFLICT","INTERNAL"]},"message":{"type":"string"},"requestId":{"type":"string","example":"req_2f9c…"},"details":{}},"required":["code","message","requestId"]}}}}}}