Introduction
Public REST API for Contract creation and Electronic Signature lifecycle. Bilingual (fa/en) errors. RFC 7807 problem+json envelope.
## Quickstart
1. **Create a Personal Access Token** in your Emza dashboard at `/dashboard/api-tokens`. The plain token is shown once at creation — store it securely.
2. Send any API request with `Authorization: Bearer pat_live_xxx`.
3. Test mode tokens (`pat_test_xxx`) don't charge your wallet and use mocked KYC/SMS.
## Headers
| Header | Purpose |
|--------|---------|
| `Authorization: Bearer <token>` | Required on all authenticated routes |
| `Idempotency-Key: <ulid>` | Recommended on POST/PATCH/PUT — 24h replay cache |
| `X-Skip-Sms: true` | (POST /contracts) skip emza's outbound SMS; you deliver signing_url yourself |
## Response shapes
- Success: `{ "data": ... }` or `{ "data": [...], "meta": {...}, "links": {...} }` for paginated
- Error: RFC 7807 problem+json envelope with bilingual `title_fa`/`title_en` + `request_id`
## Rate limits
Per-token, tier-based. Read `X-RateLimit-*` headers on every response.
<aside>Full architecture + decision log lives at <code>docs/api-master-plan/</code> in the Emza repo. Stakeholder-locked plan as of 2026-05-11.</aside>
Authenticating requests
To authenticate requests, include an Authorization header with the value "Bearer pat_live_xxxxxxxxxxxxxxxxxxxxxx".
All authenticated endpoints are marked with a requires authentication badge in the documentation below.
Create your token in the dashboard at /dashboard/api-tokens. Plain token is shown once at creation.
Contracts
Public signed-URL download.
requires authentication
Validates Laravel's signed middleware. No Bearer token required —
the URL signature is the auth proof. The signature is generated by
downloadPdf() after the bearer was verified, so this is functionally
equivalent to a 24h time-bounded capability URL.
Records an ApiRequestLog-style entry via the underlying ContractDownload
audit table (same trail as the user-panel signed-link download flow).
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/contracts/consequatur/dl" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/contracts/consequatur/dl"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (404):
Show headers
x-request-id: 01KZ8G3RFNW7T1EQHEEY87PZYX
x-emza-api-version: v1
cache-control: no-cache, private
content-type: application/problem+json
access-control-allow-origin: *
access-control-expose-headers: X-Request-ID, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After, X-Emza-API-Version, X-Idempotent-Replay
{
"type": "https://docs.elemza.com/errors/not_found",
"status": 404,
"code": "not_found",
"title_fa": "منبع یافت نشد",
"title_en": "Resource not found",
"request_id": "01KZ8G3RFNW7T1EQHEEY87PZYX"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List contracts
requires authentication
Returns paginated contracts visible to caller. Org root sees own + descendants' non-private contracts; non-root sees own + contracts where they're a signer.
Scope: contracts:read.
PII filter scope requirement: filtering by signer_mobile or
signer_national_code requires the additional signers:read_pii ability
on your token. Without it, those query parameters return
403 scope_missing. This prevents a low-privilege contracts:read
token from using the index as a confirmation oracle to check whether a
given mobile/national-code signs any contract on the owner's tree.
The * wildcard scope also passes the check.
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/contracts?status=waiting_signature&signature_method=digital&created_after=2026-01-01T00%3A00%3A00Z&created_before=consequatur&signer_mobile=09121234567&signer_national_code=consequatur&template_id=17&is_private=&page=17&per_page=17&sort=consequatur" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"status\": \"canceled\",
\"signature_method\": \"digital\",
\"created_after\": \"2026-08-05T11:49:49\",
\"created_before\": \"2026-08-05T11:49:49\",
\"signer_mobile\": \"09156277171\",
\"signer_national_code\": \"sufvyvddqa\",
\"template_id\": 17,
\"is_private\": true,
\"page\": 45,
\"per_page\": 16,
\"sort\": \"-created_at\"
}"
const url = new URL(
"https://elemza.com/api/v1/contracts"
);
const params = {
"status": "waiting_signature",
"signature_method": "digital",
"created_after": "2026-01-01T00:00:00Z",
"created_before": "consequatur",
"signer_mobile": "09121234567",
"signer_national_code": "consequatur",
"template_id": "17",
"is_private": "0",
"page": "17",
"per_page": "17",
"sort": "consequatur",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"status": "canceled",
"signature_method": "digital",
"created_after": "2026-08-05T11:49:49",
"created_before": "2026-08-05T11:49:49",
"signer_mobile": "09156277171",
"signer_national_code": "sufvyvddqa",
"template_id": 17,
"is_private": true,
"page": 45,
"per_page": 16,
"sort": "-created_at"
};
fetch(url, {
method: "GET",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (401):
Show headers
x-request-id: 01KZ8G3RMW4F46K1CKJPW3Y1KX
x-emza-api-version: v1
cache-control: no-cache, private
content-type: application/problem+json
access-control-allow-origin: *
access-control-expose-headers: X-Request-ID, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After, X-Emza-API-Version, X-Idempotent-Replay
{
"type": "https://docs.elemza.com/errors/token_invalid",
"status": 401,
"code": "token_invalid",
"title_fa": "توکن نامعتبر است",
"title_en": "Invalid token",
"request_id": "01KZ8G3RMW4F46K1CKJPW3Y1KX"
}
Example response (403, PII filter without scope):
{
"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"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get contract
requires authentication
Returns full contract shape with embedded signers, per-page geometry, and template positions snapshot.
Scope: contracts:read.
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/contracts/ABC1234567XYZ890" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/contracts/ABC1234567XYZ890"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
x-request-id: 01KZ8G3RNKKFH63R223BTZKET0
x-emza-api-version: v1
cache-control: no-cache, private
content-type: application/problem+json
access-control-allow-origin: *
access-control-expose-headers: X-Request-ID, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After, X-Emza-API-Version, X-Idempotent-Replay
{
"type": "https://docs.elemza.com/errors/token_invalid",
"status": 401,
"code": "token_invalid",
"title_fa": "توکن نامعتبر است",
"title_en": "Invalid token",
"request_id": "01KZ8G3RNKKFH63R223BTZKET0"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Download signed PDF
requires authentication
Returns the final signed PDF in one of three formats (Phase 3):
format=redirect(default) —302 Foundto a signed URL (24h TTL). The signed URL points at the unauthenticated/api/v1/contracts/{code}/dlroute, which validates the signature and streams the file. Use this when embedding in<a href>for partner-facing UIs.format=url— JSON envelope withurl,expires_at,filename,size_bytes,checksum_sha256. Use this when the API client wants to hand the URL to another system, embed in email, or pre-flight before downloading.format=stream— streams the PDF inline (consumes API egress). Use when the caller is a backend service that wants the bytes in one round-trip.
Scope: contracts:read.
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/contracts/consequatur/pdf?format=url&ttl=17" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/contracts/consequatur/pdf"
);
const params = {
"format": "url",
"ttl": "17",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (404):
Show headers
x-request-id: 01KZ8G3RPE6JTEZKV1TP3AVR3T
x-emza-api-version: v1
cache-control: no-cache, private
content-type: application/problem+json
access-control-allow-origin: *
access-control-expose-headers: X-Request-ID, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After, X-Emza-API-Version, X-Idempotent-Replay
{
"type": "https://docs.elemza.com/errors/not_found",
"status": 404,
"code": "not_found",
"title_fa": "منبع یافت نشد",
"title_en": "Resource not found",
"request_id": "01KZ8G3RPE6JTEZKV1TP3AVR3T"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Poll processing status
requires authentication
Lightweight (<200 bytes) status of the rasterization + signer-creation job.
Reads contract:progress:{id} cache key directly — no DB hit.
Step values: files, signers, sms, done, error.
Returns ready=true when step=done. If progress key is gone (TTL expired)
and contract has signers + pages, falls back to ready=true (job finished
earlier and key was reaped).
Scope: contracts:read.
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/contracts/consequatur/processing" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/contracts/consequatur/processing"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (404):
Show headers
x-request-id: 01KZ8G3RQ2VG8EJZZYM90BT45C
x-emza-api-version: v1
cache-control: no-cache, private
content-type: application/problem+json
access-control-allow-origin: *
access-control-expose-headers: X-Request-ID, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After, X-Emza-API-Version, X-Idempotent-Replay
{
"type": "https://docs.elemza.com/errors/not_found",
"status": 404,
"code": "not_found",
"title_fa": "منبع یافت نشد",
"title_en": "Resource not found",
"request_id": "01KZ8G3RQ2VG8EJZZYM90BT45C"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create contract
requires authentication
Creates a new contract. Two modes:
One-shot — multipart/form-data with file field + metadata. File is
persisted to staging and ProcessContractFilesJob is dispatched. Optional
signers[] are added by the same job after page rasterization completes.
Response is 202 Accepted; poll _links.processing until ready=true.
Multi-step — JSON without file. Creates a draft contract row only.
Caller then issues POST /contracts/{code}/files to attach the document.
Signature placement — template_id + template_enforcement
Passing template_id snapshots that template's signature slots onto the new
contract. template_enforcement decides what those slots MEAN to the signer:
locked— each slot is pinned: the signer cannot drag the signature off it, and cannot sign on a page the template never assigned them. This is the default whenevertemplate_idis present.suggested— the signature merely starts on the slot; the signer may move it anywhere, on any page.
strict / loose are accepted as legacy aliases of locked / suggested
(the spelling earlier revisions of this page advertised).
If the template pins slots on more than one page, send all_pages_signature=true
as well — otherwise one signature on any allowed page completes the contract.
Verify what was applied on the create response (template_id,
template_enforcement) or in full via GET /contracts/{code} → template_positions.
Scope: contracts:write.
Example request:
curl --request POST \
"https://elemza.com/api/v1/contracts" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: multipart/form-data" \
--header "Accept: application/json" \
--form "title=قرارداد بیمه شخص ثالث"\
--form "signature_method=consequatur"\
--form "all_pages_signature="\
--form "is_private="\
--form "template_id=26"\
--form "template_enforcement=locked"\
--form "redirect_url=https://partner.example/contracts/42/signed"\
--form "signers[]=consequatur"\
--form "file=@C:\Users\pc\AppData\Local\Temp\php3B47.tmp" const url = new URL(
"https://elemza.com/api/v1/contracts"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "multipart/form-data",
"Accept": "application/json",
};
const body = new FormData();
body.append('title', 'قرارداد بیمه شخص ثالث');
body.append('signature_method', 'consequatur');
body.append('all_pages_signature', '');
body.append('is_private', '');
body.append('template_id', '26');
body.append('template_enforcement', 'locked');
body.append('redirect_url', 'https://partner.example/contracts/42/signed');
body.append('signers[]', 'consequatur');
body.append('file', document.querySelector('input[name="file"]').files[0]);
fetch(url, {
method: "POST",
headers,
body,
}).then(response => response.json());Example response (422, template_id not reachable by the caller):
{
"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"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Upload contract file(s) (two-step flow)
requires authentication
Attaches a file to an existing draft contract that has no pages yet.
Same async pipeline as POST /contracts one-shot: stages bytes, dispatches
ProcessContractFilesJob, returns 202 with the processing link.
Scope: contracts:write.
Example request:
curl --request POST \
"https://elemza.com/api/v1/contracts/consequatur/files" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: multipart/form-data" \
--header "Accept: application/json" \
--form "file=@C:\Users\pc\AppData\Local\Temp\php3B58.tmp" const url = new URL(
"https://elemza.com/api/v1/contracts/consequatur/files"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "multipart/form-data",
"Accept": "application/json",
};
const body = new FormData();
body.append('file', document.querySelector('input[name="file"]').files[0]);
fetch(url, {
method: "POST",
headers,
body,
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Cancel contract
requires authentication
Cancels a contract in draft or waiting_signature state. Refunds the
billed amount to the original wallet (100% if no signer signed, 50% if any
Shahkar was consumed). Deletes contract files from storage. Waiting signers
receive an SMS notifying them the contract was canceled.
Scope: contracts:write.
Example request:
curl --request DELETE \
"https://elemza.com/api/v1/contracts/consequatur" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/contracts/consequatur"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"code": "ABC1234567XYZ890",
"status": "canceled",
"canceled_at": "2026-05-11T10:00:00.000Z",
"refund": {
"amount": 60000,
"currency": "IRT"
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Digital certificates (PKI)
Issuing a certificate is not something an API can do on somebody's behalf: the law wants an OTP that person typed and a live face check they attended. So these endpoints do the part an API legitimately can — start the enrolment, hand back a link for the person, and let the integrator follow it to the end.
Certificate status for one of your people
requires authentication
Answers "can this person sign digitally yet?" for somebody you have enrolled.
Privacy — this is not a lookup service for arbitrary national codes. It only answers for people you yourself started an enrolment for; anyone else returns 404, so the endpoint cannot be used to discover whether a given Iranian holds a certificate.
Scope required: certificates:read.
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/certificates?national_code=0012345678" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/certificates"
);
const params = {
"national_code": "0012345678",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Active certificate):
{
"data": {
"national_code": "0012345678",
"status": "active",
"valid_from": "2026-07-30T09:12:00.000Z",
"valid_until": "2027-07-30T09:12:00.000Z",
"mode": "live"
}
}
Example response (200, Enrolment still in progress):
{
"data": {
"national_code": "0012345678",
"status": "pending_ekyc",
"valid_from": null,
"valid_until": null,
"mode": "live"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
One enrolment request
requires authentication
The state of a single enrolment you started, by the request_id you received.
Scope required: certificates:read.
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/certificates/01jqk8x2m4n6p8r0s2t4v6w8y0" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/certificates/01jqk8x2m4n6p8r0s2t4v6w8y0"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
x-request-id: 01KZ8G3RYCK5BYKQ4P24FB7ZKV
x-emza-api-version: v1
cache-control: no-cache, private
content-type: application/problem+json
access-control-allow-origin: *
access-control-expose-headers: X-Request-ID, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After, X-Emza-API-Version, X-Idempotent-Replay
{
"type": "https://docs.elemza.com/errors/token_invalid",
"status": 401,
"code": "token_invalid",
"title_fa": "توکن نامعتبر است",
"title_en": "Invalid token",
"request_id": "01KZ8G3RYCK5BYKQ4P24FB7ZKV"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Start a certificate enrolment
requires authentication
Creates a short-lived, single-use link for one of your users to obtain a digital certificate.
Send them to onboarding_url; they complete the SMS code, the identity form and the video
check on our pages, and you learn the outcome from certificate.issued / certificate.failed
(or by polling GET /api/v1/certificates/{request_id}).
Who pays — pay_by: "issuer" charges YOUR wallet for the one-time issuance fee right now
and refunds it automatically if no certificate is ever issued. pay_by: "subject" (default)
lets the person pay on the page.
Idempotency — while an enrolment for the same national code is still open you get the SAME link back, not a second one. Pendar treats an open enrolment as a single order.
Scope required: certificates:write.
Example request:
curl --request POST \
"https://elemza.com/api/v1/certificates/onboarding" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"national_code\": \"0012345678\",
\"mobile\": \"09121234567\",
\"first_name\": \"علی\",
\"last_name\": \"رضایی\",
\"pay_by\": \"issuer\",
\"redirect_url\": \"https:\\/\\/partner.example\\/done\",
\"ttl_minutes\": 120
}"
const url = new URL(
"https://elemza.com/api/v1/certificates/onboarding"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"national_code": "0012345678",
"mobile": "09121234567",
"first_name": "علی",
"last_name": "رضایی",
"pay_by": "issuer",
"redirect_url": "https:\/\/partner.example\/done",
"ttl_minutes": 120
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201, Link created):
{
"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"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Form submissions — fill a form through the API (ADR-0029)
Submit-once semantics: no draft/resume, no edit-after-submit (FB SCOPE-CONTRACT rows 6/7). Ownership: every endpoint requires the form to BELONG to the token owner (v2 schema only) — the integrator drives fills of their OWN forms; end users never hold tokens.
Public signed-URL download twin (no Bearer — the URL signature is the auth proof, minted by downloadPdf() after the bearer was verified). Mirrors contracts /dl.
requires authentication
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/form-submissions/consequatur/dl" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/form-submissions/consequatur/dl"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (404):
Show headers
x-request-id: 01KZ8G3RH02VNBX4G30849FBBE
x-emza-api-version: v1
cache-control: no-cache, private
content-type: application/problem+json
access-control-allow-origin: *
access-control-expose-headers: X-Request-ID, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After, X-Emza-API-Version, X-Idempotent-Replay
{
"type": "https://docs.elemza.com/errors/not_found",
"status": 404,
"code": "not_found",
"title_fa": "منبع یافت نشد",
"title_en": "Resource not found",
"request_id": "01KZ8G3RH02VNBX4G30849FBBE"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List submissions
requires authentication
Paginated, newest first. Scope: forms:read.
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/forms/consequatur/submissions?status=consequatur&submitted_after=consequatur&submitted_before=consequatur&has_contract=&per_page=17" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"status\": \"draft\",
\"submitted_after\": \"2026-08-05T11:49:49\",
\"submitted_before\": \"2026-08-05T11:49:49\",
\"has_contract\": false,
\"page\": 73,
\"per_page\": 13
}"
const url = new URL(
"https://elemza.com/api/v1/forms/consequatur/submissions"
);
const params = {
"status": "consequatur",
"submitted_after": "consequatur",
"submitted_before": "consequatur",
"has_contract": "0",
"per_page": "17",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"status": "draft",
"submitted_after": "2026-08-05T11:49:49",
"submitted_before": "2026-08-05T11:49:49",
"has_contract": false,
"page": 73,
"per_page": 13
};
fetch(url, {
method: "GET",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (401):
Show headers
x-request-id: 01KZ8G3RV3Y5T3Y8J2ECWJ03S2
x-emza-api-version: v1
cache-control: no-cache, private
content-type: application/problem+json
access-control-allow-origin: *
access-control-expose-headers: X-Request-ID, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After, X-Emza-API-Version, X-Idempotent-Replay
{
"type": "https://docs.elemza.com/errors/token_invalid",
"status": 401,
"code": "token_invalid",
"title_fa": "توکن نامعتبر است",
"title_en": "Invalid token",
"request_id": "01KZ8G3RV3Y5T3Y8J2ECWJ03S2"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get one submission
requires authentication
Scope: forms:read.
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/forms/consequatur/submissions/consequatur" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/forms/consequatur/submissions/consequatur"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (404):
Show headers
x-request-id: 01KZ8G3RVXQ57NGX81GHHK6AQF
x-emza-api-version: v1
cache-control: no-cache, private
content-type: application/problem+json
access-control-allow-origin: *
access-control-expose-headers: X-Request-ID, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After, X-Emza-API-Version, X-Idempotent-Replay
{
"type": "https://docs.elemza.com/errors/not_found",
"status": 404,
"code": "not_found",
"title_fa": "منبع یافت نشد",
"title_en": "Resource not found",
"request_id": "01KZ8G3RVXQ57NGX81GHHK6AQF"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Download submission PDF
requires authentication
Image-overlay forms render a final PDF per submission (queued at submit). Same signed-URL family as the contracts PDF endpoint:
format=redirect(default) — 302 to a signed URL (anon-safe, forwardable)format=url— JSON envelope with url/expiry/checksumformat=stream— bytes in this response
Scope: forms:read.
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/forms/consequatur/submissions/consequatur/pdf?format=consequatur&ttl=17" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/forms/consequatur/submissions/consequatur/pdf"
);
const params = {
"format": "consequatur",
"ttl": "17",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (404):
Show headers
x-request-id: 01KZ8G3RWBAEH74KBCG42VRHXP
x-emza-api-version: v1
cache-control: no-cache, private
content-type: application/problem+json
access-control-allow-origin: *
access-control-expose-headers: X-Request-ID, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After, X-Emza-API-Version, X-Idempotent-Replay
{
"type": "https://docs.elemza.com/errors/not_found",
"status": 404,
"code": "not_found",
"title_fa": "منبع یافت نشد",
"title_en": "Resource not found",
"request_id": "01KZ8G3RWBAEH74KBCG42VRHXP"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Submit a form (fill via API)
requires authentication
Runs the EXACT same pipeline as the public web renderer: schema validation,
visibility filtering, formula computation, trust scoring, the signature bridge
(requires_signature forms auto-create a contract — response embeds it with per-signer
signing_urls), image-overlay PDF rendering (queued), and owner notifications.
Scope: forms:write + Idempotency-Key supported.
Fields hidden by visibility conditions are stripped server-side. query_shahkar /
query_legal_entity field values must come from POST /forms/{slug}/verify-field
(same request IP) or their matched=true claim is stripped as a forgery.
File/image upload fields are not supported in v1 (mirrors the web fill flow).
Example request:
curl --request POST \
"https://elemza.com/api/v1/forms/consequatur/submissions" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"data\": {
\"first_name\": \"علی\"
}
}"
const url = new URL(
"https://elemza.com/api/v1/forms/consequatur/submissions"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"data": {
"first_name": "علی"
}
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Forms — read-only access to v2 forms owned by the authenticated user.
Phase 5 MVP: list + show + stats. Submissions endpoint will land in Phase 5b with proper pagination + transformer.
List forms (v2 only — schema_version='2.0')
requires authentication
Paginated list of v2-schema forms owned by the calling user, most recent
first. Form Builder v1 records are excluded — use the dashboard for those.
Returns the standard data + meta + links envelope, consistent with the
other list endpoints (API-G1, audit 2026-06 — previously a silent limit(100)).
Scope required: forms:read.
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/forms?page=17&per_page=17" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/forms"
);
const params = {
"page": "17",
"per_page": "17",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
x-request-id: 01KZ8G3RS29K4GADKF6NZ8H0RJ
x-emza-api-version: v1
cache-control: no-cache, private
content-type: application/problem+json
access-control-allow-origin: *
access-control-expose-headers: X-Request-ID, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After, X-Emza-API-Version, X-Idempotent-Replay
{
"type": "https://docs.elemza.com/errors/token_invalid",
"status": 401,
"code": "token_invalid",
"title_fa": "توکن نامعتبر است",
"title_en": "Invalid token",
"request_id": "01KZ8G3RS29K4GADKF6NZ8H0RJ"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Response
Response Fields
data
object
status
One of draft, published, archived.
renderer_type
Form rendering mode: online (web form) or image_overlay (PDF background + overlay).
Show one form (metadata only — schema available via dedicated /schema endpoint in future)
requires authentication
Scope required: forms:read.
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/forms/frm-e7465df28dd45785" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/forms/frm-e7465df28dd45785"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"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"
}
}
Example response (404, Not found or not owned):
{
"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"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Form analytics (views/starts/submissions/conversion rates).
requires authentication
Aggregated counters returned by FormAnalyticsService::stats(). Includes
lifetime totals plus 7-day and 30-day windows so you can chart trends without
additional queries.
Scope required: forms:read.
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/forms/frm-e7465df28dd45785/stats" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/forms/frm-e7465df28dd45785/stats"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"totals": {
"views": 187,
"starts": 41,
"submissions": 23,
"conversion_rate": 56.1
},
"last_7_days": {
"views": 32,
"starts": 9,
"submissions": 4
},
"last_30_days": {
"views": 154,
"starts": 38,
"submissions": 21
}
}
}
Example response (404, Not found or not owned):
{
"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"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Form schema (fillable-field catalog)
requires authentication
Everything an integrator needs to build their own fill UI and construct a valid
POST /forms/{slug}/submissions payload: field names, types, labels, required flags,
options, pages, visibility conditions, and whether the API fill channel supports the
field (supported=false for file/image — mirrors the web fill flow).
Scope required: forms:read.
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/forms/consequatur/schema" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/forms/consequatur/schema"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
x-request-id: 01KZ8G3RTADWRMZD71NF78N5HT
x-emza-api-version: v1
cache-control: no-cache, private
content-type: application/problem+json
access-control-allow-origin: *
access-control-expose-headers: X-Request-ID, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After, X-Emza-API-Version, X-Idempotent-Replay
{
"type": "https://docs.elemza.com/errors/token_invalid",
"status": 401,
"code": "token_invalid",
"title_fa": "توکن نامعتبر است",
"title_en": "Invalid token",
"request_id": "01KZ8G3RTADWRMZD71NF78N5HT"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Verify a query field (Shahkar / legal entity)
requires authentication
Runs the SAME server-side verification the web renderer's «استعلام» buttons run, and
writes the same server-side binding that POST /forms/{slug}/submissions checks — echo
the returned value back inside data.{field} when you submit, FROM THE SAME IP.
The form OWNER's daily Shahkar quota pays for the lookup (identical to web). Sandbox
tokens short-circuit to matched=true without any provider call.
Scope required: forms:write.
Example request:
curl --request POST \
"https://elemza.com/api/v1/forms/consequatur/verify-field" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"field\": \"consequatur\",
\"values\": {
\"mobile\": \"09121234567\",
\"national_code\": \"0012345678\"
}
}"
const url = new URL(
"https://elemza.com/api/v1/forms/consequatur/verify-field"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"field": "consequatur",
"values": {
"mobile": "09121234567",
"national_code": "0012345678"
}
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Meta — health, identity, quota, cost
Health check
Lightweight liveness probe. No authentication required. Always returns 200 if the API process is up.
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/health" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/health"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"status": "ok",
"time": "2026-05-11T11:53:29.399Z",
"version": "v1.0.0"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Caller identity
requires authentication
Returns information about the authenticated token, its owner user, and the billing context (wallet balance, org root). Any valid token can call this — no specific scope required.
Use the token.mode field to confirm whether your token is in live mode
(charges your wallet, real KYC/SMS) or test sandbox mode (zero wallet
impact, mocked side effects). wallet_balance is always in tomans (IRR/10).
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/me" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/me"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Live token):
{
"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
}
}
Example response (200, Sandbox/test token):
{
"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
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Response
Response Fields
data
object
token
object
mode
Either live or test. Sandbox tokens never debit the wallet.
scopes
string[]
of granted abilities. ["*"] means full access.
ip_whitelist
If non-empty, requests from other IPs are rejected with 403 ip_not_allowed. Five failures within 1h auto-revoke the token.
user
object
is_org_root
True when this user is the root of a Sub-Organization tree (can invite sub-users).
org_root_id
ID of the org root if this user is a member, else null.
billing_actor_user_id
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
Current wallet balance in tomans (Iranian Rials ÷ 10).
Current quota
requires authentication
Returns the caller's current-month signature quota usage + remaining, package tier, and today's Shahkar verification credits.
Quota semantics:
free_quotais the per-period allowance from the user's active package (Free=3 lifetime, Bronze=100/mo, Silver=300/mo, Gold=600/mo — verify against live/admin/packages, these are the 2026-06 tariff). Returns 0 if the package is expired or unset.used/remainingmay be FRACTIONAL: additional signers on one contract count as half a unit each (a 2-signer contract consumes 1.5). Whole values serialize as integers; a half consumes serializes as e.g.1.5.usedis total signature-units recorded sincepackage_started_at(so pre-purchase usage doesn't deplete a freshly-bought plan). Free tier counts lifetime (no monthly reset).remaining = free_quota - usedfor limited plans. null means genuinely unlimited (signature_limitIS NULL).- When
remainingreaches 0, signatures still work — billed as overage from the wallet atbase × (1 − overage_discount); the OVERAGE discount is Free=0%, Bronze=5%, Silver=15%, Gold=25% (separate from, and smaller than, the 10/20/30% in-package discount encoded in the package price).
Shahkar credits are separate from signature quota. Daily allowance by package (Free=3, Bronze=5, Silver=10, Gold=20). Once exhausted, the client must purchase batch top-ups before further Shahkar verifications.
Scope required: contracts:read.
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/quota" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/quota"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Bronze user mid-month):
{
"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
}
}
}
Example response (200, Gold (unlimited)):
{
"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
}
}
}
Example response (200, No active package (pay-per-use)):
{
"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
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Response
Response Fields
data
object
free_quota
Monthly signature allowance from active package. Zero if package expired or none.
used
Signatures recorded this period (since package_started_at).
remaining
Signatures left this period. null means unlimited (Gold).
shahkar
object
allowed
Whether the user may attempt a Shahkar verification right now. False = quota exhausted, must purchase batch top-up.
Cost estimate
requires authentication
Returns full cost breakdown for a hypothetical contract without committing.
Use before calling POST /contracts so users can see "X toman" upfront.
Scope required: contracts:read.
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/cost-estimate?signers=3&discount_code=WELCOME10" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/cost-estimate"
);
const params = {
"signers": "3",
"discount_code": "WELCOME10",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
x-request-id: 01KZ8G3RJTMK556QEN5EVEQ5H0
x-emza-api-version: v1
cache-control: no-cache, private
content-type: application/problem+json
access-control-allow-origin: *
access-control-expose-headers: X-Request-ID, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After, X-Emza-API-Version, X-Idempotent-Replay
{
"type": "https://docs.elemza.com/errors/token_invalid",
"status": 401,
"code": "token_invalid",
"title_fa": "توکن نامعتبر است",
"title_en": "Invalid token",
"request_id": "01KZ8G3RJTMK556QEN5EVEQ5H0"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Signers
Signer endpoints for a contract.
Phase 1 = read-only (index + show). Resend + delete = Phase 2 (mutations).
Spec: docs/api-master-plan/02-endpoint-surface.md (Signer endpoints).
List signers
requires authentication
Lists all signers on a contract. PII (mobile, national_code) is masked
unless the caller's token has signers:read_pii scope.
Scope: contracts:read.
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/contracts/consequatur/signers" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/contracts/consequatur/signers"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (404):
Show headers
x-request-id: 01KZ8G3RQZEF54DJWKAYQHR5HE
x-emza-api-version: v1
cache-control: no-cache, private
content-type: application/problem+json
access-control-allow-origin: *
access-control-expose-headers: X-Request-ID, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After, X-Emza-API-Version, X-Idempotent-Replay
{
"type": "https://docs.elemza.com/errors/not_found",
"status": 404,
"code": "not_found",
"title_fa": "منبع یافت نشد",
"title_en": "Resource not found",
"request_id": "01KZ8G3RQZEF54DJWKAYQHR5HE"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get signer
requires authentication
Detailed view of a single signer. PII masked unless token has signers:read_pii.
Scope: contracts:read.
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/contracts/consequatur/signers/DEF456GHJ789KLM" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/contracts/consequatur/signers/DEF456GHJ789KLM"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (404):
Show headers
x-request-id: 01KZ8G3RRDM6ZXDFSSBCK5Q58S
x-emza-api-version: v1
cache-control: no-cache, private
content-type: application/problem+json
access-control-allow-origin: *
access-control-expose-headers: X-Request-ID, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After, X-Emza-API-Version, X-Idempotent-Replay
{
"type": "https://docs.elemza.com/errors/not_found",
"status": 404,
"code": "not_found",
"title_fa": "منبع یافت نشد",
"title_en": "Resource not found",
"request_id": "01KZ8G3RRDM6ZXDFSSBCK5Q58S"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Add signers
requires authentication
Adds one or more signers to a draft contract. Runs mandatory Shahkar
pre-validation (mobile ↔ national_code match) per signer in production.
On success, contract transitions to waiting_signature and SMS dispatch
jobs are queued for first-order signers.
Scope: signers:write.
Example request:
curl --request POST \
"https://elemza.com/api/v1/contracts/consequatur/signers" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"signers\": [
\"consequatur\"
]
}"
const url = new URL(
"https://elemza.com/api/v1/contracts/consequatur/signers"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"signers": [
"consequatur"
]
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Resend signing link SMS
requires authentication
Re-sends the signing-link SMS to a waiting signer. Rate-limited to 3 per
5 minutes per signer (returns 429 rate_limit_exceeded).
Scope: signers:write.
Example request:
curl --request POST \
"https://elemza.com/api/v1/contracts/consequatur/signers/consequatur/resend" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/contracts/consequatur/signers/consequatur/resend"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Remove signer
requires authentication
Removes a signer from a contract. Only allowed when:
- Signer status is
waiting(not yet signed) - Contract status is
draftorwaiting_signature
Scope: signers:write.
Example request:
curl --request DELETE \
"https://elemza.com/api/v1/contracts/consequatur/signers/consequatur" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/contracts/consequatur/signers/consequatur"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Templates
Template endpoints — read-only in v1.0. CRUD lands in v1.x.
Spec: docs/api-master-plan/02-endpoint-surface.md (Template endpoints).
List templates
requires authentication
Lists contract templates the caller can use.
view_scope options:
self— own templates only (default)shared_from_parent— own + templates shared by org parent/rootall_org— entire org tree (org root only)
Scope: templates:read.
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/templates?view_scope=consequatur&active=&page=17&per_page=17" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/templates"
);
const params = {
"view_scope": "consequatur",
"active": "0",
"page": "17",
"per_page": "17",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
x-request-id: 01KZ8G3S08D0NDXWZ8436Q931G
x-emza-api-version: v1
cache-control: no-cache, private
content-type: application/problem+json
access-control-allow-origin: *
access-control-expose-headers: X-Request-ID, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After, X-Emza-API-Version, X-Idempotent-Replay
{
"type": "https://docs.elemza.com/errors/token_invalid",
"status": 401,
"code": "token_invalid",
"title_fa": "توکن نامعتبر است",
"title_en": "Invalid token",
"request_id": "01KZ8G3S08D0NDXWZ8436Q931G"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get template
requires authentication
Full template shape with embedded positions + page geometry + sample preview link.
Scope: templates:read.
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/templates/consequatur" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/templates/consequatur"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (404):
Show headers
x-request-id: 01KZ8G3S157X51XD3D87PA185Q
x-emza-api-version: v1
cache-control: no-cache, private
content-type: application/problem+json
access-control-allow-origin: *
access-control-expose-headers: X-Request-ID, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After, X-Emza-API-Version, X-Idempotent-Replay
{
"type": "https://docs.elemza.com/errors/not_found",
"status": 404,
"code": "not_found",
"title_fa": "منبع یافت نشد",
"title_en": "Resource not found",
"request_id": "01KZ8G3S157X51XD3D87PA185Q"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Template page preview
requires authentication
NOT IMPLEMENTED YET — returns 501 preview_not_available. The signed
page-preview URL lands in a later phase (Storage::temporaryUrl). Documented
as a stub so merchants do not code against a fake url:null 200.
Scope: templates:read.
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/templates/17/preview/17" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/templates/17/preview/17"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
x-request-id: 01KZ8G3S1K4FW7DF5F6V0ZB7KD
x-emza-api-version: v1
cache-control: no-cache, private
content-type: application/problem+json
access-control-allow-origin: *
access-control-expose-headers: X-Request-ID, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After, X-Emza-API-Version, X-Idempotent-Replay
{
"type": "https://docs.elemza.com/errors/token_invalid",
"status": 401,
"code": "token_invalid",
"title_fa": "توکن نامعتبر است",
"title_en": "Invalid token",
"request_id": "01KZ8G3S1K4FW7DF5F6V0ZB7KD"
}
Example response (501, Stub — not implemented):
{
"status": 501,
"code": "preview_not_available"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Webhooks
Outbound webhook subscriptions. Customers create one or more endpoints, each filtered by an array of event names. Deliveries are signed with HMAC-SHA256 via the secret returned at create-time (shown only once).
Scope: webhooks:manage.
List webhook subscriptions
requires authentication
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/webhooks?active=&page=17&per_page=17" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"active\": true,
\"page\": 73,
\"per_page\": 13
}"
const url = new URL(
"https://elemza.com/api/v1/webhooks"
);
const params = {
"active": "0",
"page": "17",
"per_page": "17",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"active": true,
"page": 73,
"per_page": 13
};
fetch(url, {
method: "GET",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (401):
Show headers
x-request-id: 01KZ8G3S4GJJFT12PNFMY2NAQK
x-emza-api-version: v1
cache-control: no-cache, private
content-type: application/problem+json
access-control-allow-origin: *
access-control-expose-headers: X-Request-ID, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After, X-Emza-API-Version, X-Idempotent-Replay
{
"type": "https://docs.elemza.com/errors/token_invalid",
"status": 401,
"code": "token_invalid",
"title_fa": "توکن نامعتبر است",
"title_en": "Invalid token",
"request_id": "01KZ8G3S4GJJFT12PNFMY2NAQK"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get webhook subscription
requires authentication
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/webhooks/AYbBkm7h8gnApxq66c0TVGHFAb" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/webhooks/AYbBkm7h8gnApxq66c0TVGHFAb"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
x-request-id: 01KZ8G3S57H1RA53YQCV908RQ0
x-emza-api-version: v1
cache-control: no-cache, private
content-type: application/problem+json
access-control-allow-origin: *
access-control-expose-headers: X-Request-ID, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After, X-Emza-API-Version, X-Idempotent-Replay
{
"type": "https://docs.elemza.com/errors/token_invalid",
"status": 401,
"code": "token_invalid",
"title_fa": "توکن نامعتبر است",
"title_en": "Invalid token",
"request_id": "01KZ8G3S57H1RA53YQCV908RQ0"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List webhook deliveries
requires authentication
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/webhook-deliveries?subscription_id=consequatur&event_name=consequatur&status=consequatur&page=17&per_page=17" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/webhook-deliveries"
);
const params = {
"subscription_id": "consequatur",
"event_name": "consequatur",
"status": "consequatur",
"page": "17",
"per_page": "17",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
x-request-id: 01KZ8G3S699ZZRE27P8FQZQQM7
x-emza-api-version: v1
cache-control: no-cache, private
content-type: application/problem+json
access-control-allow-origin: *
access-control-expose-headers: X-Request-ID, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After, X-Emza-API-Version, X-Idempotent-Replay
{
"type": "https://docs.elemza.com/errors/token_invalid",
"status": 401,
"code": "token_invalid",
"title_fa": "توکن نامعتبر است",
"title_en": "Invalid token",
"request_id": "01KZ8G3S699ZZRE27P8FQZQQM7"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get webhook delivery
requires authentication
Shows full payload, response code, error message, attempt history.
Example request:
curl --request GET \
--get "https://elemza.com/api/v1/webhook-deliveries/AYbBkm7h8gnApxq66c0TVGHFAb" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/webhook-deliveries/AYbBkm7h8gnApxq66c0TVGHFAb"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (401):
Show headers
x-request-id: 01KZ8G3S72C6YBZB9P6Z67C9J4
x-emza-api-version: v1
cache-control: no-cache, private
content-type: application/problem+json
access-control-allow-origin: *
access-control-expose-headers: X-Request-ID, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After, X-Emza-API-Version, X-Idempotent-Replay
{
"type": "https://docs.elemza.com/errors/token_invalid",
"status": 401,
"code": "token_invalid",
"title_fa": "توکن نامعتبر است",
"title_en": "Invalid token",
"request_id": "01KZ8G3S72C6YBZB9P6Z67C9J4"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create webhook subscription
requires authentication
Registers an HTTPS endpoint to receive event deliveries. We POST each matching event to your URL and sign it with HMAC-SHA256:
X-Emza-Timestamp: <unix seconds>
X-Emza-Signature: sha256=<hex hash_hmac('sha256', "{timestamp}.{raw_body}", secret)>
Compare with hash_equals, and reject anything whose timestamp is more than 5 minutes old.
NOTE: this docblock is published to customers by Scribe. Until 2026-07-31 it described a
Stripe-style X-Emza-Signature: t=<unix>,v1=<hex> header that this platform has never sent —
see App\Services\Webhook\WebhookSigner, which is the only authority on the wire format, and
tests/Feature/Sdk/SdkWebhookVerificationTest, which pins the two together.
The plain signing secret is returned ONCE — store it immediately.
Subsequent reads (index, show) omit the secret entirely. Use
PATCH /webhooks/{id} with rotate_secret=true to issue a fresh secret
(invalidates the previous one immediately).
URL validation (SSRF guard): the url field is rejected with 422
if it fails any of:
- Non-HTTPS scheme (
http://,ftp://,javascript:,file://) - Loopback IPs (
127.0.0.0/8,::1) - RFC 1918 private (
10/8,172.16/12,192.168/16) - Link-local + cloud metadata (
169.254.0.0/16,100.100.100.200) - IPv6 link-local (
fe80::/10) or unique-local (fc00::/7) - Hostname suffix
.local,.internal,.private,.lanorlocalhost - Hostname whose DNS resolves to any of the above
Allowed event names (set in events array — use ["*"] for all):
contract.created, contract.processing.completed, contract.processing.failed,
contract.signer.added, contract.signer.authenticated, contract.signer.signed,
contract.signer.rejected, contract.completed, contract.canceled,
contract.refunded, * (wildcard).
If * is present alongside specific events, the specifics are dropped
(the wildcard makes them redundant). Same event listed twice is deduped.
Scope: webhooks:manage. Idempotency-Key required (24h replay window).
Example request:
curl --request POST \
"https://elemza.com/api/v1/webhooks" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"url\": \"https:\\/\\/example.com\\/webhooks\\/emza\",
\"events\": [
\"contract.completed\",
\"contract.signer.signed\"
],
\"active\": false
}"
const url = new URL(
"https://elemza.com/api/v1/webhooks"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"url": "https:\/\/example.com\/webhooks\/emza",
"events": [
"contract.completed",
"contract.signer.signed"
],
"active": false
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201):
{
"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."
}
}
Example response (422, SSRF blocked URL):
{
"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"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update webhook subscription
requires authentication
Partial update — any field omitted is left unchanged. New url values
pass through the same SSRF guard as POST /webhooks (see Create endpoint).
When rotate_secret=true, a fresh HMAC signing secret is generated and
returned in the response — the previous secret is invalidated immediately,
so any in-flight or already-sent webhook deliveries signed with the old
secret will fail verification on the receiver side. Plan rotations during
a quiet period or pause via active=false first.
Scope: webhooks:manage. Idempotency-Key required (24h replay window).
Example request:
curl --request PATCH \
"https://elemza.com/api/v1/webhooks/01krh7z0e8a4n39gf2ary73xbj" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"url\": \"http:\\/\\/kunze.biz\\/iste-laborum-eius-est-dolor.html\",
\"events\": [
\"consequatur\"
],
\"active\": false,
\"rotate_secret\": false
}"
const url = new URL(
"https://elemza.com/api/v1/webhooks/01krh7z0e8a4n39gf2ary73xbj"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"url": "http:\/\/kunze.biz\/iste-laborum-eius-est-dolor.html",
"events": [
"consequatur"
],
"active": false,
"rotate_secret": false
};
fetch(url, {
method: "PATCH",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"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"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete webhook subscription
requires authentication
Soft-deletes the subscription. In-flight deliveries that have already been queued will complete; no new deliveries are dispatched.
Example request:
curl --request DELETE \
"https://elemza.com/api/v1/webhooks/AYbBkm7h8gnApxq66c0TVGHFAb" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/webhooks/AYbBkm7h8gnApxq66c0TVGHFAb"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Send a synthetic test event
requires authentication
Fires a webhook.test event to verify the endpoint is reachable + signed
correctly. Counts toward webhook_deliveries like a real event.
Example request:
curl --request POST \
"https://elemza.com/api/v1/webhooks/AYbBkm7h8gnApxq66c0TVGHFAb/test" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/webhooks/AYbBkm7h8gnApxq66c0TVGHFAb/test"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Replay webhook delivery
requires authentication
Creates a NEW delivery row with the same payload + subscription. The original row keeps its terminal status (failed / dead-letter / delivered).
Example request:
curl --request POST \
"https://elemza.com/api/v1/webhook-deliveries/AYbBkm7h8gnApxq66c0TVGHFAb/replay" \
--header "Authorization: Bearer 42|abcdef1234567890abcdef1234567890abcdef" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://elemza.com/api/v1/webhook-deliveries/AYbBkm7h8gnApxq66c0TVGHFAb/replay"
);
const headers = {
"Authorization": "Bearer 42|abcdef1234567890abcdef1234567890abcdef",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.