openapi: 3.0.3 info: title: 'Emza Platform — Public API v1' description: 'Public REST API for Contract creation and Electronic Signature lifecycle. Bilingual (fa/en) errors. RFC 7807 problem+json envelope.' version: 1.0.0 servers: - url: 'https://elemza.com' tags: - name: Contracts description: '' - name: 'Digital certificates (PKI)' description: "\nIssuing a certificate is not something an API can do on somebody's behalf: the law wants an OTP\nthat person typed and a live face check they attended. So these endpoints do the part an API\nlegitimately can — start the enrolment, hand back a link for the person, and let the integrator\nfollow it to the end." - name: 'Form submissions — fill a form through the API (ADR-0029)' description: "\nSubmit-once semantics: no draft/resume, no edit-after-submit (FB SCOPE-CONTRACT rows 6/7).\nOwnership: every endpoint requires the form to BELONG to the token owner (v2 schema only) —\nthe integrator drives fills of their OWN forms; end users never hold tokens." - name: 'Forms — read-only access to v2 forms owned by the authenticated user.' description: "\nPhase 5 MVP: list + show + stats. Submissions endpoint will land in Phase 5b\nwith proper pagination + transformer." - name: 'Meta — health, identity, quota, cost' description: '' - name: Signers description: "\nSigner endpoints for a contract.\n\nPhase 1 = read-only (index + show). Resend + delete = Phase 2 (mutations).\n\nSpec: docs/api-master-plan/02-endpoint-surface.md (Signer endpoints)." - name: Templates description: "\nTemplate endpoints — read-only in v1.0. CRUD lands in v1.x.\n\nSpec: docs/api-master-plan/02-endpoint-surface.md (Template endpoints)." - name: Webhooks description: "\nOutbound webhook subscriptions. Customers create one or more endpoints, each\nfiltered by an array of event names. Deliveries are signed with HMAC-SHA256\nvia the secret returned at create-time (shown only once).\n\nScope: `webhooks:manage`." components: securitySchemes: default: type: http scheme: bearer description: 'Create your token in the dashboard at /dashboard/api-tokens. The plain token is shown once at creation. Format is {id}|{secret} — send it verbatim in the Authorization header, no pat_live_ prefix.' security: - default: [] paths: '/api/v1/contracts/{code}/dl': get: summary: 'Public signed-URL download.' operationId: publicSignedURLDownload description: "Validates Laravel's `signed` middleware. No Bearer token required —\nthe URL signature is the auth proof. The signature is generated by\n`downloadPdf()` after the bearer was verified, so this is functionally\nequivalent to a 24h time-bounded capability URL.\n\nRecords an `ApiRequestLog`-style entry via the underlying `ContractDownload`\naudit table (same trail as the user-panel signed-link download flow)." parameters: [] responses: 404: description: '' content: application/problem+json: schema: type: object example: type: 'https://docs.elemza.com/errors/not_found' status: 404 code: not_found title_fa: 'منبع یافت نشد' title_en: 'Resource not found' request_id: 01M0TB569B06B153MHVXBVGBW2 properties: type: type: string example: 'https://docs.elemza.com/errors/not_found' status: type: integer example: 404 code: type: string example: not_found title_fa: type: string example: 'منبع یافت نشد' title_en: type: string example: 'Resource not found' request_id: type: string example: 01M0TB569B06B153MHVXBVGBW2 tags: - Contracts parameters: - in: path name: code description: '16-char public code.' example: architecto required: true schema: type: string /api/v1/contracts: get: summary: 'List contracts' operationId: listContracts description: "Returns paginated contracts visible to caller. Org root sees own + descendants'\nnon-private contracts; non-root sees own + contracts where they're a signer.\n\nScope: `contracts:read`.\n\n**PII filter scope requirement:** filtering by `signer_mobile` or\n`signer_national_code` requires the additional `signers:read_pii` ability\non your token. Without it, those query parameters return\n**403 `scope_missing`**. This prevents a low-privilege `contracts:read`\ntoken from using the index as a confirmation oracle to check whether a\ngiven mobile/national-code signs any contract on the owner's tree.\n\nThe `*` wildcard scope also passes the check." parameters: - in: query name: status description: 'Filter by status: draft|waiting_signature|completed|voided|canceled.' example: waiting_signature required: false schema: type: string description: 'Filter by status: draft|waiting_signature|completed|voided|canceled.' example: waiting_signature - in: query name: signature_method description: 'Filter by signing method: electronic|digital.' example: digital required: false schema: type: string description: 'Filter by signing method: electronic|digital.' example: digital - in: query name: created_after description: 'ISO 8601 datetime.' example: '2026-01-01T00:00:00Z' required: false schema: type: string description: 'ISO 8601 datetime.' example: '2026-01-01T00:00:00Z' - in: query name: created_before description: 'ISO 8601 datetime.' example: architecto required: false schema: type: string description: 'ISO 8601 datetime.' example: architecto - in: query name: signer_mobile description: 'Iran mobile regex 09XXXXXXXXX. **Requires `signers:read_pii` scope.**' example: '09121234567' required: false schema: type: string description: 'Iran mobile regex 09XXXXXXXXX. **Requires `signers:read_pii` scope.**' example: '09121234567' - in: query name: signer_national_code description: '10-digit Iran national code. **Requires `signers:read_pii` scope.**' example: architecto required: false schema: type: string description: '10-digit Iran national code. **Requires `signers:read_pii` scope.**' example: architecto - in: query name: template_id description: 'Filter by template.' example: 16 required: false schema: type: integer description: 'Filter by template.' example: 16 - in: query name: is_private description: 'Filter by privacy flag.' example: false required: false schema: type: boolean description: 'Filter by privacy flag.' example: false - in: query name: page description: 'Default 1.' example: 16 required: false schema: type: integer description: 'Default 1.' example: 16 - in: query name: per_page description: 'Max 100. Default 20.' example: 16 required: false schema: type: integer description: 'Max 100. Default 20.' example: 16 - in: query name: sort description: 'created_at | -created_at | completed_at | -completed_at. Default -created_at.' example: architecto required: false schema: type: string description: 'created_at | -created_at | completed_at | -completed_at. Default -created_at.' example: architecto responses: 401: description: '' content: application/problem+json: schema: type: object example: type: 'https://docs.elemza.com/errors/token_invalid' status: 401 code: token_invalid title_fa: 'توکن نامعتبر است' title_en: 'Invalid token' request_id: 01M0TB56ECVSX0CKVWKG3R4TNJ properties: type: type: string example: 'https://docs.elemza.com/errors/token_invalid' status: type: integer example: 401 code: type: string example: token_invalid title_fa: type: string example: 'توکن نامعتبر است' title_en: type: string example: 'Invalid token' request_id: type: string example: 01M0TB56ECVSX0CKVWKG3R4TNJ 403: description: 'PII filter without scope' content: application/json: schema: type: object example: type: 'https://docs.elemza.com/errors/scope_missing' status: 403 code: scope_missing title_fa: 'این فیلتر نیاز به مجوز signers:read_pii دارد' title_en: 'This filter requires the signers:read_pii scope' request_id: 01KRH8JRC4Y855P10CYC1C0AYS properties: type: type: string example: 'https://docs.elemza.com/errors/scope_missing' status: type: integer example: 403 code: type: string example: scope_missing title_fa: type: string example: 'این فیلتر نیاز به مجوز signers:read_pii دارد' title_en: type: string example: 'This filter requires the signers:read_pii scope' request_id: type: string example: 01KRH8JRC4Y855P10CYC1C0AYS tags: - Contracts requestBody: required: false content: application/json: schema: type: object properties: status: type: string description: '' example: waiting_signature enum: - draft - waiting_signature - completed - voided - canceled nullable: true signature_method: type: string description: '' example: digital enum: - electronic - digital nullable: true created_after: type: string description: 'value یک تاریخ معتبر نیست.' example: '2026-08-24T20:25:15' nullable: true created_before: type: string description: 'value یک تاریخ معتبر نیست.' example: '2026-08-24T20:25:15' nullable: true signer_mobile: type: string description: 'Must match the regex /^09[0-9]{9}$/.' example: '09564255931' nullable: true signer_national_code: type: string description: 'value باید 10 کاراکتر باشد.' example: ikhwaykcmy nullable: true template_id: type: integer description: '' example: 16 nullable: true is_private: type: boolean description: '' example: true nullable: true page: type: integer description: 'value باید حداقل 1 باشد.' example: 22 nullable: true per_page: type: integer description: 'value باید حداقل 1 باشد. value نباید بیشتر از 100 باشد.' example: 7 nullable: true sort: type: string description: '' example: created_at enum: - created_at - '-created_at' - completed_at - '-completed_at' nullable: true post: summary: 'Create contract' operationId: createContract description: "Creates a new contract. Two modes:\n\n**One-shot** — multipart/form-data with `file` field + metadata. File is\npersisted to staging and `ProcessContractFilesJob` is dispatched. Optional\n`signers[]` are added by the same job after page rasterization completes.\nResponse is **202 Accepted**; poll `_links.processing` until `ready=true`.\n\n**Multi-step** — JSON without `file`. Creates a `draft` contract row only.\nCaller then issues `POST /contracts/{code}/files` to attach the document.\n\n### Signature placement — `template_id` + `template_enforcement`\n\nPassing `template_id` snapshots that template's signature slots onto the new\ncontract. `template_enforcement` decides what those slots MEAN to the signer:\n\n- `locked` — each slot is pinned: the signer cannot drag the signature off it,\n and cannot sign on a page the template never assigned them.\n **This is the default whenever `template_id` is present.**\n- `suggested` — the signature merely *starts* on the slot; the signer may move\n it anywhere, on any page.\n\n`strict` / `loose` are accepted as legacy aliases of `locked` / `suggested`\n(the spelling earlier revisions of this page advertised).\n\nIf the template pins slots on more than one page, send `all_pages_signature=true`\nas well — otherwise one signature on any allowed page completes the contract.\n\nVerify what was applied on the create response (`template_id`,\n`template_enforcement`) or in full via `GET /contracts/{code}` → `template_positions`.\n\nScope: `contracts:write`." parameters: [] responses: 422: description: 'template_id not reachable by the caller' content: application/json: schema: type: object example: type: 'https://docs.elemza.com/errors/template_not_accessible' status: 422 code: template_not_accessible title_fa: 'قالب انتخاب‌شده وجود ندارد یا در دسترس شما نیست' title_en: 'The selected template does not exist or is not accessible to you' request_id: 01KRH8JRC4Y855P10CYC1C0AYS properties: type: type: string example: 'https://docs.elemza.com/errors/template_not_accessible' status: type: integer example: 422 code: type: string example: template_not_accessible title_fa: type: string example: 'قالب انتخاب‌شده وجود ندارد یا در دسترس شما نیست' title_en: type: string example: 'The selected template does not exist or is not accessible to you' request_id: type: string example: 01KRH8JRC4Y855P10CYC1C0AYS tags: - Contracts requestBody: required: true content: multipart/form-data: schema: type: object properties: title: type: string description: 'Contract title (1..120 chars).' example: 'قرارداد بیمه شخص ثالث' signature_method: type: string description: "electronic|digital. Default electronic. `digital` = PADES\n signature via the Pendar CA cloud-HSM. Requires the PKI feature to be enabled for\n your account (pre-flight via `GET /me` → `data.capabilities.pki`), otherwise\n **403 `pki_not_available`**. The method is contract-wide (all signers sign the same\n way) and IMMUTABLE after creation — to change it, cancel and recreate (ADR-0009)." example: architecto all_pages_signature: type: boolean description: 'Default false. When true, every page must carry the signature element.' example: false is_private: type: boolean description: 'Default false. When true, hides contract from org-ancestors.' example: false template_id: type: integer description: "Optional — must be owned by the caller or shared from the org root;\n anything else is rejected with 422 `template_not_accessible` (never silently dropped).\n List valid ids via `GET /api/v1/templates`." example: 26 template_enforcement: type: string description: "locked|suggested (legacy aliases: strict|loose).\n Defaults to `locked` when `template_id` is set." example: locked redirect_url: type: string description: "HTTPS URL to send the signer back to after they finish signing.\n They see our confirmation first, then return automatically. Without it they stay on our\n confirmation page." example: 'https://partner.example/contracts/42/signed' file: type: string format: binary description: 'PDF or PNG/JPG, max 4MB (multipart/form-data only).' source: type: object description: "Optional document source instead of `file`. Currently only\n `{type: \"form_submission\", code: \"ABCD1234EFGH5678\"}` — uses the rendered PDF of one\n of YOUR form submissions as this contract's document (the فرم → PDF → امضا chain,\n works with both signature methods). 409 `not_ready` if the submission PDF is still\n rendering; 422 `not_applicable` for online-renderer forms (no PDF artifact)." example: [] properties: { } signers: type: array description: 'Optional inline signers (one-shot). Same shape as POST /contracts/{code}/signers.' example: - architecto items: type: string required: - title '/api/v1/contracts/{code}': get: summary: 'Get contract' operationId: getContract description: "Returns full contract shape with embedded signers, per-page geometry,\nand template positions snapshot.\n\nScope: `contracts:read`." parameters: [] responses: 401: description: '' content: application/problem+json: schema: type: object example: type: 'https://docs.elemza.com/errors/token_invalid' status: 401 code: token_invalid title_fa: 'توکن نامعتبر است' title_en: 'Invalid token' request_id: 01M0TB56EQYEJPZ984WW270GM5 properties: type: type: string example: 'https://docs.elemza.com/errors/token_invalid' status: type: integer example: 401 code: type: string example: token_invalid title_fa: type: string example: 'توکن نامعتبر است' title_en: type: string example: 'Invalid token' request_id: type: string example: 01M0TB56EQYEJPZ984WW270GM5 tags: - Contracts delete: summary: 'Cancel contract' operationId: cancelContract description: "Cancels a contract in `draft` or `waiting_signature` state. Refunds the\nbilled amount to the original wallet (100% if no signer signed, 50% if any\nShahkar was consumed). Deletes contract files from storage. Waiting signers\nreceive an SMS notifying them the contract was canceled.\n\nScope: `contracts:write`." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: code: ABC1234567XYZ890 status: canceled canceled_at: '2026-05-11T10:00:00.000Z' refund: amount: 60000 currency: IRT properties: data: type: object properties: code: type: string example: ABC1234567XYZ890 status: type: string example: canceled canceled_at: type: string example: '2026-05-11T10:00:00.000Z' refund: type: object properties: amount: type: integer example: 60000 currency: type: string example: IRT tags: - Contracts parameters: - in: path name: code description: '16-char public code (A-Z, 0-9).' example: ABC1234567XYZ890 required: true schema: type: string '/api/v1/contracts/{code}/pdf': get: summary: 'Download signed PDF' operationId: downloadSignedPDF description: "Returns the final signed PDF in one of three formats (Phase 3):\n\n- `format=redirect` (default) — `302 Found` to a signed URL (24h TTL).\n The signed URL points at the unauthenticated `/api/v1/contracts/{code}/dl`\n route, which validates the signature and streams the file. Use this when\n embedding in `` for partner-facing UIs.\n- `format=url` — JSON envelope with `url`, `expires_at`, `filename`, `size_bytes`,\n `checksum_sha256`. Use this when the API client wants to hand the URL\n to another system, embed in email, or pre-flight before downloading.\n- `format=stream` — streams the PDF inline (consumes API egress). Use when\n the caller is a backend service that wants the bytes in one round-trip.\n\nScope: `contracts:read`." parameters: - in: query name: format description: 'redirect|url|stream. Default redirect.' example: url required: false schema: type: string description: 'redirect|url|stream. Default redirect.' example: url - in: query name: ttl description: 'Signed URL lifetime in seconds (60..86400). Default 86400. Ignored when `format=stream`.' example: 16 required: false schema: type: integer description: 'Signed URL lifetime in seconds (60..86400). Default 86400. Ignored when `format=stream`.' example: 16 responses: 404: description: '' content: application/problem+json: schema: type: object example: type: 'https://docs.elemza.com/errors/not_found' status: 404 code: not_found title_fa: 'منبع یافت نشد' title_en: 'Resource not found' request_id: 01M0TB56F7FRJNS78ATMT67JVA properties: type: type: string example: 'https://docs.elemza.com/errors/not_found' status: type: integer example: 404 code: type: string example: not_found title_fa: type: string example: 'منبع یافت نشد' title_en: type: string example: 'Resource not found' request_id: type: string example: 01M0TB56F7FRJNS78ATMT67JVA tags: - Contracts parameters: - in: path name: code description: '16-char public code.' example: architecto required: true schema: type: string '/api/v1/contracts/{code}/processing': get: summary: 'Poll processing status' operationId: pollProcessingStatus description: "Lightweight (<200 bytes) status of the rasterization + signer-creation job.\nReads `contract:progress:{id}` cache key directly — no DB hit.\n\n**Step values:** `files`, `signers`, `sms`, `done`, `error`.\n\nReturns `ready=true` when `step=done`. If progress key is gone (TTL expired)\nand contract has signers + pages, falls back to `ready=true` (job finished\nearlier and key was reaped).\n\nScope: `contracts:read`." parameters: [] responses: 404: description: '' content: application/problem+json: schema: type: object example: type: 'https://docs.elemza.com/errors/not_found' status: 404 code: not_found title_fa: 'منبع یافت نشد' title_en: 'Resource not found' request_id: 01M0TB56FMBZKHZN0ZY16GMJAC properties: type: type: string example: 'https://docs.elemza.com/errors/not_found' status: type: integer example: 404 code: type: string example: not_found title_fa: type: string example: 'منبع یافت نشد' title_en: type: string example: 'Resource not found' request_id: type: string example: 01M0TB56FMBZKHZN0ZY16GMJAC tags: - Contracts parameters: - in: path name: code description: '16-char contract code.' example: architecto required: true schema: type: string '/api/v1/contracts/{code}/files': post: summary: 'Upload contract file(s) (two-step flow)' operationId: uploadContractFilestwoStepFlow description: "Attaches a file to an existing **draft** contract that has no pages yet.\nSame async pipeline as `POST /contracts` one-shot: stages bytes, dispatches\n`ProcessContractFilesJob`, returns 202 with the `processing` link.\n\nScope: `contracts:write`." parameters: [] responses: { } tags: - Contracts requestBody: required: true content: multipart/form-data: schema: type: object properties: file: type: string format: binary description: 'PDF or PNG/JPG, max 4MB (multipart/form-data).' required: - file parameters: - in: path name: code description: '16-char contract code.' example: architecto required: true schema: type: string /api/v1/certificates: get: summary: 'Certificate status for one of your people' operationId: certificateStatusForOneOfYourPeople description: "Answers \"can this person sign digitally yet?\" for somebody you have enrolled.\n\n**Privacy** — this is not a lookup service for arbitrary national codes. It only answers for\npeople you yourself started an enrolment for; anyone else returns 404, so the endpoint cannot\nbe used to discover whether a given Iranian holds a certificate.\n\nScope required: `certificates:read`." parameters: - in: query name: national_code description: "The person's national code." example: '0010350829' required: true schema: type: string description: "The person's national code." example: '0010350829' responses: 200: description: '' content: application/json: schema: oneOf: - description: 'Active certificate' type: object example: data: national_code: '0010350829' status: active valid_from: '2026-07-30T09:12:00.000Z' valid_until: '2027-07-30T09:12:00.000Z' mode: live properties: data: type: object properties: national_code: type: string example: '0010350829' status: type: string example: active valid_from: type: string example: '2026-07-30T09:12:00.000Z' valid_until: type: string example: '2027-07-30T09:12:00.000Z' mode: type: string example: live - description: 'Enrolment still in progress' type: object example: data: national_code: '0010350829' status: pending_ekyc valid_from: null valid_until: null mode: live properties: data: type: object properties: national_code: type: string example: '0010350829' status: type: string example: pending_ekyc valid_from: type: string example: null nullable: true valid_until: type: string example: null nullable: true mode: type: string example: live tags: - 'Digital certificates (PKI)' '/api/v1/certificates/{id}': get: summary: 'One enrolment request' operationId: oneEnrolmentRequest description: "The state of a single enrolment you started, by the `request_id` you received.\n\nScope required: `certificates:read`." parameters: [] responses: 401: description: '' content: application/problem+json: schema: type: object example: type: 'https://docs.elemza.com/errors/token_invalid' status: 401 code: token_invalid title_fa: 'توکن نامعتبر است' title_en: 'Invalid token' request_id: 01M0TB56P8HCD9VKZECE1SNVS5 properties: type: type: string example: 'https://docs.elemza.com/errors/token_invalid' status: type: integer example: 401 code: type: string example: token_invalid title_fa: type: string example: 'توکن نامعتبر است' title_en: type: string example: 'Invalid token' request_id: type: string example: 01M0TB56P8HCD9VKZECE1SNVS5 tags: - 'Digital certificates (PKI)' parameters: - in: path name: id description: 'The `request_id` from the onboarding call.' example: 01jqk8x2m4n6p8r0s2t4v6w8y0 required: true schema: type: string /api/v1/certificates/onboarding: post: summary: 'Start a certificate enrolment' operationId: startACertificateEnrolment description: "Creates a short-lived, single-use link for one of your users to obtain a digital certificate.\nSend them to `onboarding_url`; they complete the SMS code, the identity form and the video\ncheck on our pages, and you learn the outcome from `certificate.issued` / `certificate.failed`\n(or by polling `GET /api/v1/certificates/{request_id}`).\n\n**Who pays** — `pay_by: \"issuer\"` charges YOUR wallet for the one-time issuance fee right now\nand refunds it automatically if no certificate is ever issued. `pay_by: \"subject\"` (default)\nlets the person pay on the page.\n\n**Idempotency** — while an enrolment for the same national code is still open you get the SAME\nlink back, not a second one. Pendar treats an open enrolment as a single order.\n\nScope required: `certificates:write`." parameters: [] responses: 201: description: 'Link created' content: application/json: schema: type: object example: data: request_id: 01jqk8x2m4n6p8r0s2t4v6w8y0 onboarding_url: 'https://elemza.com/pki/onboarding/01jqk8x2m4n6p8r0s2t4v6w8y0?expires=...&signature=...' expires_at: '2026-07-30T12:00:00.000Z' status: pending pay_by: issuer mode: live properties: data: type: object properties: request_id: type: string example: 01jqk8x2m4n6p8r0s2t4v6w8y0 onboarding_url: type: string example: 'https://elemza.com/pki/onboarding/01jqk8x2m4n6p8r0s2t4v6w8y0?expires=...&signature=...' expires_at: type: string example: '2026-07-30T12:00:00.000Z' status: type: string example: pending pay_by: type: string example: issuer mode: type: string example: live tags: - 'Digital certificates (PKI)' requestBody: required: true content: application/json: schema: type: object properties: national_code: type: string description: "The person's 10-digit national code." example: '0010350829' mobile: type: string description: 'Their mobile, 09xxxxxxxxx.' example: '09121234567' first_name: type: string description: 'Optional, prefills the form.' example: علی last_name: type: string description: 'Optional, prefills the form.' example: رضایی pay_by: type: string description: '`issuer` or `subject` (default).' example: issuer redirect_url: type: string description: 'HTTPS URL to send the person back to when they finish.' example: 'https://partner.example/done' ttl_minutes: type: integer description: 'Link lifetime, 5–1440 (default 60).' example: 120 required: - national_code - mobile '/api/v1/form-submissions/{code}/dl': get: summary: "Public signed-URL download twin (no Bearer — the URL signature is the auth proof,\nminted by downloadPdf() after the bearer was verified). Mirrors contracts /dl." operationId: publicSignedURLDownloadTwinnoBearerTheURLSignatureIsTheAuthProofMintedByDownloadPdfAfterTheBearerWasVerifiedMirrorsContractsdl description: '' parameters: [] responses: 404: description: '' content: application/problem+json: schema: type: object example: type: 'https://docs.elemza.com/errors/not_found' status: 404 code: not_found title_fa: 'منبع یافت نشد' title_en: 'Resource not found' request_id: 01M0TB56B2TW4QKAHSPA045ACY properties: type: type: string example: 'https://docs.elemza.com/errors/not_found' status: type: integer example: 404 code: type: string example: not_found title_fa: type: string example: 'منبع یافت نشد' title_en: type: string example: 'Resource not found' request_id: type: string example: 01M0TB56B2TW4QKAHSPA045ACY tags: - 'Form submissions — fill a form through the API (ADR-0029)' parameters: - in: path name: code description: '16-char submission code.' example: architecto required: true schema: type: string '/api/v1/form-submissions/{code}/files/{field}/dl': get: summary: "Public signed-URL twin for a single field file (no Bearer — the signature is the auth\nproof, minted by downloadFieldFile() only after the bearer was verified)." operationId: publicSignedURLTwinForASingleFieldFilenoBearerTheSignatureIsTheAuthProofMintedByDownloadFieldFileOnlyAfterTheBearerWasVerified description: '' parameters: [] responses: 404: description: '' content: application/problem+json: schema: type: object example: type: 'https://docs.elemza.com/errors/not_found' status: 404 code: not_found title_fa: 'منبع یافت نشد' title_en: 'Resource not found' request_id: 01M0TB56BDPVRXEKC0F5PRAYFA properties: type: type: string example: 'https://docs.elemza.com/errors/not_found' status: type: integer example: 404 code: type: string example: not_found title_fa: type: string example: 'منبع یافت نشد' title_en: type: string example: 'Resource not found' request_id: type: string example: 01M0TB56BDPVRXEKC0F5PRAYFA tags: - 'Form submissions — fill a form through the API (ADR-0029)' parameters: - in: path name: code description: '16-char submission code.' example: architecto required: true schema: type: string - in: path name: field description: "The field's technical `name`." example: architecto required: true schema: type: string '/api/v1/forms/{slug}/submissions': get: summary: 'List submissions' operationId: listSubmissions description: 'Paginated, newest first. Scope: `forms:read`.' parameters: - in: query name: status description: draft|pending_payment|submitted|processing|completed|signed. example: architecto required: false schema: type: string description: draft|pending_payment|submitted|processing|completed|signed. example: architecto - in: query name: submitted_after description: 'ISO 8601.' example: architecto required: false schema: type: string description: 'ISO 8601.' example: architecto - in: query name: submitted_before description: 'ISO 8601.' example: architecto required: false schema: type: string description: 'ISO 8601.' example: architecto - in: query name: has_contract description: 'Only submissions that bridged to a contract.' example: false required: false schema: type: boolean description: 'Only submissions that bridged to a contract.' example: false - in: query name: per_page description: 'Max 100, default 20.' example: 16 required: false schema: type: integer description: 'Max 100, default 20.' example: 16 responses: 401: description: '' content: application/problem+json: schema: type: object example: type: 'https://docs.elemza.com/errors/token_invalid' status: 401 code: token_invalid title_fa: 'توکن نامعتبر است' title_en: 'Invalid token' request_id: 01M0TB56K1MBR6KBMRYXPDZBE3 properties: type: type: string example: 'https://docs.elemza.com/errors/token_invalid' status: type: integer example: 401 code: type: string example: token_invalid title_fa: type: string example: 'توکن نامعتبر است' title_en: type: string example: 'Invalid token' request_id: type: string example: 01M0TB56K1MBR6KBMRYXPDZBE3 tags: - 'Form submissions — fill a form through the API (ADR-0029)' requestBody: required: false content: application/json: schema: type: object properties: status: type: string description: '' example: submitted enum: - draft - pending_payment - submitted - processing - completed - signed nullable: true submitted_after: type: string description: 'value یک تاریخ معتبر نیست.' example: '2026-08-24T20:25:15' nullable: true submitted_before: type: string description: 'value یک تاریخ معتبر نیست.' example: '2026-08-24T20:25:15' nullable: true has_contract: type: boolean description: '' example: true nullable: true page: type: integer description: 'value باید حداقل 1 باشد.' example: 16 nullable: true per_page: type: integer description: 'value باید حداقل 1 باشد. value نباید بیشتر از 100 باشد.' example: 22 nullable: true post: summary: 'Submit a form (fill via API)' operationId: submitAFormfillViaAPI description: "Runs the EXACT same pipeline as the public web renderer: schema validation,\nvisibility filtering, formula computation, trust scoring, the signature bridge\n(`requires_signature` forms auto-create a contract — response embeds it with per-signer\n`signing_url`s), image-overlay PDF rendering (queued), and owner notifications.\n\nScope: `forms:write` + `Idempotency-Key` supported.\n\nFields hidden by visibility conditions are stripped server-side. `query_shahkar` /\n`query_legal_entity` field values must come from `POST /forms/{slug}/verify-field`\n(same request IP) or their matched=true claim is stripped as a forgery.\n\n`file` / `image` fields take EITHER of two shapes, both real:\n - the `path` returned by `POST /forms/{slug}/uploads` — preferred, stores the bytes on\n disk exactly as the web fill flow does, and is readable back via the `/files/{field}`\n endpoint;\n - an inline `data:;base64,…` URI, which is also what a drawn `signature` is.\nBoth are magic-byte validated and size-capped server-side. (This docblock claimed\nuploads were \"not supported in v1 (mirrors the web fill flow)\" until 2026-08-14 — the\nsecond half had been untrue since 2026-06-22, when the web renderer got real uploads,\nand the first half was never enforced by any code: base64 always passed.)" parameters: [] responses: { } tags: - 'Form submissions — fill a form through the API (ADR-0029)' requestBody: required: true content: application/json: schema: type: object properties: data: type: object description: 'Map of field name → value.' example: first_name: علی properties: { } required: - data parameters: - in: path name: slug description: "The form's slug." example: architecto required: true schema: type: string '/api/v1/forms/{slug}/submissions/{code}': get: summary: 'Get one submission' operationId: getOneSubmission description: 'Scope: `forms:read`.' parameters: [] responses: 404: description: '' content: application/problem+json: schema: type: object example: type: 'https://docs.elemza.com/errors/not_found' status: 404 code: not_found title_fa: 'منبع یافت نشد' title_en: 'Resource not found' request_id: 01M0TB56KHGP7765916R7C299G properties: type: type: string example: 'https://docs.elemza.com/errors/not_found' status: type: integer example: 404 code: type: string example: not_found title_fa: type: string example: 'منبع یافت نشد' title_en: type: string example: 'Resource not found' request_id: type: string example: 01M0TB56KHGP7765916R7C299G tags: - 'Form submissions — fill a form through the API (ADR-0029)' parameters: - in: path name: slug description: "The form's slug." example: architecto required: true schema: type: string - in: path name: code description: '16-char submission code.' example: architecto required: true schema: type: string '/api/v1/forms/{slug}/submissions/{code}/pdf': get: summary: 'Download submission PDF' operationId: downloadSubmissionPDF description: "Image-overlay forms render a final PDF per submission (queued at submit). Same\nsigned-URL family as the contracts PDF endpoint:\n\n- `format=redirect` (default) — 302 to a signed URL (anon-safe, forwardable)\n- `format=url` — JSON envelope with url/expiry/checksum\n- `format=stream` — bytes in this response\n\nScope: `forms:read`." parameters: - in: query name: format description: 'redirect|url|stream. Default redirect.' example: architecto required: false schema: type: string description: 'redirect|url|stream. Default redirect.' example: architecto - in: query name: ttl description: 'Signed URL lifetime seconds (60..86400). Default 86400.' example: 16 required: false schema: type: integer description: 'Signed URL lifetime seconds (60..86400). Default 86400.' example: 16 responses: 404: description: '' content: application/problem+json: schema: type: object example: type: 'https://docs.elemza.com/errors/not_found' status: 404 code: not_found title_fa: 'منبع یافت نشد' title_en: 'Resource not found' request_id: 01M0TB56M1X2AGEBJVV6DPVMAM properties: type: type: string example: 'https://docs.elemza.com/errors/not_found' status: type: integer example: 404 code: type: string example: not_found title_fa: type: string example: 'منبع یافت نشد' title_en: type: string example: 'Resource not found' request_id: type: string example: 01M0TB56M1X2AGEBJVV6DPVMAM tags: - 'Form submissions — fill a form through the API (ADR-0029)' parameters: - in: path name: slug description: "The form's slug." example: architecto required: true schema: type: string - in: path name: code description: '16-char submission code.' example: architecto required: true schema: type: string '/api/v1/forms/{slug}/submissions/{code}/files/{field}': get: summary: 'Download one uploaded field file' operationId: downloadOneUploadedFieldFile description: "Serves whatever the submission actually holds for `{field}`: a stored `forms/…` path,\nOR an inline `data:…;base64,…` URI. Both shapes are real and both are served — the\ndata-URI is what a drawn signature is, and what API callers necessarily wrote before\nan uploads endpoint existed, so refusing it would orphan real historical data.\n\nThe field's type is resolved from the submission's OWN schema snapshot, not the live\nform: editing a form must never change what an already-recorded submission is\nunderstood to contain.\n\nSame three formats as the submission PDF endpoint.\n\nScope: `forms:read`." parameters: - in: query name: format description: 'redirect|url|stream. Default redirect.' example: architecto required: false schema: type: string description: 'redirect|url|stream. Default redirect.' example: architecto - in: query name: ttl description: 'Signed URL lifetime seconds (60..86400). Default 86400.' example: 16 required: false schema: type: integer description: 'Signed URL lifetime seconds (60..86400). Default 86400.' example: 16 responses: 404: description: '' content: application/problem+json: schema: type: object example: type: 'https://docs.elemza.com/errors/not_found' status: 404 code: not_found title_fa: 'منبع یافت نشد' title_en: 'Resource not found' request_id: 01M0TB56ME91KZHB7W3SNX629B properties: type: type: string example: 'https://docs.elemza.com/errors/not_found' status: type: integer example: 404 code: type: string example: not_found title_fa: type: string example: 'منبع یافت نشد' title_en: type: string example: 'Resource not found' request_id: type: string example: 01M0TB56ME91KZHB7W3SNX629B tags: - 'Form submissions — fill a form through the API (ADR-0029)' parameters: - in: path name: slug description: "The form's slug." example: architecto required: true schema: type: string - in: path name: code description: '16-char submission code.' example: architecto required: true schema: type: string - in: path name: field description: "The field's technical `name`." example: architecto required: true schema: type: string '/api/v1/forms/{slug}/uploads': post: summary: 'Upload a file for a `file` / `image` field' operationId: uploadAFileForAfileimageField description: "Multipart, ONE file per call. The returned `path` is what you then put in\n`data.{field}` on `POST /forms/{slug}/submissions`. The bytes land in the form's own\nfolder byte-identically to what the web fill flow produces, so the submissions table,\nthe answer sheet and the image-overlay PDF all render it the same way — an API fill\nand a browser fill become indistinguishable downstream, which is the whole point.\n\nSize and type come from the FIELD's own config (`max_size_kb` / `allowed_mimes` as the\ndesigner set them), clamped to the server ceiling, and the MIME is read from the\nfile's magic bytes — never from its name or extension.\n\n`signature` is deliberately not accepted here: it is drawn, and the canvas yields a\n`data:image/png;base64,…` value that goes straight into `data`.\n\nScope: `forms:write` + `Idempotency-Key` supported." parameters: [] responses: { } tags: - 'Form submissions — fill a form through the API (ADR-0029)' requestBody: required: true content: multipart/form-data: schema: type: object properties: field: type: string description: "The target field's technical `name` (see `/schema`)." example: national_card file: type: string format: binary description: 'The file itself — multipart/form-data.' required: - field - file parameters: - in: path name: slug description: "The form's slug." example: architecto required: true schema: type: string /api/v1/forms: get: summary: "List forms (v2 only — schema_version='2.0')" operationId: listFormsv2OnlySchemaVersion20 description: "Paginated list of v2-schema forms owned by the calling user, most recent\nfirst. Form Builder v1 records are excluded — use the dashboard for those.\nReturns the standard `data` + `meta` + `links` envelope, consistent with the\nother list endpoints (API-G1, audit 2026-06 — previously a silent limit(100)).\n\nScope required: `forms:read`." parameters: - in: query name: page description: 'Page number. Default 1.' example: 16 required: false schema: type: integer description: 'Page number. Default 1.' example: 16 - in: query name: per_page description: 'Items per page. Max 100. Default 50.' example: 16 required: false schema: type: integer description: 'Items per page. Max 100. Default 50.' example: 16 responses: 401: description: '' content: application/problem+json: schema: type: object example: type: 'https://docs.elemza.com/errors/token_invalid' status: 401 code: token_invalid title_fa: 'توکن نامعتبر است' title_en: 'Invalid token' request_id: 01M0TB56HSWV85A9A6QE19BEB7 properties: type: type: string example: 'https://docs.elemza.com/errors/token_invalid' status: type: integer example: 401 code: type: string example: token_invalid title_fa: type: string example: 'توکن نامعتبر است' title_en: type: string example: 'Invalid token' request_id: type: string example: 01M0TB56HSWV85A9A6QE19BEB7 tags: - 'Forms — read-only access to v2 forms owned by the authenticated user.' '/api/v1/forms/{slug}': get: summary: 'Show one form (metadata only — schema available via dedicated /schema endpoint in future)' operationId: showOneFormmetadataOnlySchemaAvailableViaDedicatedschemaEndpointInFuture description: 'Scope required: `forms:read`.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: id: 142 slug: frm-e7465df28dd45785 title: 'قرارداد همکاری' status: published renderer_type: online submission_count: 23 views_count: 187 created_at: '2026-05-10T14:32:00.000Z' properties: data: type: object properties: id: type: integer example: 142 slug: type: string example: frm-e7465df28dd45785 title: type: string example: 'قرارداد همکاری' status: type: string example: published renderer_type: type: string example: online submission_count: type: integer example: 23 views_count: type: integer example: 187 created_at: type: string example: '2026-05-10T14:32:00.000Z' 404: description: 'Not found or not owned' content: application/json: schema: type: object example: type: 'https://docs.elemza.com/errors/form_not_found' status: 404 code: form_not_found title_fa: 'فرم یافت نشد یا متعلق به شما نیست' title_en: 'Form not found or not owned by the caller' request_id: 01KRH8JRC4Y855P10CYC1C0AYS properties: type: type: string example: 'https://docs.elemza.com/errors/form_not_found' status: type: integer example: 404 code: type: string example: form_not_found title_fa: type: string example: 'فرم یافت نشد یا متعلق به شما نیست' title_en: type: string example: 'Form not found or not owned by the caller' request_id: type: string example: 01KRH8JRC4Y855P10CYC1C0AYS tags: - 'Forms — read-only access to v2 forms owned by the authenticated user.' parameters: - in: path name: slug description: "The form's public slug." example: frm-e7465df28dd45785 required: true schema: type: string '/api/v1/forms/{slug}/stats': get: summary: 'Form analytics (views/starts/submissions/conversion rates).' operationId: formAnalyticsviewsstartssubmissionsconversionRates description: "Aggregated funnel counters for one form: lifetime totals plus 7-day and 30-day windows, so\nyou can chart trends without additional queries.\n\nWARNING: Two engines count submissions here, and the response names both.\n\n- `submissions` — the lifetime counter, incremented once per accepted submission. It never\ndecreases, so a deleted submission stays counted, and it is written in exactly one place, so a\nrow created by any other path is invisible to it. **This is the v1 meaning and it does not\nchange without a version bump.** `submissions_recorded` is the same integer under a name that\nsays so.\n- `submissions_stored` — non-draft rows held right now. This is the number the owner sees in\ntheir panel, and it can differ from `submissions` in either direction.\n\n`abandons`, `completion_rate` and `abandon_rate` all derive from `submissions`, so the funnel\nadds up. `completion_rate_stored` is `null` when stored rows exceed recorded starts: the two\ncounters disagree, and a ratio across them would be drift rather than a measurement.\n\nAdded 2026-08-20 — `submissions_recorded`, `submissions_stored`, `completion_rate_stored`.\nPurely additive; no existing field changed meaning or value.\n\nScope required: `forms:read`." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: views: 187 starts: 41 abandons: 18 submissions: 23 submissions_recorded: 23 submissions_stored: 25 start_rate: 21.9 completion_rate: 56.1 completion_rate_stored: 61.0 abandon_rate: 43.9 last_submission_at: '2026-08-19T11:02:44+00:00' submissions_last_7d: 4 submissions_last_30d: 21 properties: data: type: object properties: views: type: integer example: 187 starts: type: integer example: 41 abandons: type: integer example: 18 submissions: type: integer example: 23 submissions_recorded: type: integer example: 23 submissions_stored: type: integer example: 25 start_rate: type: number example: 21.9 completion_rate: type: number example: 56.1 completion_rate_stored: type: number example: 61.0 abandon_rate: type: number example: 43.9 last_submission_at: type: string example: '2026-08-19T11:02:44+00:00' submissions_last_7d: type: integer example: 4 submissions_last_30d: type: integer example: 21 404: description: 'Not found or not owned' content: application/json: schema: type: object example: type: 'https://docs.elemza.com/errors/form_not_found' status: 404 code: form_not_found title_fa: 'فرم یافت نشد یا متعلق به شما نیست' title_en: 'Form not found or not owned by the caller' request_id: 01KRH8JRC4Y855P10CYC1C0AYS properties: type: type: string example: 'https://docs.elemza.com/errors/form_not_found' status: type: integer example: 404 code: type: string example: form_not_found title_fa: type: string example: 'فرم یافت نشد یا متعلق به شما نیست' title_en: type: string example: 'Form not found or not owned by the caller' request_id: type: string example: 01KRH8JRC4Y855P10CYC1C0AYS tags: - 'Forms — read-only access to v2 forms owned by the authenticated user.' parameters: - in: path name: slug description: "The form's public slug." example: frm-e7465df28dd45785 required: true schema: type: string '/api/v1/forms/{slug}/schema': get: summary: 'Form schema (fillable-field catalog)' operationId: formSchemafillableFieldCatalog description: "Everything an integrator needs to build their own fill UI and construct a valid\n`POST /forms/{slug}/submissions` payload: field names, types, labels, required flags,\noptions, pages, visibility conditions, and — per field — the SHAPE its value must take\n(`value_format`).\n\n`supported` used to report `false` for file/image \"because the web fill flow does not\nsupport them either\". Both halves were wrong: the web renderer has had real uploads\nsince 2026-06-22, and nothing ever rejected an inline base64 value on the API side. It\nnow reports true for every fillable field, and `value_format` carries the real answer:\n\n - `upload_path_or_data_uri` — file/image: either the `path` from\n `POST /forms/{slug}/uploads`, or an inline `data:;base64,…`\n - `data_uri` — signature: what the canvas produces\n - `verify_field_result` — query fields: from `POST /forms/{slug}/verify-field`\n - `scalar` — everything else\n\n**`id` is the key to keep.** It is generated once and never changes — not when the\nfield is renamed, relabelled, reordered or moved. Key your local copy of a form by\n`id`, never by `name`: a client keyed by name cannot tell a RENAME apart from \"one\nfield deleted, one added\", and those call for opposite actions. `repeat.config.ref`\nand the `fill_flow` sections both reference fields by this same id.\n\n`pages` describes what each field's `page` points at, and is never empty.\n\n`fill_flow` is present only for `image_overlay` forms (null otherwise) and says\nwhich of the two presentations the owner chose: `document` draws fields at absolute\ncoordinates on a page bitmap, while `guided` turns them into ordered question cards\n— with `doc` saying whether the document is shown alongside. Only `guided` is\nreasonable to reproduce outside our own renderer.\n\nScope required: `forms:read`." parameters: [] responses: 200: description: 'An online form with one text field' content: application/json: schema: type: object example: data: form: slug: frm-e7465df28dd45785 title: 'قرارداد همکاری' version: 12 pages: - id: page_a1b2c3d4e5f60718 title: 'مشخصات فردی' description: null sort_order: 0 visibility_condition: null fill_flow: null fields: - id: fld_9a3f1c72e4b58d06 name: full_name type: text label: 'نام و نام خانوادگی' required: true is_input: true supported: true value_format: scalar page: page_a1b2c3d4e5f60718 help_text: null placeholder: null default_value: null options: null config: { } has_visibility_condition: false visibility_condition: null is_formula: false formula: null validations: [] sort_order: 0 properties: data: type: object properties: form: type: object properties: slug: type: string example: frm-e7465df28dd45785 title: type: string example: 'قرارداد همکاری' version: type: integer example: 12 pages: type: array example: - id: page_a1b2c3d4e5f60718 title: 'مشخصات فردی' description: null sort_order: 0 visibility_condition: null items: type: object properties: id: type: string example: page_a1b2c3d4e5f60718 title: type: string example: 'مشخصات فردی' description: type: string example: null nullable: true sort_order: type: integer example: 0 visibility_condition: type: string example: null nullable: true fill_flow: type: string example: null description: 'Image-overlay forms only (`null` otherwise): `style` is `document` or `guided`, `doc` is `live` or `hidden`, plus the ordered `sections` and any `unassigned` field ids.' fields: type: array example: - id: fld_9a3f1c72e4b58d06 name: full_name type: text label: 'نام و نام خانوادگی' required: true is_input: true supported: true value_format: scalar page: page_a1b2c3d4e5f60718 help_text: null placeholder: null default_value: null options: null config: [] has_visibility_condition: false visibility_condition: null is_formula: false formula: null validations: [] sort_order: 0 items: type: object properties: id: type: string example: fld_9a3f1c72e4b58d06 name: type: string example: full_name type: type: string example: text label: type: string example: 'نام و نام خانوادگی' required: type: boolean example: true is_input: type: boolean example: true supported: type: boolean example: true value_format: type: string example: scalar page: type: string example: page_a1b2c3d4e5f60718 help_text: type: string example: null nullable: true placeholder: type: string example: null nullable: true default_value: type: string example: null nullable: true options: type: string example: null nullable: true config: type: object properties: { } has_visibility_condition: type: boolean example: false visibility_condition: type: string example: null nullable: true is_formula: type: boolean example: false formula: type: string example: null nullable: true validations: type: array example: [] sort_order: type: integer example: 0 tags: - 'Forms — read-only access to v2 forms owned by the authenticated user.' parameters: - in: path name: slug description: "The form's public slug." example: architecto required: true schema: type: string '/api/v1/forms/{slug}/verify-field': post: summary: 'Verify a query field (Shahkar / legal entity)' operationId: verifyAQueryFieldShahkarLegalEntity description: "Runs the SAME server-side verification the web renderer's «استعلام» buttons run, and\nwrites the same server-side binding that `POST /forms/{slug}/submissions` checks — echo\nthe returned `value` back inside `data.{field}` when you submit, FROM THE SAME IP.\n\nThe form OWNER's daily Shahkar quota pays for the lookup (identical to web). Sandbox\ntokens short-circuit to `matched=true` without any provider call.\n\nScope required: `forms:write`." parameters: [] responses: { } tags: - 'Forms — read-only access to v2 forms owned by the authenticated user.' requestBody: required: true content: application/json: schema: type: object properties: field: type: string description: "The query field's name." example: architecto values: type: object description: 'Raw values of the referenced input fields.' example: mobile: '09121234567' national_code: '0010350829' properties: { } required: - field - values parameters: - in: path name: slug description: "The form's public slug." example: architecto required: true schema: type: string /api/v1/health: get: summary: 'Health check' operationId: healthCheck description: "Lightweight liveness probe. **No authentication required.**\nAlways returns 200 if the API process is up." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: ok time: '2026-05-11T11:53:29.399Z' version: v1.0.0 properties: status: type: string example: ok time: type: string example: '2026-05-11T11:53:29.399Z' version: type: string example: v1.0.0 tags: - 'Meta — health, identity, quota, cost' security: [] /api/v1/me: get: summary: 'Caller identity' operationId: callerIdentity description: "Returns information about the authenticated token, its owner user, and\nthe billing context (wallet balance, org root). Any valid token can call this —\nno specific scope required.\n\nUse the `token.mode` field to confirm whether your token is in **live** mode\n(charges your wallet, real KYC/SMS) or **test** sandbox mode (zero wallet\nimpact, mocked side effects). `wallet_balance` is always in tomans (IRR/10)." parameters: [] responses: 200: description: '' content: application/json: schema: oneOf: - description: 'Live token' type: object example: data: token: id: '42' name: production-server-2026 mode: live scopes: - 'contracts:read' - 'contracts:write' - 'webhooks:manage' ip_whitelist: - 203.0.113.5/32 expires_at: '2027-01-01T00:00:00.000Z' last_used_at: '2026-05-13T18:00:42.000Z' user: id: 1234 name: 'آرش بنائیان چمله' type: personal is_org_root: false org_root_id: null billing_actor_user_id: 1234 wallet_balance: 849310 properties: data: type: object properties: token: type: object properties: id: type: string example: '42' name: type: string example: production-server-2026 mode: type: string example: live description: 'Either `live` or `test`. Sandbox tokens never debit the wallet.' scopes: type: array example: - 'contracts:read' - 'contracts:write' - 'webhooks:manage' description: 'of granted abilities. `["*"]` means full access.' items: type: string ip_whitelist: type: array example: - 203.0.113.5/32 description: 'If non-empty, requests from other IPs are rejected with 403 `ip_not_allowed`. Five failures within 1h auto-revoke the token.' items: type: string expires_at: type: string example: '2027-01-01T00:00:00.000Z' last_used_at: type: string example: '2026-05-13T18:00:42.000Z' user: type: object properties: id: type: integer example: 1234 name: type: string example: 'آرش بنائیان چمله' type: type: string example: personal is_org_root: type: boolean example: false description: 'True when this user is the root of a Sub-Organization tree (can invite sub-users).' org_root_id: type: string example: null description: 'ID of the org root if this user is a member, else null.' billing_actor_user_id: type: integer example: 1234 description: 'The wallet that pays for this request. Same as `user.id` for solo users; may be `org_root_id` for org members with `auto_charge_from_parent=true`.' wallet_balance: type: integer example: 849310 description: 'Current wallet balance in tomans (Iranian Rials ÷ 10).' - description: 'Sandbox/test token' type: object example: data: token: id: '13' name: ci-tests-sandbox mode: test scopes: - '*' ip_whitelist: [] expires_at: null last_used_at: '2026-05-13T17:52:23.000Z' user: id: 1234 name: 'آرش بنائیان چمله' type: personal is_org_root: false org_root_id: null billing_actor_user_id: 1234 wallet_balance: 849310 properties: data: type: object properties: token: type: object properties: id: type: string example: '13' name: type: string example: ci-tests-sandbox mode: type: string example: test description: 'Either `live` or `test`. Sandbox tokens never debit the wallet.' scopes: type: array example: - '*' description: 'of granted abilities. `["*"]` means full access.' items: type: string ip_whitelist: type: array example: [] description: 'If non-empty, requests from other IPs are rejected with 403 `ip_not_allowed`. Five failures within 1h auto-revoke the token.' expires_at: type: string example: null nullable: true last_used_at: type: string example: '2026-05-13T17:52:23.000Z' user: type: object properties: id: type: integer example: 1234 name: type: string example: 'آرش بنائیان چمله' type: type: string example: personal is_org_root: type: boolean example: false description: 'True when this user is the root of a Sub-Organization tree (can invite sub-users).' org_root_id: type: string example: null description: 'ID of the org root if this user is a member, else null.' billing_actor_user_id: type: integer example: 1234 description: 'The wallet that pays for this request. Same as `user.id` for solo users; may be `org_root_id` for org members with `auto_charge_from_parent=true`.' wallet_balance: type: integer example: 849310 description: 'Current wallet balance in tomans (Iranian Rials ÷ 10).' tags: - 'Meta — health, identity, quota, cost' /api/v1/quota: get: summary: 'Current quota' operationId: currentQuota description: "Returns the caller's current-month signature quota usage + remaining,\npackage tier, and today's Shahkar verification credits.\n\n**Quota semantics:**\n- `free_quota` is the per-period allowance from the user's active package\n (Free=3 lifetime, Bronze=100/mo, Silver=300/mo, Gold=600/mo — verify against\n live `/admin/packages`, these are the 2026-06 tariff). Returns 0 if the package\n is expired or unset.\n- `used`/`remaining` may be FRACTIONAL: additional signers on one contract count\n as half a unit each (a 2-signer contract consumes 1.5). Whole values serialize\n as integers; a half consumes serializes as e.g. `1.5`.\n- `used` is total signature-units recorded since `package_started_at` (so\n pre-purchase usage doesn't deplete a freshly-bought plan). Free tier counts\n lifetime (no monthly reset).\n- `remaining = free_quota - used` for limited plans. **null** means\n genuinely unlimited (`signature_limit` IS NULL).\n- When `remaining` reaches 0, signatures still work — billed as overage from the\n wallet at `base × (1 − overage_discount)`; the OVERAGE discount is\n Free=0%, Bronze=5%, Silver=15%, Gold=25% (separate from, and smaller than, the\n 10/20/30% in-package discount encoded in the package price).\n\n**Shahkar credits** are separate from signature quota. Daily allowance\nby package (Free=3, Bronze=5, Silver=10, Gold=20). Once exhausted, the\nclient must purchase batch top-ups before further Shahkar verifications.\n\nScope required: `contracts:read`." parameters: [] responses: 200: description: '' content: application/json: schema: oneOf: - description: 'Bronze user mid-month' type: object example: data: service: electronic_signature period_start: '2026-05-01T00:00:00.000Z' period_end: '2026-05-31T23:59:59.999Z' package: slug: bronze name: برنزی free_quota: 30 used: 17 remaining: 13 shahkar: free_limit: 5 used_today: 2 free_remaining: 3 extra_credits: 0 extra_remaining: 0 allowed: true properties: data: type: object properties: service: type: string example: electronic_signature period_start: type: string example: '2026-05-01T00:00:00.000Z' period_end: type: string example: '2026-05-31T23:59:59.999Z' package: type: object properties: slug: type: string example: bronze name: type: string example: برنزی free_quota: type: integer example: 30 description: 'Monthly signature allowance from active package. Zero if package expired or none.' used: type: integer example: 17 description: 'Signatures recorded this period (since package_started_at).' remaining: type: integer example: 13 description: 'Signatures left this period. `null` means unlimited (Gold).' shahkar: type: object properties: free_limit: type: integer example: 5 used_today: type: integer example: 2 free_remaining: type: integer example: 3 extra_credits: type: integer example: 0 extra_remaining: type: integer example: 0 allowed: type: boolean example: true description: 'Whether the user may attempt a Shahkar verification right now. False = quota exhausted, must purchase batch top-up.' - description: 'Gold (unlimited)' type: object example: data: service: electronic_signature period_start: '2026-05-01T00:00:00.000Z' period_end: '2026-05-31T23:59:59.999Z' package: slug: gold name: طلایی free_quota: 0 used: 412 remaining: null shahkar: free_limit: 20 used_today: 4 free_remaining: 16 extra_credits: 0 extra_remaining: 0 allowed: true properties: data: type: object properties: service: type: string example: electronic_signature period_start: type: string example: '2026-05-01T00:00:00.000Z' period_end: type: string example: '2026-05-31T23:59:59.999Z' package: type: object properties: slug: type: string example: gold name: type: string example: طلایی free_quota: type: integer example: 0 description: 'Monthly signature allowance from active package. Zero if package expired or none.' used: type: integer example: 412 description: 'Signatures recorded this period (since package_started_at).' remaining: type: string example: null description: 'Signatures left this period. `null` means unlimited (Gold).' shahkar: type: object properties: free_limit: type: integer example: 20 used_today: type: integer example: 4 free_remaining: type: integer example: 16 extra_credits: type: integer example: 0 extra_remaining: type: integer example: 0 allowed: type: boolean example: true description: 'Whether the user may attempt a Shahkar verification right now. False = quota exhausted, must purchase batch top-up.' - description: 'No active package (pay-per-use)' type: object example: data: service: electronic_signature period_start: '2026-05-01T00:00:00.000Z' period_end: '2026-05-31T23:59:59.999Z' package: null free_quota: 0 used: 0 remaining: 0 shahkar: free_limit: 0 used_today: 0 free_remaining: 0 extra_credits: 0 extra_remaining: 0 allowed: false properties: data: type: object properties: service: type: string example: electronic_signature period_start: type: string example: '2026-05-01T00:00:00.000Z' period_end: type: string example: '2026-05-31T23:59:59.999Z' package: type: string example: null nullable: true free_quota: type: integer example: 0 description: 'Monthly signature allowance from active package. Zero if package expired or none.' used: type: integer example: 0 description: 'Signatures recorded this period (since package_started_at).' remaining: type: integer example: 0 description: 'Signatures left this period. `null` means unlimited (Gold).' shahkar: type: object properties: free_limit: type: integer example: 0 used_today: type: integer example: 0 free_remaining: type: integer example: 0 extra_credits: type: integer example: 0 extra_remaining: type: integer example: 0 allowed: type: boolean example: false description: 'Whether the user may attempt a Shahkar verification right now. False = quota exhausted, must purchase batch top-up.' tags: - 'Meta — health, identity, quota, cost' /api/v1/cost-estimate: get: summary: 'Cost estimate' operationId: costEstimate description: "Returns full cost breakdown for a hypothetical contract without committing.\nUse before calling `POST /contracts` so users can see \"X toman\" upfront.\n\nScope required: `contracts:read`." parameters: - in: query name: signers description: 'Number of signers (1-20).' example: 3 required: true schema: type: integer description: 'Number of signers (1-20).' example: 3 - in: query name: discount_code description: 'Optional discount code.' example: WELCOME10 required: false schema: type: string description: 'Optional discount code.' example: WELCOME10 responses: 401: description: '' content: application/problem+json: schema: type: object example: type: 'https://docs.elemza.com/errors/token_invalid' status: 401 code: token_invalid title_fa: 'توکن نامعتبر است' title_en: 'Invalid token' request_id: 01M0TB56CK30WTDC7J030ZXPFV properties: type: type: string example: 'https://docs.elemza.com/errors/token_invalid' status: type: integer example: 401 code: type: string example: token_invalid title_fa: type: string example: 'توکن نامعتبر است' title_en: type: string example: 'Invalid token' request_id: type: string example: 01M0TB56CK30WTDC7J030ZXPFV tags: - 'Meta — health, identity, quota, cost' '/api/v1/contracts/{code}/signers': get: summary: 'List signers' operationId: listSigners description: "Lists all signers on a contract. PII (mobile, national_code) is masked\nunless the caller's token has `signers:read_pii` scope.\n\nScope: `contracts:read`." parameters: [] responses: 404: description: '' content: application/problem+json: schema: type: object example: type: 'https://docs.elemza.com/errors/not_found' status: 404 code: not_found title_fa: 'منبع یافت نشد' title_en: 'Resource not found' request_id: 01M0TB56GKFB93AA71XDX16ZQ5 properties: type: type: string example: 'https://docs.elemza.com/errors/not_found' status: type: integer example: 404 code: type: string example: not_found title_fa: type: string example: 'منبع یافت نشد' title_en: type: string example: 'Resource not found' request_id: type: string example: 01M0TB56GKFB93AA71XDX16ZQ5 tags: - Signers post: summary: 'Add signers' operationId: addSigners description: "Adds one or more signers to a draft contract. Runs **mandatory** Shahkar\npre-validation (mobile ↔ national_code match) per signer in production.\nOn success, contract transitions to `waiting_signature` and SMS dispatch\njobs are queued for first-order signers.\n\nScope: `signers:write`." parameters: [] responses: { } tags: - Signers requestBody: required: true content: application/json: schema: type: object properties: signers: type: array description: 'Array of signer objects.' example: - architecto items: type: string required: - signers parameters: - in: path name: code description: '16-char contract code.' example: architecto required: true schema: type: string '/api/v1/contracts/{code}/signers/{slug}': get: summary: 'Get signer' operationId: getSigner description: "Detailed view of a single signer. PII masked unless token has `signers:read_pii`.\n\nScope: `contracts:read`." parameters: [] responses: 404: description: '' content: application/problem+json: schema: type: object example: type: 'https://docs.elemza.com/errors/not_found' status: 404 code: not_found title_fa: 'منبع یافت نشد' title_en: 'Resource not found' request_id: 01M0TB56GWSQ659J9SK71B68G7 properties: type: type: string example: 'https://docs.elemza.com/errors/not_found' status: type: integer example: 404 code: type: string example: not_found title_fa: type: string example: 'منبع یافت نشد' title_en: type: string example: 'Resource not found' request_id: type: string example: 01M0TB56GWSQ659J9SK71B68G7 tags: - Signers delete: summary: 'Remove signer' operationId: removeSigner description: "Removes a signer from a contract. Only allowed when:\n- Signer status is `waiting` (not yet signed)\n- Contract status is `draft` or `waiting_signature`\n\nScope: `signers:write`." parameters: [] responses: 204: description: '' content: application/json: schema: type: object example: { } properties: { } tags: - Signers parameters: - in: path name: code description: '16-char contract code.' example: architecto required: true schema: type: string - in: path name: slug description: 'Per-signer slug.' example: DEF456GHJ789KLM required: true schema: type: string '/api/v1/contracts/{code}/signers/{slug}/resend': post: summary: 'Resend signing link SMS' operationId: resendSigningLinkSMS description: "Re-sends the signing-link SMS to a waiting signer. Rate-limited to 3 per\n5 minutes per signer (returns 429 `rate_limit_exceeded`).\n\nScope: `signers:write`." parameters: [] responses: { } tags: - Signers parameters: - in: path name: code description: '16-char contract code.' example: architecto required: true schema: type: string - in: path name: slug description: 'Per-signer slug.' example: architecto required: true schema: type: string /api/v1/templates: get: summary: 'List templates' operationId: listTemplates description: "Lists contract templates the caller can use.\n\n`view_scope` options:\n- `self` — own templates only (default)\n- `shared_from_parent` — own + templates shared by org parent/root\n- `all_org` — entire org tree (org root only)\n\nScope: `templates:read`." parameters: - in: query name: view_scope description: 'self | shared_from_parent | all_org. Default self.' example: architecto required: false schema: type: string description: 'self | shared_from_parent | all_org. Default self.' example: architecto - in: query name: active description: 'Filter by active status.' example: false required: false schema: type: boolean description: 'Filter by active status.' example: false - in: query name: page description: '' example: 16 required: false schema: type: integer description: '' example: 16 - in: query name: per_page description: 'Max 100. Default 20.' example: 16 required: false schema: type: integer description: 'Max 100. Default 20.' example: 16 responses: 401: description: '' content: application/problem+json: schema: type: object example: type: 'https://docs.elemza.com/errors/token_invalid' status: 401 code: token_invalid title_fa: 'توکن نامعتبر است' title_en: 'Invalid token' request_id: 01M0TB56Q4V9KMNG657H9VRAJ5 properties: type: type: string example: 'https://docs.elemza.com/errors/token_invalid' status: type: integer example: 401 code: type: string example: token_invalid title_fa: type: string example: 'توکن نامعتبر است' title_en: type: string example: 'Invalid token' request_id: type: string example: 01M0TB56Q4V9KMNG657H9VRAJ5 tags: - Templates '/api/v1/templates/{id}': get: summary: 'Get template' operationId: getTemplate description: "Full template shape with embedded positions + page geometry + sample preview link.\n\nScope: `templates:read`." parameters: [] responses: 404: description: '' content: application/problem+json: schema: type: object example: type: 'https://docs.elemza.com/errors/not_found' status: 404 code: not_found title_fa: 'منبع یافت نشد' title_en: 'Resource not found' request_id: 01M0TB56QFAPZ7H9RXZN7YJPKJ properties: type: type: string example: 'https://docs.elemza.com/errors/not_found' status: type: integer example: 404 code: type: string example: not_found title_fa: type: string example: 'منبع یافت نشد' title_en: type: string example: 'Resource not found' request_id: type: string example: 01M0TB56QFAPZ7H9RXZN7YJPKJ tags: - Templates parameters: - in: path name: id description: 'The ID of the template.' example: architecto required: true schema: type: string - in: path name: template description: 'Template ID.' example: 16 required: true schema: type: integer '/api/v1/templates/{template}/preview/{page}': get: summary: 'Template page preview' operationId: templatePagePreview description: "**NOT IMPLEMENTED YET** — returns `501 preview_not_available`. The signed\npage-preview URL lands in a later phase (Storage::temporaryUrl). Documented\nas a stub so merchants do not code against a fake `url:null` 200.\n\nScope: `templates:read`." parameters: [] responses: 401: description: '' content: application/problem+json: schema: type: object example: type: 'https://docs.elemza.com/errors/token_invalid' status: 401 code: token_invalid title_fa: 'توکن نامعتبر است' title_en: 'Invalid token' request_id: 01M0TB56QR43AMCAN2EM1QGE91 properties: type: type: string example: 'https://docs.elemza.com/errors/token_invalid' status: type: integer example: 401 code: type: string example: token_invalid title_fa: type: string example: 'توکن نامعتبر است' title_en: type: string example: 'Invalid token' request_id: type: string example: 01M0TB56QR43AMCAN2EM1QGE91 501: description: 'Stub — not implemented' content: application/json: schema: type: object example: status: 501 code: preview_not_available properties: status: type: integer example: 501 code: type: string example: preview_not_available tags: - Templates parameters: - in: path name: template description: 'Template ID.' example: 16 required: true schema: type: integer - in: path name: page description: 'Page number (1-indexed).' example: 16 required: true schema: type: integer /api/v1/webhooks: get: summary: 'List webhook subscriptions' operationId: listWebhookSubscriptions description: '' parameters: - in: query name: active description: 'Filter by active state.' example: false required: false schema: type: boolean description: 'Filter by active state.' example: false - in: query name: page description: '' example: 16 required: false schema: type: integer description: '' example: 16 - in: query name: per_page description: 'Max 100.' example: 16 required: false schema: type: integer description: 'Max 100.' example: 16 responses: 401: description: '' content: application/problem+json: schema: type: object example: type: 'https://docs.elemza.com/errors/token_invalid' status: 401 code: token_invalid title_fa: 'توکن نامعتبر است' title_en: 'Invalid token' request_id: 01M0TB56SHQJ6EZQ4CSVZ2Y3EV properties: type: type: string example: 'https://docs.elemza.com/errors/token_invalid' status: type: integer example: 401 code: type: string example: token_invalid title_fa: type: string example: 'توکن نامعتبر است' title_en: type: string example: 'Invalid token' request_id: type: string example: 01M0TB56SHQJ6EZQ4CSVZ2Y3EV tags: - Webhooks requestBody: required: false content: application/json: schema: type: object properties: active: type: boolean description: '' example: true nullable: true page: type: integer description: 'value باید حداقل 1 باشد.' example: 16 nullable: true per_page: type: integer description: 'value باید حداقل 1 باشد. value نباید بیشتر از 100 باشد.' example: 22 nullable: true post: summary: 'Create webhook subscription' operationId: createWebhookSubscription description: "Registers an HTTPS endpoint to receive event deliveries. We POST each\nmatching event to your URL and sign it with HMAC-SHA256:\n\n X-Emza-Timestamp: \n X-Emza-Signature: sha256=\n\nCompare with `hash_equals`, and reject anything whose timestamp is more than 5 minutes old.\n\nNOTE: this docblock is published to customers by Scribe. Until 2026-07-31 it described a\nStripe-style `X-Emza-Signature: t=,v1=` header that this platform has never sent —\nsee App\\Services\\Webhook\\WebhookSigner, which is the only authority on the wire format, and\ntests/Feature/Sdk/SdkWebhookVerificationTest, which pins the two together.\n\n**The plain signing `secret` is returned ONCE — store it immediately.**\nSubsequent reads (`index`, `show`) omit the secret entirely. Use\n`PATCH /webhooks/{id}` with `rotate_secret=true` to issue a fresh secret\n(invalidates the previous one immediately).\n\n**URL validation (SSRF guard):** the `url` field is rejected with `422`\nif it fails any of:\n - Non-HTTPS scheme (`http://`, `ftp://`, `javascript:`, `file://`)\n - Loopback IPs (`127.0.0.0/8`, `::1`)\n - RFC 1918 private (`10/8`, `172.16/12`, `192.168/16`)\n - Link-local + cloud metadata (`169.254.0.0/16`, `100.100.100.200`)\n - IPv6 link-local (`fe80::/10`) or unique-local (`fc00::/7`)\n - Hostname suffix `.local`, `.internal`, `.private`, `.lan` or `localhost`\n - Hostname whose DNS resolves to any of the above\n\n**Allowed event names** (set in `events` array — use `[\"*\"]` for all):\n`contract.created`, `contract.processing.completed`, `contract.processing.failed`,\n`contract.signer.added`, `contract.signer.authenticated`, `contract.signer.signed`,\n`contract.signer.rejected`, `contract.completed`, `contract.canceled`,\n`contract.refunded`, `*` (wildcard).\n\nIf `*` is present alongside specific events, the specifics are dropped\n(the wildcard makes them redundant). Same event listed twice is deduped.\n\nScope: `webhooks:manage`. Idempotency-Key required (24h replay window)." parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: data: id: 01krh7z0e8a4n39gf2ary73xbj url: 'https://example.com/webhooks/emza' events: - contract.completed - contract.signer.signed active: true disabled_at: null failed_deliveries_count: 0 last_delivery_at: null last_delivery_status: null created_at: '2026-05-13T18:00:42.000Z' updated_at: '2026-05-13T18:00:42.000Z' secret: 35ada3b45aa00df901b74b2b483114315a0c21f7c096f17e0bb29ab7d907fe32 _warning: 'Save this secret now — it will NOT be shown again.' properties: data: type: object properties: id: type: string example: 01krh7z0e8a4n39gf2ary73xbj url: type: string example: 'https://example.com/webhooks/emza' events: type: array example: - contract.completed - contract.signer.signed items: type: string active: type: boolean example: true disabled_at: type: string example: null nullable: true failed_deliveries_count: type: integer example: 0 last_delivery_at: type: string example: null nullable: true last_delivery_status: type: string example: null nullable: true created_at: type: string example: '2026-05-13T18:00:42.000Z' updated_at: type: string example: '2026-05-13T18:00:42.000Z' secret: type: string example: 35ada3b45aa00df901b74b2b483114315a0c21f7c096f17e0bb29ab7d907fe32 _warning: type: string example: 'Save this secret now — it will NOT be shown again.' 422: description: 'SSRF blocked URL' content: application/json: schema: type: object example: type: 'https://docs.elemza.com/errors/validation' status: 422 code: validation title_fa: 'اعتبارسنجی ورودی شکست خورد' title_en: 'Request validation failed' errors: url: - 'آدرس‌های داخلی شبکه (localhost / *.local / *.internal) مجاز نیستند.' request_id: 01KRH8JRC4Y855P10CYC1C0AYS properties: type: type: string example: 'https://docs.elemza.com/errors/validation' status: type: integer example: 422 code: type: string example: validation title_fa: type: string example: 'اعتبارسنجی ورودی شکست خورد' title_en: type: string example: 'Request validation failed' errors: type: object properties: url: type: array example: - 'آدرس‌های داخلی شبکه (localhost / *.local / *.internal) مجاز نیستند.' items: type: string request_id: type: string example: 01KRH8JRC4Y855P10CYC1C0AYS tags: - Webhooks requestBody: required: true content: application/json: schema: type: object properties: url: type: string description: 'HTTPS endpoint to receive deliveries. Max 500 chars.' example: 'https://example.com/webhooks/emza' events: type: array description: 'Event names from the allowed list. Use `["*"]` for all.' example: - contract.completed - contract.signer.signed items: type: string active: type: boolean description: 'When false, subscription is paused (no deliveries). Default true.' example: false required: - url - events '/api/v1/webhooks/{id}': get: summary: 'Get webhook subscription' operationId: getWebhookSubscription description: '' parameters: [] responses: 401: description: '' content: application/problem+json: schema: type: object example: type: 'https://docs.elemza.com/errors/token_invalid' status: 401 code: token_invalid title_fa: 'توکن نامعتبر است' title_en: 'Invalid token' request_id: 01M0TB56SVEZ9C45TYK3QK1750 properties: type: type: string example: 'https://docs.elemza.com/errors/token_invalid' status: type: integer example: 401 code: type: string example: token_invalid title_fa: type: string example: 'توکن نامعتبر است' title_en: type: string example: 'Invalid token' request_id: type: string example: 01M0TB56SVEZ9C45TYK3QK1750 tags: - Webhooks patch: summary: 'Update webhook subscription' operationId: updateWebhookSubscription description: "Partial update — any field omitted is left unchanged. New `url` values\npass through the same SSRF guard as `POST /webhooks` (see Create endpoint).\n\nWhen `rotate_secret=true`, a fresh HMAC signing secret is generated and\nreturned in the response — the previous secret is invalidated immediately,\nso any in-flight or already-sent webhook deliveries signed with the old\nsecret will fail verification on the receiver side. Plan rotations during\na quiet period or pause via `active=false` first.\n\nScope: `webhooks:manage`. Idempotency-Key required (24h replay window)." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: id: 01krh7z0e8a4n39gf2ary73xbj url: 'https://example.com/webhooks/emza' events: - '*' active: false failed_deliveries_count: 2 last_delivery_at: '2026-05-13T17:30:00.000Z' last_delivery_status: delivered properties: data: type: object properties: id: type: string example: 01krh7z0e8a4n39gf2ary73xbj url: type: string example: 'https://example.com/webhooks/emza' events: type: array example: - '*' items: type: string active: type: boolean example: false failed_deliveries_count: type: integer example: 2 last_delivery_at: type: string example: '2026-05-13T17:30:00.000Z' last_delivery_status: type: string example: delivered tags: - Webhooks requestBody: required: false content: application/json: schema: type: object properties: url: type: string description: 'New endpoint URL (subject to same SSRF guard as create).' example: 'http://www.bailey.biz/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html' events: type: array description: 'Replaces the entire events list. Same allowed values as create.' example: - architecto items: type: string active: type: boolean description: 'Toggle the subscription on/off without deleting it.' example: false rotate_secret: type: boolean description: 'When true, issues a fresh secret (returned in response under `secret` + `_warning`).' example: false delete: summary: 'Delete webhook subscription' operationId: deleteWebhookSubscription description: "Soft-deletes the subscription. In-flight deliveries that have already\nbeen queued will complete; no new deliveries are dispatched." parameters: [] responses: { } tags: - Webhooks parameters: - in: path name: id description: 'The ID of the webhook.' example: B8c6F7NNfcj9m6RJYC6RtkmZN6 required: true schema: type: string /api/v1/webhook-deliveries: get: summary: 'List webhook deliveries' operationId: listWebhookDeliveries description: '' parameters: - in: query name: subscription_id description: 'Filter by subscription.' example: architecto required: false schema: type: string description: 'Filter by subscription.' example: architecto - in: query name: event_name description: 'Filter by event name.' example: architecto required: false schema: type: string description: 'Filter by event name.' example: architecto - in: query name: status description: pending|delivered|failed|dead-letter example: architecto required: false schema: type: string description: pending|delivered|failed|dead-letter example: architecto - in: query name: page description: '' example: 16 required: false schema: type: integer description: '' example: 16 - in: query name: per_page description: 'Max 100.' example: 16 required: false schema: type: integer description: 'Max 100.' example: 16 responses: 401: description: '' content: application/problem+json: schema: type: object example: type: 'https://docs.elemza.com/errors/token_invalid' status: 401 code: token_invalid title_fa: 'توکن نامعتبر است' title_en: 'Invalid token' request_id: 01M0TB56TMQ5DM02AAXFJY67D0 properties: type: type: string example: 'https://docs.elemza.com/errors/token_invalid' status: type: integer example: 401 code: type: string example: token_invalid title_fa: type: string example: 'توکن نامعتبر است' title_en: type: string example: 'Invalid token' request_id: type: string example: 01M0TB56TMQ5DM02AAXFJY67D0 tags: - Webhooks '/api/v1/webhook-deliveries/{id}': get: summary: 'Get webhook delivery' operationId: getWebhookDelivery description: 'Shows full payload, response code, error message, attempt history.' parameters: [] responses: 401: description: '' content: application/problem+json: schema: type: object example: type: 'https://docs.elemza.com/errors/token_invalid' status: 401 code: token_invalid title_fa: 'توکن نامعتبر است' title_en: 'Invalid token' request_id: 01M0TB56V0MKCWTNVDCQ1QR7SG properties: type: type: string example: 'https://docs.elemza.com/errors/token_invalid' status: type: integer example: 401 code: type: string example: token_invalid title_fa: type: string example: 'توکن نامعتبر است' title_en: type: string example: 'Invalid token' request_id: type: string example: 01M0TB56V0MKCWTNVDCQ1QR7SG tags: - Webhooks parameters: - in: path name: id description: 'The ID of the webhook delivery.' example: B8c6F7NNfcj9m6RJYC6RtkmZN6 required: true schema: type: string '/api/v1/webhooks/{id}/test': post: summary: 'Send a synthetic test event' operationId: sendASyntheticTestEvent description: "Fires a `webhook.test` event to verify the endpoint is reachable + signed\ncorrectly. Counts toward `webhook_deliveries` like a real event." parameters: [] responses: { } tags: - Webhooks parameters: - in: path name: id description: 'The ID of the webhook.' example: B8c6F7NNfcj9m6RJYC6RtkmZN6 required: true schema: type: string '/api/v1/webhook-deliveries/{id}/replay': post: summary: 'Replay webhook delivery' operationId: replayWebhookDelivery description: "Creates a NEW delivery row with the same payload + subscription. The\noriginal row keeps its terminal status (failed / dead-letter / delivered)." parameters: [] responses: { } tags: - Webhooks parameters: - in: path name: id description: 'The ID of the webhook delivery.' example: B8c6F7NNfcj9m6RJYC6RtkmZN6 required: true schema: type: string