# Templates (/templates)

Save reusable signer layouts once and initialize Esign sessions from them at runtime.



If you send the same document layout repeatedly — offer letters, NDAs,
agreements — templates let you define the signer layout once and reuse it.
A template stores the source `file_id` and an ordered `signers` array,
where each signer contains its own PDF elements and optional defaults
(sign type, verification settings, geofencing, and more).

## How templates work [#how-templates-work]

1. [Save a template](/api-reference/templates/saveTemplate) with the file
   and the per-signer element layout.
2. At runtime, call
   [Initialize Esign from template](/api-reference/templates/initializeFromTemplate)
   with the `template_id` and the actual signer details (name, email/mobile,
   reason).
3. The template's signer defaults are merged with the runtime signer data,
   then the normal Esign validation flow runs — the result is a real Esign
   session, identical to one created with `initialize-esign`.

## Ordering rules [#ordering-rules]

The template's `signers` array is **ordered**: the first template signer
maps to the first runtime signer in `initialize-from-template`, the second
to the second, and so on. The runtime signer count must match the template
signer count exactly, or the request fails with `signer_count_mismatch`.

Every template signer must contain at least one `signature` element.

## Save a template [#save-a-template]

```json title="POST /api/v1/templates/save"
{
  "name": "Employment Offer Template",
  "file_id": "file_onwEUoMxnWhewUzodZeW",
  "folder": "HR",
  "signers": [
    {
      "sign_type": "suresign",
      "auth_mode": 1,
      "max_retry": 1,
      "reason": "Employer sign",
      "verify_email": false,
      "verify_mobile": false,
      "elements": [
        { "x": 100, "y": 120, "page": 0, "alignment": "center", "type": "signature" }
      ]
    },
    {
      "sign_type": "suresign",
      "auth_mode": 1,
      "reason": "Employee sign",
      "verify_email": true,
      "verify_location": true,
      "location_source": "ip",
      "enforce_geofence": true,
      "latitude": 19.076,
      "longitude": 72.8777,
      "radius": 500,
      "elements": [
        { "x": 100, "y": 220, "page": 0, "alignment": "center", "type": "signature" }
      ]
    }
  ]
}
```

Only `name`, `file_id`, and `signers` (with `x`, `y`, `page`, `alignment`,
`type` per element) are required — everything else is an optional signer
default. `file_id` must belong to the authenticated user.

**Success response** (truncated):

```json
{
  "data": {
    "client_id": "template_xxx",
    "name": "Employment Offer Template",
    "file_id": "file_onwEUoMxnWhewUzodZeW",
    "folder": "HR",
    "signers": [
      {
        "sign_type": "suresign",
        "auth_mode": [1],
        "reason": "Employer sign",
        "elements": [
          {
            "field_id": "field_xxx",
            "x": 100,
            "y": 120,
            "page": 0,
            "type": "signature",
            "alignment": "center",
            "width": 50,
            "height": 50
          }
        ]
      }
    ]
  },
  "status_code": 200,
  "message": "Success",
  "success": true
}
```

**Error responses:**

| HTTP Status | Message Code   | Description                                               |
| ----------- | -------------- | --------------------------------------------------------- |
| `400`       | `bad_request`  | Template signer validation failed.                        |
| `401`       | `unauthorized` | Invalid or expired access token.                          |
| `404`       | `not_found`    | The provided file ID does not exist for the current user. |

## `field_id` and `element_values` [#field_id-and-element_values]

When a template is saved, each element gets an auto-generated `field_id`
(returned in the save/update response). At runtime, you can fill element
values per signer by referencing those IDs:

```json
{
  "template_id": "template_xxx",
  "signers": [
    {
      "full_name": "Employer A",
      "email": "employer@example.com",
      "reason": "Employer sign",
      "element_values": {
        "field_xxx": "INV-1001",
        "field_yyy": "now"
      }
    }
  ]
}
```

`element_values` only overrides an element's `value` — position and type
always come from the template. Signature `value` asset IDs are stripped at
initialization unless the saved field sets `allow_wet_signature: true`.

## Initialize an Esign from a template [#initialize-an-esign-from-a-template]

```json title="POST /api/v1/esign/initialize-from-template"
{
  "template_id": "template_xxx",
  "sign_type": "suresign",
  "auth_mode": 1,
  "max_retry": 1,
  "folder": "HR",
  "allow_download": true,
  "expiry_seconds": 7200,
  "redirect_url": "https://example.com/esign/complete",
  "backend": "emudhra",
  "stamp": {
    "amount": "100.00",
    "state_code": "MH",
    "article_id": "article_001",
    "first_party_name": "Employer A",
    "second_party_name": "Employee B"
  },
  "email_template_overrides": {
    "company_name": "Acme Corp",
    "legal_name": "Acme Corporation Pvt Ltd.",
    "support_email": "support@acme.com"
  },
  "signers": [
    {
      "full_name": "Employer A",
      "email": "employer@example.com",
      "reason": "Employer sign",
      "face_ref_file_id": "file_faceReferenceImage",
      "face_match_threshold": 80,
      "face_max_attempts": 5,
      "notification": { "invite": true, "completion": true, "whatsapp": true },
      "element_values": { "field_xxx": "INV-1001", "field_yyy": "now" }
    },
    {
      "full_name": "Employee B",
      "email": "employee@example.com",
      "mobile": "9123456789",
      "country_code": "+91",
      "reason": "Employee sign",
      "name_match": true,
      "notification": { "invite": false, "completion": true, "whatsapp": false },
      "element_values": { "field_zzz": "Employee B" }
    }
  ]
}
```

### Notes [#notes]

* Only `template_id` and `signers` are required; signers must be supplied
  in the same order as the template signer array, with a matching count.
* `full_name` should be supplied, `reason` must be supplied unless a
  default reason is already saved in the template signer, and at least one
  of `email` or `mobile` must be present (`country_code` is required when
  `mobile` is provided).
* `expiry_seconds`: optional, min = 3600 (1 hour), max = 2592000 (30 days).
* `name_match` is optional and only applies to Aadhaar signers; it is
  ignored for non-Aadhaar signers.
* `face_ref_file_id` enables face verification for the runtime signer —
  upload the JPEG or PNG through
  [Upload asset](/api-reference/files/uploadAsset) and use the returned
  `client_id`. `face_match_threshold` (50–100, default 80) and
  `face_max_attempts` (1–10, default 5) apply only when it is provided.
* `stamp`: optional e-stamping. Use either the amount-based reservation
  flow (`amount` + `state_code` + `article_id`, required together) or pass
  pre-reserved `stamp_instance_ids`.
* `notification` works exactly as in `initialize-esign` — see
  [Getting started](/getting-started#notification).

The response is a full Esign session: each signer includes an
`invite_url`, `token`, merged element list, and verification state.

**Error responses:**

| HTTP Status | Message Code            | Description                                                    |
| ----------- | ----------------------- | -------------------------------------------------------------- |
| `400`       | `signer_count_mismatch` | Runtime signer count does not match the saved template.        |
| `400`       | `bad_request`           | Validation failed after template and runtime signer merge.     |
| `401`       | `unauthorized`          | Invalid or expired access token.                               |
| `404`       | `not_found`             | The provided template ID or referenced file ID does not exist. |

## Updating a template [#updating-a-template]

[Update template](/api-reference/templates/updateTemplate) takes the
template `client_id`; `name`, `folder`, `file_id`, and `signers` are all
optional:

```json title="PUT /api/v1/templates/update"
{
  "client_id": "template_xxx",
  "name": "Employment Offer Template v2",
  "folder": "HR Updated",
  "signers": [
    {
      "sign_type": "suresign",
      "max_retry": 2,
      "reason": "Employer sign updated",
      "accept_virtual_sign": true,
      "elements": [
        { "x": 140, "y": 120, "page": 0, "alignment": "center", "type": "signature" }
      ]
    },
    {
      "sign_type": "pfx",
      "auth_mode": [1, 2],
      "max_retry": 3,
      "pfx_id": "pfx_yyy",
      "reason": "Employee sign updated",
      "verify_email": true,
      "verify_mobile": true,
      "elements": [
        { "x": 140, "y": 220, "page": 0, "alignment": "center", "type": "signature" }
      ]
    }
  ]
}
```

* If `signers` is provided, it **replaces** the saved blueprint completely
  and regenerates every `field_id` — update any stored `element_values`
  mappings accordingly.
* If `signers` is omitted, the saved signers are preserved.
* If `file_id` is provided, it must belong to the authenticated user.

**Error responses:**

| HTTP Status | Message Code   | Description                                                   |
| ----------- | -------------- | ------------------------------------------------------------- |
| `400`       | `bad_request`  | Template signer validation failed when `signers` is provided. |
| `401`       | `unauthorized` | Invalid or expired access token.                              |
| `404`       | `not_found`    | The template or provided file ID does not exist.              |

## Signer defaults available in templates [#signer-defaults-available-in-templates]

Per template signer you can preset:

* `sign_type` (`aadhaar`, `suresign`, or `pfx`), `auth_mode`, `max_retry`,
  `pfx_id`, `reason`
* `verify_email` / `verify_mobile`, `accept_virtual_sign`
* Location controls: `verify_location`, `location_source` (`device` or
  `ip`), and geofencing via `enforce_geofence` + `latitude` + `longitude` +
  `radius` (the last three are required together when geofencing is on)

Runtime-only signer options — `name_match`, face verification
(`face_ref_file_id`, `face_match_threshold`, `face_max_attempts`), and
`notification` — are passed in `initialize-from-template`.
