Skip to main content
This guide covers creating a signing request, attaching a template or document, and inviting recipients to sign.

Steps

  1. Create or select a template
  2. Create a signing request referencing the template
  3. Add recipients with required information (first name, last name, email)
  4. Optionally add form fields with percentage-based positioning
  5. Send the request via email or embed the signing view

Recipient Schema (Required Fields)

Breaking Change: Recipients now require first_name and last_name separately instead of a single name field.
Each recipient must include:
  • first_name (required) - Recipient’s first name
  • last_name (required) - Recipient’s last name
  • email (required) - Valid email address
  • designation (required) - Role: "Signer", "CC", or "Approver" (currently only "Signer" is fully supported)
  • order (optional) - Signing order for sequential workflows
Optional fields:
  • phone_number, street_address, city, state_province, postal_code, country, title, company
  • custom_fields - Object with custom key-value pairs

Create a signing request from a template (API)

Endpoint: POST /signing-requests
Example curl (create request from template):
curl -X POST "https://api.firma.dev/functions/v1/signing-request-api/signing-requests" \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "template_id": "tmpl_123",
    "name": "NDA - Acme Corp",
    "recipients": [
      { 
        "first_name": "Alice",
        "last_name": "Johnson",
        "email": "alice@example.com",
        "designation": "Signer",
        "order": 1
      }
    ]
  }'
Successful response (201) returns a Document resource including id (the signing_request_id) and document_url where appropriate.
Template fields and users can be partially patched when creating the signing request. This allows you to create the signing request with only the updated fields.

Create a signing request (server example) — Node (fetch)

const fetch = require('node-fetch')

async function createFromTemplate(templateId) {
  const resp = await fetch('https://api.firma.dev/functions/v1/signing-request-api/signing-requests', {
    method: 'POST',
    headers: {
      'Authorization': `${process.env.FIRMA_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      template_id: templateId,
      name: 'NDA - Acme Corp',
      recipients: [
        {
          first_name: 'Alice',
          last_name: 'Johnson',
          email: 'alice@example.com',
          designation: 'Signer',
          order: 1
        }
      ]
    })
  })
  if (!resp.ok) throw new Error('failed to create')
  const body = await resp.json()
  return body // contains id, document_url, recipients, etc.
}

Create a signing request (server example) — Python (requests)

import os
import requests

def create_from_template(template_id):
    url = 'https://api.firma.dev/functions/v1/signing-request-api/signing-requests'
    resp = requests.post(url, headers={
        'Authorization': f" {os.environ['FIRMA_API_KEY']}",
        'Content-Type': 'application/json'
    }, json={
        'template_id': template_id,
        'name': 'NDA - Acme Corp',
        'recipients': [
            {
                'first_name': 'Alice',
                'last_name': 'Johnson',
                'email': 'alice@example.com',
                'designation': 'Signer',
                'order': 1
            }
        ]
    })
    resp.raise_for_status()
    return resp.json()

Adding form fields (percentage-based positioning)

Critical: All field position coordinates (x, y, width, height) must be percentages (0-100) relative to page dimensions, not pixels. The page_number field is required.
When creating a signing request directly (POST /signing-requests) or updating one, you can add form fields:

Field positioning example

{
  "name": "Contract with Fields",
  "recipients": [
    {
      "first_name": "Bob",
      "last_name": "Smith",
      "email": "bob@example.com",
      "designation": "Signer",
      "order": 1
    }
  ],
  "fields": [
    {
      "type": "signature",
      "required": true,
      "recipient_id": "recipient_uuid_here",
      "page_number": 1,
      "position": {
        "x": 10,
        "y": 80,
        "width": 30,
        "height": 8
      }
    },
    {
      "type": "text",
      "required": true,
      "recipient_id": "recipient_uuid_here",
      "variable_name": "company_name",
      "page_number": 1,
      "position": {
        "x": 50,
        "y": 20,
        "width": 40,
        "height": 5
      }
    },
    {
      "type": "date",
      "required": true,
      "page_number": 1,
      "date_signing_default": true,
      "position": {
        "x": 10,
        "y": 90,
        "width": 20,
        "height": 4
      }
    }
  ]
}

Field types

  • signature - Signature field
  • text - Single-line text input
  • date - Date picker
  • checkbox - Checkbox
  • dropdown - Dropdown selector (requires dropdown_options)
  • initials - Initials field
  • image - Image field

Positioning guidelines

The coordinate system uses percentages for responsive scaling:
  • x: 0 (left edge) to 100 (right edge)
  • y: 0 (top edge) to 100 (bottom edge)
  • width: percentage of page width (e.g., 30 = 30% width)
  • height: percentage of page height (e.g., 8 = 8% height)
For a US Letter page (8.5” × 11”), use these rough conversions:
  • 1 inch ≈ 11.76% width
  • 1 inch ≈ 9.09% height

Updating signing requests

Before a signing request is sent, you can update its details using the API. The API provides two methods:
Cannot update after sending: Once a signing request is sent, it cannot be modified. Updates only work for requests with status not_sent.

Comprehensive update (PUT)

Use comprehensive-update-signing-request for complex updates involving multiple sections. When to use:
  • Updating multiple recipients at once
  • Deleting recipients (with field reassignment/deletion)
  • Updating fields and reminders together
  • Making coordinated changes across multiple sections
Structure: All sections are optional, but at least one must be provided:
  • signing_request_properties - Update name, description, document, expiration, settings
  • recipients - Upsert recipients (include id to update, omit to create)
  • deleted_recipients - Delete recipients with field_action (delete or reassign fields)
  • fields - Upsert fields (include id to update, omit to create)
  • reminders - Upsert reminders (include id to update, omit to create)
Requirements:
  • ✅ Can update multiple sections in one request
  • ✅ Supports recipient deletion with field handling
  • ✅ Only works before the request is sent
Example (Node.js):
const response = await fetch(`https://api.firma.dev/functions/v1/signing-request-api/signing-requests/${signingRequestId}`, {
  method: 'PUT',
  headers: {
    'Authorization': `${process.env.FIRMA_API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    signing_request_properties: {
      name: 'Updated Contract Name',
      expiration_hours: 72
    },
    recipients: [
      {
        id: 'rec1-e89b-12d3-a456-426614174000', // Include id to update existing
        first_name: 'Jane',
        last_name: 'Smith',
        email: 'jane@example.com',
        designation: 'Signer',
        order: 1
      },
      {
        id: 'temp_sdjkdsjkfskfjskd-426614174000', // Include temp id to create new recipient
        first_name: 'Bob',
        last_name: 'Johnson',
        email: 'bob@example.com',
        designation: 'Signer',
        order: 2
      }
    ],
    fields: [
      {
        id: 'field1-e89b-12d3-a456-426614174000',
        type: 'signature',
        position: { x: 15, y: 85, width: 30, height: 10 },
        page_number: 1,
        required: true,
        recipient_id: 'rec1-e89b-12d3-a456-426614174000'
      },
	  {
        id: 'field2-e89b-12d3-a456-426614174000',
        type: 'signature',
        position: { x: 15, y: 65, width: 30, height: 10 },
        page_number: 1,
        required: true,
        recipient_id: 'temp_sdjkdsjkfskfjskd-426614174000'
      }
    ]
  })
});

const updatedRequest = await response.json();

Partial update (PATCH)

Use partially-update-signing-request when updating specific properties or a single recipient. When to use:
  • Updating name, description, or settings
  • Adding or updating one recipient at a time
  • Making targeted changes without affecting other data
Important: Cannot update both properties AND a recipient in the same request. Choose one:
  • Update properties only (name, description, document, expiration_hours, settings)
  • OR update/create a single recipient
Benefits:
  • ✅ Only send the fields you want to change
  • ✅ More efficient for small changes
  • ✅ Other fields remain unchanged
  • ✅ Safer for concurrent edits
Example (Node.js):
// Update a single recipient
const response = await fetch(`https://api.firma.dev/functions/v1/signing-request-api/signing-requests/${signingRequestId}`, {
  method: 'PATCH',
  headers: {
    'Authorization': `${process.env.FIRMA_API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    recipient: {
      id: 'rec123-e89b-12d3-a456-426614174000', // Include id to update existing recipient
      first_name: 'John',
      last_name: 'Updated',
      email: 'john.updated@example.com',
      designation: 'Signer',
      order: 1
    }
  })
});

const updatedRequest = await response.json();
Example (Python):
import requests
import os

## Update only properties (name and expiration)
response = requests.patch(
    f"https://api.firma.dev/functions/v1/signing-request-api/signing-requests/{signing_request_id}",
    headers={
        "Authorization": f"{os.getenv('FIRMA_API_KEY')}",
        "Content-Type": "application/json"
    },
    json={
        "name": "Updated Contract Name",
        "expiration_hours": 72
    }
)

updated_request = response.json()

Choosing between PUT and PATCH

ScenarioUseReason
Complete signing request rebuildPUTFull replacement with all sections (properties, recipients, fields, reminders)
Update name or settings onlyPATCHMore efficient, preserves recipients and fields
Add/update one recipientPATCHSingle recipient mode, other recipients unchanged
Update multiple recipientsPUTCan upsert multiple recipients in one request
Delete recipients with field reassignmentPUTSupports deleted_recipients with field_action
Change document and all fieldsPUTMajor structural changes
Update custom fieldsPATCHPreserves core signing request
Replace all recipientsPUTClean slate approach
Important: Both update methods only work before the signing request is sent. Once sent, the signing request becomes immutable to prevent tampering with active signature workflows.
Best practices:
  • Update signing requests before calling /send
  • Validate recipient data before updating
  • Use PATCH for incremental changes
  • Implement retry logic with exponential backoff

Sending (email invites)

Once you have a signing request ID (and have made any necessary updates), call POST /signing-requests/{signing_request_id}/send to send emails to all recipients.

Example

curl -X POST "https://api.firma.dev/functions/v1/signing-request-api/signing-requests/sr_abc123/send" \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json"
The /send endpoint validates that all recipients have required information (first_name, last_name, email) and that any fields with variable_name have corresponding data in recipient records.

Embedding the signing view

The public signing UI is available at the pattern: https://app.firma.dev/signing/{signing_request_user_id} Notes:
  • The signing_request_user_id is typically returned as part of the recipients object or as a per-recipient token; check the response from GET /signing-requests/id for recipient-level signing links or tokens.
  • If the API returns a direct document_url or embed_url, use that. If not, generate an ephemeral signing link server-side and return it to the frontend.

Example — fetch signing details and render iframe

### Getting the signing URL

- The `signing_request_user_id` is returned in the `recipients` array after creation
- Alternatively, fetch signing request details via GET `/signing-requests/{id}` to retrieve per-recipient signing links

### Examplefetch signing details and render iframe

```js
// fetch signing request
const r = await fetch('/internal/signing-request/' + signingRequestId)
const json = await r.json()

// get recipient signing URL
const recipient = json.recipients[0]
const signingUrl = recipient.signing_url || `https://app.firma.dev/signing/${recipient.id}`

// render iframe
const iframe = document.createElement('iframe')
iframe.src = signingUrl
iframe.style.width = '100%'
iframe.style.height = '900px'
iframe.frameBorder = '0'
iframe.allow = 'camera;microphone;clipboard-write'
document.getElementById('signing-root').appendChild(iframe)

Edge cases & tips

  • Signing order: Ensure recipients have sequential order values (1, 2, 3…) for sequential signing workflows
  • Audit trail: Download the audit trail via GET /signing-requests/{id}/tracking to see all user actions
  • Download completed PDF: Use GET /signing-requests/{id}/download after completion
  • Webhooks: Subscribe to events like signing_request.completed instead of polling (see Webhooks guide)

Next steps

  • For sequential signing workflows with multiple signers, ensure each recipient has a sequential order value (1, 2, 3…).
  • For audit and compliance, download the final PDF via GET /signing-requests/signing_request_id/download after completion.
  • Use webhooks (see Webhooks Guide) to react to signing events instead of polling.