MENU navbar-image

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"
}
 

Request      

GET api/v1/contracts/{code}/dl

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

code   string     

16-char public code. Example: consequatur

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"
}
 

Request      

GET api/v1/contracts

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

status   string  optional    

Filter by status: draft|waiting_signature|completed|voided|canceled. Example: waiting_signature

signature_method   string  optional    

Filter by signing method: electronic|digital. Example: digital

created_after   string  optional    

ISO 8601 datetime. Example: 2026-01-01T00:00:00Z

created_before   string  optional    

ISO 8601 datetime. Example: consequatur

signer_mobile   string  optional    

Iran mobile regex 09XXXXXXXXX. Requires signers:read_pii scope. Example: 09121234567

signer_national_code   string  optional    

10-digit Iran national code. Requires signers:read_pii scope. Example: consequatur

template_id   integer  optional    

Filter by template. Example: 17

is_private   boolean  optional    

Filter by privacy flag. Example: false

page   integer  optional    

Default 1. Example: 17

per_page   integer  optional    

Max 100. Default 20. Example: 17

sort   string  optional    

created_at | -created_at | completed_at | -completed_at. Default -created_at. Example: consequatur

Body Parameters

status   string  optional    

Example: canceled

Must be one of:
  • draft
  • waiting_signature
  • completed
  • voided
  • canceled
signature_method   string  optional    

Example: digital

Must be one of:
  • electronic
  • digital
created_after   string  optional    

value یک تاریخ معتبر نیست. Example: 2026-08-05T11:49:49

created_before   string  optional    

value یک تاریخ معتبر نیست. Example: 2026-08-05T11:49:49

signer_mobile   string  optional    

Must match the regex /^09[0-9]{9}$/. Example: 09156277171

signer_national_code   string  optional    

value باید 10 کاراکتر باشد. Example: sufvyvddqa

template_id   integer  optional    

Example: 17

is_private   boolean  optional    

Example: true

page   integer  optional    

value باید حداقل 1 باشد. Example: 45

per_page   integer  optional    

value باید حداقل 1 باشد. value نباید بیشتر از 100 باشد. Example: 16

sort   string  optional    

Example: -created_at

Must be one of:
  • created_at
  • -created_at
  • completed_at
  • -completed_at

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"
}
 

Request      

GET api/v1/contracts/{code}

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

code   string     

16-char public code (A-Z, 0-9). Example: ABC1234567XYZ890

Download signed PDF

requires authentication

Returns the final signed PDF in one of three formats (Phase 3):

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"
}
 

Request      

GET api/v1/contracts/{code}/pdf

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

code   string     

16-char public code. Example: consequatur

Query Parameters

format   string  optional    

redirect|url|stream. Default redirect. Example: url

ttl   integer  optional    

Signed URL lifetime in seconds (60..86400). Default 86400. Ignored when format=stream. Example: 17

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"
}
 

Request      

GET api/v1/contracts/{code}/processing

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

code   string     

16-char contract code. Example: consequatur

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:

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"
}
 

Request      

POST api/v1/contracts

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: multipart/form-data

Accept        

Example: application/json

Body Parameters

title   string     

Contract title (1..120 chars). Example: قرارداد بیمه شخص ثالث

signature_method   string  optional    

electronic|digital. Default electronic. digital = PADES signature via the Pendar CA cloud-HSM. Requires the PKI feature to be enabled for your account (pre-flight via GET /medata.capabilities.pki), otherwise 403 pki_not_available. The method is contract-wide (all signers sign the same way) and IMMUTABLE after creation — to change it, cancel and recreate (ADR-0009). Example: consequatur

all_pages_signature   boolean  optional    

Default false. When true, every page must carry the signature element. Example: false

is_private   boolean  optional    

Default false. When true, hides contract from org-ancestors. Example: false

template_id   integer  optional    

Optional — must be owned by the caller or shared from the org root; anything else is rejected with 422 template_not_accessible (never silently dropped). List valid ids via GET /api/v1/templates. Example: 26

template_enforcement   string  optional    

locked|suggested (legacy aliases: strict|loose). Defaults to locked when template_id is set. Example: locked

redirect_url   string  optional    

HTTPS URL to send the signer back to after they finish signing. They see our confirmation first, then return automatically. Without it they stay on our confirmation page. Example: https://partner.example/contracts/42/signed

file   file  optional    

PDF or PNG/JPG, max 4MB (multipart/form-data only). Example: C:\Users\pc\AppData\Local\Temp\php3B47.tmp

source   object  optional    

Optional document source instead of file. Currently only {type: "form_submission", code: "ABCD1234EFGH5678"} — uses the rendered PDF of one of YOUR form submissions as this contract's document (the فرم → PDF → امضا chain, works with both signature methods). 409 not_ready if the submission PDF is still rendering; 422 not_applicable for online-renderer forms (no PDF artifact).

signers   string[]  optional    

Optional inline signers (one-shot). Same shape as POST /contracts/{code}/signers.

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());

Request      

POST api/v1/contracts/{code}/files

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: multipart/form-data

Accept        

Example: application/json

URL Parameters

code   string     

16-char contract code. Example: consequatur

Body Parameters

file   file     

PDF or PNG/JPG, max 4MB (multipart/form-data). Example: C:\Users\pc\AppData\Local\Temp\php3B58.tmp

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"
        }
    }
}
 

Request      

DELETE api/v1/contracts/{code}

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

code   string     

16-char public code. Example: consequatur

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"
    }
}
 

Request      

GET api/v1/certificates

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

national_code   string     

The person's national code. Example: 0012345678

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"
}
 

Request      

GET api/v1/certificates/{id}

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The request_id from the onboarding call. Example: 01jqk8x2m4n6p8r0s2t4v6w8y0

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 payspay_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"
    }
}
 

Request      

POST api/v1/certificates/onboarding

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

national_code   string     

The person's 10-digit national code. Example: 0012345678

mobile   string     

Their mobile, 09xxxxxxxxx. Example: 09121234567

first_name   string  optional    

Optional, prefills the form. Example: علی

last_name   string  optional    

Optional, prefills the form. Example: رضایی

pay_by   string  optional    

issuer or subject (default). Example: issuer

redirect_url   string  optional    

HTTPS URL to send the person back to when they finish. Example: https://partner.example/done

ttl_minutes   integer  optional    

Link lifetime, 5–1440 (default 60). Example: 120

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"
}
 

Request      

GET api/v1/form-submissions/{code}/dl

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

code   string     

16-char submission code. Example: consequatur

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"
}
 

Request      

GET api/v1/forms/{slug}/submissions

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

slug   string     

The form's slug. Example: consequatur

Query Parameters

status   string  optional    

draft|pending_payment|submitted|processing|completed|signed. Example: consequatur

submitted_after   string  optional    

ISO 8601. Example: consequatur

submitted_before   string  optional    

ISO 8601. Example: consequatur

has_contract   boolean  optional    

Only submissions that bridged to a contract. Example: false

per_page   integer  optional    

Max 100, default 20. Example: 17

Body Parameters

status   string  optional    

Example: draft

Must be one of:
  • draft
  • pending_payment
  • submitted
  • processing
  • completed
  • signed
submitted_after   string  optional    

value یک تاریخ معتبر نیست. Example: 2026-08-05T11:49:49

submitted_before   string  optional    

value یک تاریخ معتبر نیست. Example: 2026-08-05T11:49:49

has_contract   boolean  optional    

Example: false

page   integer  optional    

value باید حداقل 1 باشد. Example: 73

per_page   integer  optional    

value باید حداقل 1 باشد. value نباید بیشتر از 100 باشد. Example: 13

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"
}
 

Request      

GET api/v1/forms/{slug}/submissions/{code}

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

slug   string     

The form's slug. Example: consequatur

code   string     

16-char submission code. Example: consequatur

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:

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"
}
 

Request      

GET api/v1/forms/{slug}/submissions/{code}/pdf

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

slug   string     

The form's slug. Example: consequatur

code   string     

16-char submission code. Example: consequatur

Query Parameters

format   string  optional    

redirect|url|stream. Default redirect. Example: consequatur

ttl   integer  optional    

Signed URL lifetime seconds (60..86400). Default 86400. Example: 17

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());

Request      

POST api/v1/forms/{slug}/submissions

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

slug   string     

The form's slug. Example: consequatur

Body Parameters

data   object     

Map of field name → value.

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"
}
 

Request      

GET api/v1/forms

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

page   integer  optional    

Page number. Default 1. Example: 17

per_page   integer  optional    

Items per page. Max 100. Default 50. Example: 17

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"
}
 

Request      

GET api/v1/forms/{slug}

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

slug   string     

The form's public slug. Example: frm-e7465df28dd45785

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"
}
 

Request      

GET api/v1/forms/{slug}/stats

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

slug   string     

The form's public slug. Example: frm-e7465df28dd45785

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"
}
 

Request      

GET api/v1/forms/{slug}/schema

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

slug   string     

The form's public slug. Example: consequatur

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());

Request      

POST api/v1/forms/{slug}/verify-field

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

slug   string     

The form's public slug. Example: consequatur

Body Parameters

field   string     

The query field's name. Example: consequatur

values   object     

Raw values of the referenced input fields.

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"
}
 

Request      

GET api/v1/health

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

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
    }
}
 

Request      

GET api/v1/me

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

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:

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
        }
    }
}
 

Request      

GET api/v1/quota

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

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"
}
 

Request      

GET api/v1/cost-estimate

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

signers   integer     

Number of signers (1-20). Example: 3

discount_code   string  optional    

Optional discount code. Example: WELCOME10

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"
}
 

Request      

GET api/v1/contracts/{code}/signers

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

code   string     

16-char contract code. Example: consequatur

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"
}
 

Request      

GET api/v1/contracts/{code}/signers/{slug}

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

code   string     

16-char contract code. Example: consequatur

slug   string     

Per-signer slug. Example: DEF456GHJ789KLM

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());

Request      

POST api/v1/contracts/{code}/signers

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

code   string     

16-char contract code. Example: consequatur

Body Parameters

signers   string[]     

Array of signer objects.

first_name   string     

Example: consequatur

last_name   string     

Example: consequatur

mobile   string     

Iran mobile regex 09XXXXXXXXX. Example: 09121234567

national_code   string     

10-digit valid Iran national code. Example: consequatur

email   string  optional    

Optional. Example: qkunze@example.com

order   integer  optional    

Default 0 (parallel signing). Example: 17

signer_type   string  optional    

natural|legal. Default natural. Example: consequatur

legal_national_code   string  optional    

Required when signer_type=legal (11-digit company tax ID). Example: consequatur

legal_company_name   string  optional    

Required when signer_type=legal. Example: consequatur

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());

Request      

POST api/v1/contracts/{code}/signers/{slug}/resend

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

code   string     

16-char contract code. Example: consequatur

slug   string     

Per-signer slug. Example: consequatur

Remove signer

requires authentication

Removes a signer from a contract. Only allowed when:

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
 

Request      

DELETE api/v1/contracts/{code}/signers/{slug}

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

code   string     

16-char contract code. Example: consequatur

slug   string     

Per-signer slug. Example: consequatur

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:

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"
}
 

Request      

GET api/v1/templates

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

view_scope   string  optional    

self | shared_from_parent | all_org. Default self. Example: consequatur

active   boolean  optional    

Filter by active status. Example: false

page   integer  optional    

Example: 17

per_page   integer  optional    

Max 100. Default 20. Example: 17

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"
}
 

Request      

GET api/v1/templates/{id}

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the template. Example: consequatur

template   integer     

Template ID. Example: 17

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"
}
 

Request      

GET api/v1/templates/{template}/preview/{page}

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

template   integer     

Template ID. Example: 17

page   integer     

Page number (1-indexed). Example: 17

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"
}
 

Request      

GET api/v1/webhooks

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

active   boolean  optional    

Filter by active state. Example: false

page   integer  optional    

Example: 17

per_page   integer  optional    

Max 100. Example: 17

Body Parameters

active   boolean  optional    

Example: true

page   integer  optional    

value باید حداقل 1 باشد. Example: 73

per_page   integer  optional    

value باید حداقل 1 باشد. value نباید بیشتر از 100 باشد. Example: 13

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"
}
 

Request      

GET api/v1/webhooks/{id}

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the webhook. Example: AYbBkm7h8gnApxq66c0TVGHFAb

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"
}
 

Request      

GET api/v1/webhook-deliveries

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

subscription_id   string  optional    

Filter by subscription. Example: consequatur

event_name   string  optional    

Filter by event name. Example: consequatur

status   string  optional    

pending|delivered|failed|dead-letter Example: consequatur

page   integer  optional    

Example: 17

per_page   integer  optional    

Max 100. Example: 17

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"
}
 

Request      

GET api/v1/webhook-deliveries/{id}

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the webhook delivery. Example: AYbBkm7h8gnApxq66c0TVGHFAb

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:

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"
}
 

Request      

POST api/v1/webhooks

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

url   string     

HTTPS endpoint to receive deliveries. Max 500 chars. Example: https://example.com/webhooks/emza

events   string[]     

Event names from the allowed list. Use ["*"] for all.

active   boolean  optional    

When false, subscription is paused (no deliveries). Default true. Example: false

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"
    }
}
 

Request      

PATCH api/v1/webhooks/{id}

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

Webhook subscription ULID. Example: 01krh7z0e8a4n39gf2ary73xbj

Body Parameters

url   string  optional    

New endpoint URL (subject to same SSRF guard as create). Example: http://kunze.biz/iste-laborum-eius-est-dolor.html

events   string[]  optional    

Replaces the entire events list. Same allowed values as create.

active   boolean  optional    

Toggle the subscription on/off without deleting it. Example: false

rotate_secret   boolean  optional    

When true, issues a fresh secret (returned in response under secret + _warning). Example: false

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());

Request      

DELETE api/v1/webhooks/{id}

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the webhook. Example: AYbBkm7h8gnApxq66c0TVGHFAb

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());

Request      

POST api/v1/webhooks/{id}/test

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the webhook. Example: AYbBkm7h8gnApxq66c0TVGHFAb

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());

Request      

POST api/v1/webhook-deliveries/{id}/replay

Headers

Authorization        

Example: Bearer 42|abcdef1234567890abcdef1234567890abcdef

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the webhook delivery. Example: AYbBkm7h8gnApxq66c0TVGHFAb