# Home Source: https://developers.luminpdf.com/index Add document, eSignature, and PDF workflows to your product with the Lumin API, guides, and MCP.

Power your apps with Lumin

Add document, eSignature, and PDF workflows to your product in minutes. We take care of storage, compliance, and security, you ship the features your users need.

Get Started
Getting started

What you can build

Everything you need to embed document workflows, signing, and automation into your stack.

API Reference

The complete technical reference for agreements, signature requests, templates, documents, Workspaces, webhooks, and more.

Explore the API →

Guides & Walkthroughs

From your first API call to production-ready OAuth flows and real-time webhooks, follow along step by step.

Browse guides →

Postman Collection

Import our ready-made collection and start testing every Lumin endpoint in seconds.

Open in Postman →

Webhooks

Subscribe to document and signature events, verify signatures, and handle retries with the same patterns you use in production.

Webhook overview →

Illustration for Lumin MCP

Meet Lumin MCP

Let AI assistants read, write, and manage your Lumin documents and signature requests. Plug in ChatGPT, Claude, or Cursor and control your agreement workflows through natural language.

Get started with Lumin MCP

Need a quick start?

Jump to the guides and tools teams use first when integrating Lumin.

Book a demo

Resources

Support

The document platform where editing, signing, and automation come together.

© 2026 Lumin, Inc.

Terms of use

Developers

Lumin

API Sections

# Agreements Source: https://developers.luminpdf.com/tabs/api-reference/api/agreements/agreements Generate and download AgreementGen documents from templates. The Agreements API powers [**AgreementGen**](https://www.luminpdf.com/app/generate) — Lumin's automated document generation engine. Use it to create personalized, ready-to-download documents from AgreementGen templates without any manual editing. ## Key concepts * **AgreementGen Template** (`ag_` prefix) — a template designed specifically for automated document generation. Templates contain placeholder fields and optional variables/collections for dynamic data. * **Agreement** — a generated document produced by filling an AgreementGen template with your data. Once created, the document can be downloaded as a PDF. * **Collections** — groups of repeating variables within a template, commonly used for line items, table rows, or lists of entries in the generated document. ## Typical workflow 1. **Find a template** — use the [Templates API](/tabs/api-reference/api/templates/templates) to list available templates and get field details for an `ag_` template. 2. **Create an Agreement** — fill in the template fields (and collections if applicable) to generate the document. 3. **Download the file** — retrieve the generated PDF as a signed URL or binary data. ## Available endpoints Create a new AgreementGen document from an AgreementGen template. Obtain a downloadable file or binary PDF data from an AgreementGen document. # Create Agreement Source: https://developers.luminpdf.com/tabs/api-reference/api/agreements/create-agreement POST /agreements Create a new AgreementGen document from a AgreementGen template. This endpoint requires the following scope: `agreements` # Get Agreement File Source: https://developers.luminpdf.com/tabs/api-reference/api/agreements/get-agreement-file GET /agreements/{agreement_id}/file Obtain a downloadable file or binary PDF data from an AgreementGen document. This endpoint requires the following scope: `agreements` # Add Password Source: https://developers.luminpdf.com/tabs/api-reference/api/documents/add-password tabs/api-reference/lumin-openapi.yaml POST /documents/add-password Add or change password protection on a PDF document and return the encrypted file as a temporary download URL. This endpoint requires the following scope: `pdf:files` # Compress Document Source: https://developers.luminpdf.com/tabs/api-reference/api/documents/compress-document tabs/api-reference/lumin-openapi.yaml POST /documents/compress Compress a PDF document to reduce its file size and return a temporary download URL. This endpoint requires the following scope: `pdf:files` # Upload Document Source: https://developers.luminpdf.com/tabs/api-reference/api/documents/create-document tabs/api-reference/lumin-openapi.yaml POST /documents Create and save a document in the Workspace using file upload or a PDF template. This endpoint supports two methods, selected by the `method` field: * **`file-upload`** - import an existing file from a URL or multipart upload * **`template`** - generate a PDF from a Lumin PDF template and optionally prefill fields Copy examples from the dropdown to quickly get the sample request body for each method. The response returns a `document_id` in the `id` field. Use that ID with PDF operation endpoints such as [Merge Documents](/tabs/api-reference/api/documents/merge-documents) or [Compress Document](/tabs/api-reference/api/documents/compress-document).

Upload from file

Set `method` to `file-upload` and provide the file via `document_data.file_url` or a multipart `document_data.file` upload. ### Supported file formats PDF, DOCX, XLSX, PPTX, DOC, XLS, PNG, JPEG/JPG ### File size limits | Plan | Max size | | ---- | -------- | | Free | 20 MB | | Paid | 200 MB |

Create from template

Set `method` to `template`, provide a `pdf_` template ID, and optionally prefill form fields. 1. List templates with [List Templates](/tabs/api-reference/api/templates/list-templates) and find a template whose ID starts with `pdf_`. 2. Optionally inspect fields with [Get Template Details](/tabs/api-reference/api/templates/get-template-details). Only PDF templates (`pdf_` prefix) are supported for document creation. | Field | Description | | --------------------------- | ---------------------------------------------------------------------------------------------------- | | `document_data.template_id` | PDF template ID (must start with `pdf_`). | | `document_data.fields` | Optional key–value pairs for form fields defined in the template. Supports text and checkbox fields. | This endpoint requires the following scope: `pdf:files` # Documents Source: https://developers.luminpdf.com/tabs/api-reference/api/documents/documents Create, upload, and process PDF documents in the Workspace. The Documents API lets you create and store PDF documents in your Lumin Workspace, and run PDF operations such as merge, compress, split, password protection, summarization, and translation. ## Key concepts * **Document** — a file stored in a Lumin Workspace. Once created, it can be viewed, shared, annotated, or used in other workflows. * **Upload (file-upload method)** — import an existing file by providing a file URL or uploading directly via multipart form data. * **Template (template method)** — generate a PDF by filling in fields from a PDF template (`pdf_` prefix templates). * **Location** — where the document is saved: the Workspace's shared document list, a specific Space, or the user's Personal document list. You can optionally specify a folder. * **PDF operations** — process documents by Lumin document ID or publicly accessible file URL. Most operations return a temporary `signed_url` that expires after 30 minutes. To persist results, download the file and re-upload via [Upload Document](/tabs/api-reference/api/documents/create-document). ## Supported file formats PDF, DOCX, XLSX, PPTX, DOC, XLS, PNG, JPEG/JPG ## File size limits | Plan | Max size | | ---- | -------- | | Free | 20 MB | | Paid | 200 MB | ## Create documents Import and save a document from a file URL or direct file upload. Create and save a PDF document from a PDF template. ## PDF operations Combine 2–20 PDF or image files into a single PDF. Reduce PDF file size with standard or maximum compression. Extract page ranges or split a PDF into equal-sized parts. Add or change password protection on a PDF. Remove password protection from a PDF. Generate an AI-powered summary of a PDF's text content. Translate a PDF into a target language while preserving layout. # Merge Documents Source: https://developers.luminpdf.com/tabs/api-reference/api/documents/merge-documents tabs/api-reference/lumin-openapi.yaml POST /documents/merge Merge multiple documents into a single PDF file and return a temporary download URL. This endpoint requires the following scope: `pdf:files` # Remove Password Source: https://developers.luminpdf.com/tabs/api-reference/api/documents/remove-password tabs/api-reference/lumin-openapi.yaml POST /documents/remove-password Remove password protection from a PDF document and return the unprotected file as a temporary download URL. This endpoint requires the following scope: `pdf:files` # Split Document Source: https://developers.luminpdf.com/tabs/api-reference/api/documents/split-document tabs/api-reference/lumin-openapi.yaml POST /documents/split Split or extract pages from a PDF document into one or more output files and return a temporary download URL. This endpoint requires the following scope: `pdf:files` # Summarize Document Source: https://developers.luminpdf.com/tabs/api-reference/api/documents/summarize-document tabs/api-reference/lumin-openapi.yaml POST /documents/summarize Generate an AI-powered summary of a PDF document's text content. This endpoint requires the following scope: `pdf:files` # Translate Document Source: https://developers.luminpdf.com/tabs/api-reference/api/documents/translate-document tabs/api-reference/lumin-openapi.yaml POST /documents/translate Translate a PDF document into a target language while preserving the original formatting, layout, and images. When `output_format` is `html`, sanitize the returned content before injecting it into a DOM. Use a trusted HTML sanitizer such as [DOMPurify](https://github.com/cure53/DOMPurify) to prevent XSS. This endpoint requires the following scope: `pdf:files` # Lumin API Reference Source: https://developers.luminpdf.com/tabs/api-reference/api/lumin-api-reference The Lumin API Reference provides tools to integrate document workflows, eSignatures, and automation into your applications. The Lumin REST API exposes document workflows as JSON endpoints under `https://api.luminpdf.com/v1`. Send signature requests, fill templates, generate agreements, and manage workspace resources from your backend. **Start here:** [Quickstart](/tabs/guides/getting-started/quickstart) · [Authentication guide](/tabs/guides/authentication/overview) · [Changelog](/tabs/changelog) ## Authentication Every request must include credentials. Most endpoints accept either method. Pass your key in the `Authorization` header: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} Authorization: API-key ``` Or send it as `X-API-Key: `. | | | | --------------- | ----------- | | Security scheme | `apiKey` | | Header | `X-API-Key` | Best for server-to-server integrations and backend jobs. See [API Keys](/tabs/guides/authentication/api-key). Pass an access token in the `Authorization` header: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} Authorization: Bearer ``` | | | | --------------- | ------------------ | | Security scheme | `http` (bearer) | | Flow | Authorization code | Scopes control what each token can access — for example `sign:requests` for signature requests or `templates` for template operations. See [OAuth 2.0](/tabs/guides/authentication/oauth2). ## Support * **API support:** [integration@luminpdf.com](mailto:integration@luminpdf.com) * **Help center:** [help.luminpdf.com](https://help.luminpdf.com) * **Terms of use:** [luminpdf.com/terms-of-use](https://www.luminpdf.com/terms-of-use/) # Cancel Signature Request Source: https://developers.luminpdf.com/tabs/api-reference/api/signature-requests/cancel-signature-request PUT /signature_request/cancel/{signature_request_id} Cancel Signature Request by id. This endpoint requires the following scope: `sign:requests` # Create Signing Session Source: https://developers.luminpdf.com/tabs/api-reference/api/signature-requests/create-signing-session POST /signature_request/{signature_request_id}/signing-session Returns a short-lived sign_url for embedding the Lumin signing experience with the Embed Signing SDK. This endpoint requires the following scope: `sign:requests` # Get Signature Request Source: https://developers.luminpdf.com/tabs/api-reference/api/signature-requests/get-signature-request GET /signature_request/{signature_request_id} Returns the information of the signature request. This endpoint requires one of the following scopes: `sign:requests.read` or `sign:requests` # Get Signature Request File Source: https://developers.luminpdf.com/tabs/api-reference/api/signature-requests/get-signature-request-file GET /signature_request/{signature_request_id}/file Obtain the file of the Signature Request by id — agreement, CoC, or merged PDF. This endpoint requires one of the following scopes: `sign:requests.read` or `sign:requests` # Get Signing Link Source: https://developers.luminpdf.com/tabs/api-reference/api/signature-requests/get-signing-link POST /signature_request/{signature_request_id}/signing-link Returns a signing link for an existing signature request for embedding in your applications. This endpoint requires the following scope: `sign:requests` # Send Reminder Emails Source: https://developers.luminpdf.com/tabs/api-reference/api/signature-requests/send-reminder-emails POST /signature_request/remind/{signature_request_id} Send reminder emails to selected signers whose status is NEED_TO_SIGN. This endpoint requires the following scope: `sign:requests` # Send Signature Request Source: https://developers.luminpdf.com/tabs/api-reference/api/signature-requests/send-signature-request POST /signature_request/send Creates and sends a new signature request with the submitted documents. This endpoint requires the following scope: `sign:requests` The `verification` field on signers requires a **Digital Trust license** enabled at the Workspace level. If your Workspace is not licensed, the request will return a `403 verification_not_licensed` error. Contact your Lumin account manager to enable Digital Trust. # Send Signature Request from Template Source: https://developers.luminpdf.com/tabs/api-reference/api/signature-requests/send-signature-request-from-template POST /signature_request/send-from-template Creates and sends a new signature request from a template with the submitted template data. This endpoint requires the following scopes: `sign:requests` and `templates` The `verification` field on signers requires a **Digital Trust license** enabled at the Workspace level. If your Workspace is not licensed, the request will return a `403 verification_not_licensed` error. Contact your Lumin account manager to enable Digital Trust. # Signature Requests Source: https://developers.luminpdf.com/tabs/api-reference/api/signature-requests/signature-requests Create, send, update, and manage signature requests; download files and signing links. The Signature Requests API lets you build complete eSignature workflows — from creating and sending requests to tracking signer progress and downloading signed documents. ## Key concepts * **Signature Request** — a document sent to one or more signers for legally-binding electronic signatures. * **Signer** — a recipient who must review and sign the document. Each signer receives a unique signing link. * **Signing Link** — a URL that opens the hosted signing experience for a specific signer (redirect flows). * **Signing Session** — a short-lived `sign_url` for embedding the signing experience inline with the Embed Signing SDK. * **Template** — a reusable document with pre-defined signature fields. Use `Send from Template` to create requests without re-uploading files each time. ## Typical workflow 1. **Send** a signature request with documents and signer details (or use a template). 2. **Deliver signing** to each signer — use [Create Signing Session](/tabs/api-reference/api/signature-requests/create-signing-session) for embedded signing, or [Get Signing Link](/tabs/api-reference/api/signature-requests/get-signing-link) for redirect flows. 3. **Monitor** the request status via the [Get Signature Request](/tabs/api-reference/api/signature-requests/get-signature-request) endpoint or [webhooks](/tabs/guides/webhooks/overview). 4. **Download** the signed agreement, Certificate of Completion (CoC), or merged PDF once all signers have completed. ## Available endpoints Returns the information of the signature request. Update the due date of an existing signature request. Creates and sends a new signature request with the submitted documents. Creates and sends a new signature request from a template with the submitted template data. Returns a hosted signing URL for redirect flows. Returns a short-lived sign\_url for the Embed Signing SDK. Send reminder emails to signers who still need to sign. Cancel a signature request. Obtain agreement, CoC, or merged PDF via signed URL or binary response. # Update Signature Request Source: https://developers.luminpdf.com/tabs/api-reference/api/signature-requests/update-signature-request PATCH /signature_request/{signature_request_id} Update the due date of an existing signature request. This endpoint requires the following scope: `sign:requests` # Generate Document from Template Source: https://developers.luminpdf.com/tabs/api-reference/api/templates/generate-document-from-template POST /templates/{template_id}/generate-document Creates a downloadable document from an existing Lumin template. This endpoint requires the following scope: `templates` # Get Template Details Source: https://developers.luminpdf.com/tabs/api-reference/api/templates/get-template-details GET /templates/{template_id} Returns essential template details for a specific template. This endpoint requires the following scope: `templates` # List Templates Source: https://developers.luminpdf.com/tabs/api-reference/api/templates/list-templates GET /templates Returns a paginated list of templates in a Lumin Workspace. This endpoint requires the following scope: `templates` ```bash Request theme={"theme":{"light":"github-light","dark":"github-dark"}} curl --request GET \ --url 'https://api.luminpdf.com/v1/templates?page=1&limit=25' \ --header 'X-API-Key: ' \ --header 'X-Lumin-API-Version: 1.1' ``` ```json Response theme={"theme":{"light":"github-light","dark":"github-dark"}} { "page": 1, "limit": 25, "total_count": 3, "data": [ { "template_id": "sign_123456", "type": "pdf", "name": "Mutual NDA", "created_at": 1748456885430, "updated_at": 1748456885430 } ] } ``` # Templates Source: https://developers.luminpdf.com/tabs/api-reference/api/templates/templates List templates, get details, and generate documents. The Templates API gives you access to reusable document templates stored in your Lumin Workspace. Templates contain pre-defined fields and layouts, allowing you to generate personalized documents or signature requests without starting from scratch each time. ## Key concepts * **Template** — a reusable document layout with placeholder fields (e.g., name, date, amount) that can be filled programmatically. * **Sign Template** (`sign_` prefix), created from the [Lumin Sign site](https://www.luminpdf.com/app/sign), designed for eSignature workflows. Use with [Send Signature Request from Template](/tabs/api-reference/api/signature-requests/send-signature-request-from-template) to create signature requests. * **AgreementGen Template** (`ag_` prefix), created from the [Lumin AgreementGen site](https://www.luminpdf.com/app/generate), designed for automated document generation via the [Agreements API](/tabs/api-reference/api/agreements/agreements) or embedding in signature requests. Use with [Send Signature Request from Template](/tabs/api-reference/api/signature-requests/send-signature-request-from-template) to create signature requests. * **PDF Template** (`pdf_` prefix), created from the [Lumin site](https://www.luminpdf.com/app/pdf), designed for generating standalone PDF documents via [Generate Document from Template](/tabs/api-reference/api/templates/generate-document-from-template) or the Documents API. ## Typical workflow 1. **List** available templates in your Workspace to find the template you need. 2. **Get details** for a specific template to retrieve its fields, roles, and collections. 3. **Generate** a document or signature request by filling in the template fields with your data. ## Available endpoints Returns a paginated list of templates in a Lumin Workspace. Returns essential template details for a specific template. Creates a downloadable PDF document from an existing Lumin template. # Get User Information Source: https://developers.luminpdf.com/tabs/api-reference/api/users/get-user-information GET /user/info Get information of current user. This endpoint requires the following scope: `profile.read` # Users Source: https://developers.luminpdf.com/tabs/api-reference/api/users/users Retrieve information about the authenticated user. The Users API returns profile information for the currently authenticated user — the person or service account associated with the API key or OAuth token making the request. ## What you get The `Get User Information` endpoint returns: * **User ID** and **email address** * **Display name** * **Workspace** the user belongs to * **Role** within the Workspace (owner, admin, or member) This is useful for verifying which account is authenticated, displaying user context in your application, or routing logic based on the user's role. ## Available endpoints Get information of the current user. # Create Space Source: https://developers.luminpdf.com/tabs/api-reference/api/workspaces/create-space POST /workspaces/spaces Create a new Space within the authorized Workspace. This endpoint requires the following scope: `workspaces` # Get Workspace Information Source: https://developers.luminpdf.com/tabs/api-reference/api/workspaces/get-workspace-information GET /workspaces/info Return information of the authorized Workspace. This endpoint requires one of the following scopes: `workspaces.read` or `workspaces` # Get Workspace Members Source: https://developers.luminpdf.com/tabs/api-reference/api/workspaces/get-workspace-members GET /workspaces/members Return a paginated list of members in a Workspace. This endpoint requires one of the following scopes: `workspaces.read` or `workspaces` ```bash Request theme={"theme":{"light":"github-light","dark":"github-dark"}} curl --request GET \ --url 'https://api.luminpdf.com/v1/workspaces/members?page=1&limit=25' \ --header 'X-API-Key: ' ``` # List Spaces Source: https://developers.luminpdf.com/tabs/api-reference/api/workspaces/list-spaces GET /workspaces/spaces Return a paginated list of Spaces within the authorized Workspace. This endpoint requires one of the following scopes: `workspaces.read` or `workspaces` # Workspaces Source: https://developers.luminpdf.com/tabs/api-reference/api/workspaces/workspaces Manage your Workspace, members, and Spaces. The Workspaces API provides access to your organization's Workspace — the top-level container for all your documents, templates, members, and Spaces in Lumin. ## Key concepts * **Workspace** — the top-level organizational unit. Every API key or OAuth token is scoped to a single Workspace. * **Member** — a user who belongs to the Workspace. Members can have different roles (owner, admin, member) that determine their permissions. * **Space** — a sub-group within a Workspace used to organize members and documents. Spaces allow teams to collaborate on separate sets of documents while sharing the same Workspace billing and settings. ## Typical workflow 1. **Get Workspace info** to retrieve the Workspace name, plan, and settings. 2. **List members** to see who has access and their roles. 3. **List or create Spaces** to organize teams and their documents within the Workspace. ## Available endpoints Return information of the authorized Workspace. Return a paginated list of members in a Workspace. Return a paginated list of Spaces within the authorized Workspace. Create a new Space within the authorized Workspace. # Changelog Source: https://developers.luminpdf.com/tabs/changelog This changelog lists all additions and updates to the Lumin API. ### June 17, 2026 **Added** * Add [Merge Documents](/tabs/api-reference/api/documents/merge-documents) endpoint to combine 2–20 PDF or image files into a single PDF and return a temporary download URL. * Add [Compress Document](/tabs/api-reference/api/documents/compress-document) endpoint to reduce PDF file size with `standard` or `maximum` compression levels. * Add [Split Document](/tabs/api-reference/api/documents/split-document) endpoint to extract page ranges or split a PDF into equal-sized parts. * Add [Add Password](/tabs/api-reference/api/documents/add-password) endpoint to add or change password protection on a PDF (Business plans). * Add [Remove Password](/tabs/api-reference/api/documents/remove-password) endpoint to remove password protection from a PDF. * Add [Summarize Document](/tabs/api-reference/api/documents/summarize-document) endpoint to generate an AI-powered summary of a PDF's text content. * Add [Translate Document](/tabs/api-reference/api/documents/translate-document) endpoint to translate a PDF into a target language while preserving layout. ### June 11, 2026 **Added** * Add [Create Signing Session](/tabs/api-reference/api/signature-requests/create-signing-session) endpoint to return a short-lived `sign_url` for a single signer on an existing signature request. Use this URL with the `@luminpdf/lumin-embed-signing-sdk` package to embed the signing experience in your application. See the [Embedded Signing](/tabs/guides/walkthroughs/embedded-signing) walkthrough. ### May 27, 2026 **Added** * Add [List Spaces](/tabs/api-reference/api/workspaces/list-spaces) endpoint to return a paginated list of Spaces within the authorized Workspace. * Add [Create Space](/tabs/api-reference/api/workspaces/create-space) endpoint to create a new Space within the authorized Workspace, with optional initial members. * Add `collections` array to the [Get Template Details](/tabs/api-reference/api/templates/get-template-details) response. AgreementGen templates expose the table-scoped row-loop collections (`<>` / `<>`) referenced in the document, including each collection's `name`, `type`, and the variable names rendered inside the loop body. * Accept a `collections` object on [Create Agreement](/tabs/api-reference/api/agreements/create-agreement), [Send Signature Request from Template](/tabs/api-reference/api/signature-requests/send-signature-request-from-template), and [Generate Document from Template](/tabs/api-reference/api/templates/generate-document-from-template) to populate row-loop tables in AgreementGen templates. Each key matches a collection name returned by `Get Template Details`; the value is an ordered array of flat record objects. Maximum 100 items per collection and up to 50 collections per request. **Removed** * Remove the deprecated `Download File` (`GET /signature_request/files/{signature_request_id}`) and `Download File as File URL` (`GET /signature_request/files_as_file_url/{signature_request_id}`) endpoints. Use [Get Signature Request File](/tabs/api-reference/api/signature-requests/get-signature-request-file) instead. ### March 31, 2026 **Added** * Add [Get Signing Link](/tabs/api-reference/api/signature-requests/get-signing-link) endpoint to generate a signing URL for a signer within an existing signature request. This enables embedding signing flows directly in your own applications. **Fixed** * Add missing field descriptions for `email_address` and `name` in the signers response of [Get Signature Request](/tabs/api-reference/api/signature-requests/get-signature-request). ### March 10, 2026 **Fixed** * Remove `group` field from the signers object in the [Send Signature Request from Template](/tabs/api-reference/api/signature-requests/send-signature-request-from-template) endpoint. Signing order is determined by the template's signer roles configuration. ### February 01, 2026 **Added** * Add `template` method to [Create Document](/tabs/api-reference/api/documents/create-document) endpoint, allowing document creation from PDF templates (template IDs with `pdf_` prefix). * Add documents for [OAuth 2.0](/tabs/guides/authentication/oauth2) authorization flow. ### January 20, 2026 **Added** * Add [Send Signature Request from Template](/tabs/api-reference/api/signature-requests/send-signature-request-from-template) endpoint to create signature requests from templates. * Add [Create Agreement](/tabs/api-reference/api/agreements/create-agreement) endpoint to create AgreementGen documents from templates. * Add [Get Agreement File](/tabs/api-reference/api/agreements/get-agreement-file) endpoint to download AgreementGen documents. ### January 19, 2026 **Added** * Add [Send Reminder Emails](/tabs/api-reference/api/signature-requests/send-reminder-emails) endpoint to send reminder emails to signers who haven't signed yet. ### January 07, 2026 **Added** * Add [Update Signature Request](/tabs/api-reference/api/signature-requests/update-signature-request) endpoint to update the due date of existing signature requests. * Add [Get Template Details](/tabs/api-reference/api/templates/get-template-details) endpoint to retrieve template information including signer roles, tags, and fields. * Add [Create Document](/tabs/api-reference/api/documents/create-document) endpoint to create documents from file upload. * Add [Get Workspace Information](/tabs/api-reference/api/workspaces/get-workspace-information) endpoint to retrieve Workspace details. * Add [Get Workspace Members](/tabs/api-reference/api/workspaces/get-workspace-members) endpoint to list members in a Workspace. * Add `signature_request_due_date_updated` event to [Signature Request Events](/tabs/guides/webhooks/overview#supported-event-types). ### October 10, 2025 **Added** * Add [Get Signature Request File](/tabs/api-reference/api/signature-requests/get-signature-request-file) endpoint with support for multiple file types: * `agreement` — The completed/signed agreement PDF * `coc` — The Certificate of Completion PDF * `merged` — A single PDF combining agreement and CoC ### September 08, 2025 **Added** * Add [List Templates](/tabs/api-reference/api/templates/list-templates) endpoint. * Add [Generate Document from Template](/tabs/api-reference/api/templates/generate-document-from-template) endpoint. * Add [Create PDF from Template](/tabs/guides/walkthroughs/create-pdf-from-template) walkthrough. ### July 28, 2024 **Added** * Add `custom_email` parameter to [Send Signature Request](/tabs/api-reference/api/signature-requests/send-signature-request) to send a custom email to the signer. ### April 03, 2024 **Added** * Add [Cancel Signature Request](/tabs/api-reference/api/signature-requests/cancel-signature-request) endpoint. * Add `signature_request_canceled` and `signature_request_cancel_failed` events to [Signature Request Events](/tabs/guides/webhooks/overview#supported-event-types). ### February 26, 2024 **Added** * Add `Download File as File URL` endpoint. (Removed on May 27, 2026.) * Add `Download File` endpoint. (Removed on May 27, 2026.) ### January 10, 2024 To enhance the flexibility of the Signature Request process, two new parameters have been introduced: 1. `signing_type`: This parameter allows you to specify the signing order for the Signature Request. It accepts two possible values: * `SAME_TIME`: All signers can sign the document simultaneously. * `ORDER`: Signers must sign the document in a specified order. 2. `group`: When the `signing_type` is set to `ORDER`, you must use the group parameter to specify the group of signers who need to sign in the defined order. For more information, see [Send Signature Request](/tabs/api-reference/api/signature-requests/send-signature-request). ### December 19, 2023 **Added** * Add `signature_request_created` and `signature_request_invalid` events to [Signature Request Events](/tabs/guides/webhooks/overview#supported-event-types). ### December 6, 2023 **Added** * Add webhook support for signature request events. For more information about supported events, see [Signature Request Events](/tabs/guides/webhooks/overview#supported-event-types). For more information about events and callbacks, see [Events](/tabs/guides/webhooks/overview). ### November 8, 2023 **Added** * Add `use_text_tags` parameter to [Send Signature Request](/tabs/api-reference/api/signature-requests/send-signature-request) to enable text tags in the document. For more information, see [Text Tags](/tabs/guides/walkthroughs/text-tags). To begin and end a tag, use square brackets `[ ]` in your document. Within the square brackets, use the pipe character `|` to divide the parts of the tag. The first part of the tag is the type. The second part indicates if it is required or not. The third part indicates which signer in the list of signers needs to complete the field. The last two parts are optional and are for setting a label and unique ID ex: \[text|noreq|signer1|Label|UniqueId] **Fixed** * Some minor issues related to signers list and signer name in signature request. ### October 26, 2023 **Added** * Create API Key from **Developer Settings page**. For more information, see [Authentication](/tabs/guides/authentication/overview). * Add [Send Signature Request](/tabs/api-reference/api/signature-requests/send-signature-request) endpoint. * Add [Get Signature Request](/tabs/api-reference/api/signature-requests/get-signature-request) endpoint. * Add [Get User Info](/tabs/api-reference/api/users/get-user-information) endpoint. # API Keys Source: https://developers.luminpdf.com/tabs/guides/authentication/api-key Generate and use API Keys to authenticate requests to the Lumin API. API Keys are the simplest way to authenticate requests to the Lumin API. They are long-lived credentials that you include on each request. API Keys are best suited for server-to-server integrations and backend automation where individual user authorization is not required. Treat API Keys like passwords. Do not share them, embed them in client-side code, or commit them to source control. ## Generate an API key You must be a **Workspace Owner** to create API Keys. Log in to Lumin and go to **Settings → Developer settings → API keys**. Click **Generate key**, enter a name to identify this key, and click **Create**. Copy the key immediately and store it in a secret manager. You will not be able to see it again after closing the dialog. ## Use your API key You can pass your API key to the Lumin API in two ways: Pass the key in the `X-API-KEY` request header: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X GET "https://api.luminpdf.com/v1/user/info" \ -H "X-API-KEY: YOUR_API_KEY" ``` Pass the key as the username in HTTP Basic Auth with an empty password. Include the trailing colon (`:`) to indicate there is no password: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X GET "https://api.luminpdf.com/v1/user/info" \ -u "YOUR_API_KEY:" ``` Both methods work for all Lumin API endpoints. Use whichever your HTTP client or library handles most conveniently. ## Multiple API keys Each Lumin account can have up to **4 active API Keys** at a time. All active keys are valid and can be used in parallel — useful during key rotation or when multiple services need separate credentials. One key can be designated as the **Primary Key**. The Primary Key is used to generate HMAC signatures for [verifying webhook event payloads](/tabs/guides/webhooks/account-webhooks). If you change which key is primary, update your webhook signature verification logic accordingly. ## Key rotation Rotate your API Keys regularly to limit the impact of accidental exposure. Create a new key from **Settings → Developer settings → API keys**. Deploy the new key to your application or secret manager. Confirm your integration is authenticating successfully with the new key before proceeding. Once you have confirmed the new key works, delete the old key from Developer settings. Rotate keys at least every 6 months as a routine practice. Rotate immediately if you suspect a key has been exposed or compromised. ## Security best practices * Store API Keys in a secret manager such as AWS Secrets Manager, HashiCorp Vault, or GCP Secret Manager — not in environment files committed to source control. * Never embed API Keys in mobile apps, browser-based JavaScript, or any client-side code. * Use separate keys for separate services or environments so you can rotate or revoke them independently. * Always verify a new key works before deleting the old one. * For applications where individual users authorize access to their own data, use [OAuth 2.0](/tabs/guides/authentication/oauth2) instead of sharing a single API Key. # OAuth 2.0 Source: https://developers.luminpdf.com/tabs/guides/authentication/oauth2 Use OAuth 2.0 to let users authorize your application to access their Lumin data. OAuth 2.0 lets your application request access to a user's Lumin account on their behalf. The user reviews the requested permissions on a consent screen and explicitly approves or denies access. This makes OAuth 2.0 the right choice when your application acts on behalf of individual Lumin users rather than a single shared workspace. Lumin supports the **authorization code flow** with two client types: | Client type | Use case | Client secret required | Refresh tokens | | ------------------ | -------------------------------------- | ---------------------- | -------------- | | **Public (PKCE)** | Mobile apps, SPAs, desktop apps | No — uses PKCE instead | No | | **Private/Server** | Server-side web apps, backend services | Yes | Yes | Public (PKCE) apps do not receive refresh tokens. When the access token expires, the user must re-authorize. If your app needs long-lived background access, create a Private/Server app instead. ## Register your application Before you can use OAuth 2.0, register your application in Lumin. You must be a **Workspace Owner** to do this. Each Workspace can have up to **5 integration apps**. Log in to Lumin and go to **Settings → Developer settings → Integration apps**, then click **Create app**. Enter an **Application name** and select an **Application type**: * **Public Application** — no client secret, uses PKCE. Choose this for mobile apps, SPAs, or desktop apps. * **Private Application** — server-based with a client secret. Choose this for backend or server-side applications. You cannot change the application type after creation. If you need a different type, create a new application. Choose only the scopes your application actually needs. The scopes you select appear on the user consent screen. See [Scopes](#scopes) for the full list. Enter one or more redirect URIs where Lumin will send users after they authorize your app. * Must use `https://` or an app-specific scheme (e.g., `myapp://callback`) * No wildcards, IP addresses, or relative paths * Separate multiple URIs with commas Fill in the information users will see when granting access: app logo, website URL, Privacy Policy URL, Terms of Use URL, and a contact email. Click **Create**. You will receive: * **Client ID** — required for all OAuth 2.0 flows * **Client Secret** — issued only for Private Applications Store these credentials securely. Do not hardcode them in your source code or commit them to repositories. ## Authorization code flow Use this flow for server-side applications. The flow uses a client secret and issues refresh tokens so your server can maintain access without user re-interaction. Send the user to Lumin's authorization URL with your app's parameters: ```http theme={"theme":{"light":"github-light","dark":"github-dark"}} GET https://auth.luminpdf.com/oauth2/auth ?client_id=YOUR_CLIENT_ID &response_type=code &redirect_uri=YOUR_REDIRECT_URI &scope=openid offline_access sign:requests &state=RANDOM_STATE_VALUE &nonce=RANDOM_NONCE_VALUE ``` Include `offline_access` in the scope to receive a refresh token. After the user approves access, Lumin redirects them to your `redirect_uri` with an authorization `code` in the query string. Make a server-to-server POST request to exchange the code for an access token and refresh token: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST "https://auth.luminpdf.com/oauth2/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "code=YOUR_AUTHORIZATION_CODE" \ -d "client_id=YOUR_CLIENT_ID" \ -d "client_secret=YOUR_CLIENT_SECRET" \ -d "redirect_uri=YOUR_REDIRECT_URI" ``` Lumin returns the tokens: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "access_token": "eyJhbGci0i...", "expires_in": 3600, "refresh_token": "def502...", "token_type": "bearer", "scope": "openid offline_access sign:requests" } ``` Pass the access token in the `Authorization` header as a Bearer token: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X GET "https://api.luminpdf.com/v1/user/info" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" ``` Access tokens expire after 1 hour. Use the refresh token to get a new access token without requiring the user to re-authorize: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST "https://auth.luminpdf.com/oauth2/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=refresh_token" \ -d "refresh_token=YOUR_REFRESH_TOKEN" \ -d "client_id=YOUR_CLIENT_ID" \ -d "client_secret=YOUR_CLIENT_SECRET" ``` Use this flow for mobile apps, SPAs, and desktop apps that cannot securely store a client secret. PKCE (Proof Key for Code Exchange) replaces the client secret with a cryptographic challenge. Lumin supports the **S256 (SHA-256)** PKCE method. 1. Create a `code_verifier`: a cryptographically random string. 2. Generate a `code_challenge` by hashing the `code_verifier` with SHA-256 and encoding the result as Base64URL. 3. Use `code_challenge_method=S256`. ``` code_verifier = "R8zFoqsOyeysd881QITZs3dK1YsdIvFNBf04D1bukBw" code_challenge = "RqN6kvc2f99WD-BQG3SzsDfQcX54BxuyuM40alAt8b5M" ``` Store the `code_verifier` — you will need it in step 3. Send the user to Lumin's authorization URL, including the PKCE parameters: ```http theme={"theme":{"light":"github-light","dark":"github-dark"}} GET https://auth.luminpdf.com/oauth2/auth ?client_id=YOUR_CLIENT_ID &response_type=code &redirect_uri=YOUR_REDIRECT_URI &scope=openid sign:requests &state=RANDOM_STATE_VALUE &code_challenge=YOUR_CODE_CHALLENGE &code_challenge_method=S256 ``` After the user approves, Lumin redirects them to your `redirect_uri` with an authorization `code`. Exchange the code for an access token. Provide the `code_verifier` instead of a client secret: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST "https://auth.luminpdf.com/oauth2/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "code=YOUR_AUTHORIZATION_CODE" \ -d "client_id=YOUR_CLIENT_ID" \ -d "redirect_uri=YOUR_REDIRECT_URI" \ -d "code_verifier=YOUR_CODE_VERIFIER" ``` ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "access_token": "ory_at__aqzPqv1z0Uw...", "expires_in": 3598, "token_type": "bearer", "scope": "openid sign:requests" } ``` Public apps do not receive a `refresh_token`. When the access token expires, start a new authorization flow. Pass the access token in the `Authorization` header: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X GET "https://api.luminpdf.com/v1/user/info" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" ``` ## Scopes Scopes define what your application can access. Request only the scopes your app actually needs — users see the requested scopes on the consent screen. | Category | Scope | Description | | ------------------ | -------------------- | ------------------------------------------------------------------- | | Account | `openid` | Retrieve basic identity details (username, email, profile picture). | | Account | `offline_access` | Request a refresh token for long-lived access. Private apps only. | | Account | `profile.read` | View basic user profile information. | | Account | `profile.settings` | Manage user account settings. | | Workspace | `workspaces` | View and manage Workspaces and Spaces. | | Workspace | `workspaces.read` | View information about the authenticated user's Workspace. | | Templates | `templates` | View and manage templates in a Workspace. | | Documents | `pdf:files` | Create, edit, and delete PDF files in a Workspace. | | Documents | `pdf:files.read` | Retrieve PDF documents stored in a Workspace. | | Signature Requests | `sign:requests` | Create, update, or view signature requests. | | Signature Requests | `sign:requests.read` | Retrieve signature requests. | | Agreements | `agreements` | Create, update, or delete AgreementGen documents. | Private integration apps receive the `openid` and `offline_access` scopes by default. You can view the scope string for an existing application by opening its **Application details** modal in **Settings → Developer settings → Integration apps**. # Authentication Source: https://developers.luminpdf.com/tabs/guides/authentication/overview Learn how to authenticate requests to the Lumin API using API Keys or OAuth 2.0. Before your application can access Lumin data or call Lumin APIs, you need to authenticate your requests. Lumin supports two authentication methods: ## API Key API Keys are long-lived credentials you generate from the Lumin Developer settings page. You include the key on each request using the `X-API-KEY` header or HTTP Basic Auth. **Best for:** * Server-to-server integrations * Scheduled backend jobs and cron tasks * Quick testing and development [Learn how to use API Keys →](/tabs/guides/authentication/api-key) ## OAuth 2.0 OAuth 2.0 lets end users grant your application access to their Lumin data without sharing their credentials. Lumin supports the authorization code flow with two client types: * **Public Application (PKCE)** — for mobile apps, single-page applications, and desktop apps that cannot securely store a client secret. Uses PKCE (Proof Key for Code Exchange) instead of a client secret. * **Private/Server Application** — for server-side applications that can securely store a client secret. Also supports refresh tokens for long-lived sessions. **Best for:** * Web apps where users sign in and authorize access * Applications acting on behalf of individual Lumin users * Scenarios requiring fine-grained permission control via scopes [Learn how to use OAuth 2.0 →](/tabs/guides/authentication/oauth2) ## Choosing the right method | Scenario | Recommended method | | ----------------------------------------------- | ----------------------------------------------- | | Scheduled backend job with no user interaction | API Key or OAuth 2.0 Private Authorization Code | | Web app where users sign in and provide consent | OAuth 2.0 Private Authorization Code | | Mobile or single-page app | OAuth 2.0 Public Authorization Code with PKCE | | Quick testing or prototyping | API Key | Both methods can be used in production. If you are building a user-facing application where individuals authorize access to their own Lumin data, use OAuth 2.0. If you are running automated backend processes on behalf of a single workspace, an API Key is simpler to set up. # Introduction Source: https://developers.luminpdf.com/tabs/guides/getting-started/index Lumin APIs let you build document workflows — eSignatures, templates, and automation — directly into your application. All API endpoints use standard HTTP methods, return JSON responses (except documented file downloads), and follow predictable resource-oriented URL patterns. ## Base URLs | Environment | Base URL | | ----------- | ----------------------------- | | Production | `https://api.luminpdf.com/v1` | ## What you can build Each card below opens the **overview for that API area**, with links to every endpoint in that group. Send signature requests, update due dates, cancel requests, send reminders, get signing links, and download agreements and certificates. Create AgreementGen documents from templates and download agreement files. List templates, fetch template details, and generate PDFs from Sign or AgreementGen templates. Create documents from file upload or PDF templates and organize them in your Workspace. Read Workspace metadata and list members with pagination. Retrieve information about the authenticated user. ## Get started Get oriented and run the docs locally, or follow your team’s onboarding steps. Use API keys or bearer tokens — see schemes and headers for every request. Jump into endpoint pages with request bodies, responses, and the interactive playground. See what’s new and what changed in recent API releases. # Quick Start Source: https://developers.luminpdf.com/tabs/guides/getting-started/quickstart Get your API key, verify your credentials, and send your first signature request in a few minutes. This guide walks you through getting authenticated and sending your first signature request using the Lumin API. ## Prerequisites * A Lumin account with Workspace Owner access * `curl` or an HTTP client of your choice 1. Go to **Settings → Developer settings → API keys** in the Lumin app, or click [here](https://account.luminpdf.com/sign-in?return_to=https://luminpdf.com/app/pdf/workspace/last_accessed/dashboard/developer-settings) to go directly to the Developer settings page. 2. Click **Generate key**, enter a name for the key, and click **Create**. 3. Copy and securely store the key — you won't be able to see it again. Treat your API key like a password. Do not commit it to source control or expose it in client-side code. Confirm your API key works by calling `GET /user/info`: ```bash curl theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X GET "https://api.luminpdf.com/v1/user/info" \ -H "X-API-KEY: YOUR_API_KEY" ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} import requests response = requests.get( "https://api.luminpdf.com/v1/user/info", headers={"X-API-KEY": "YOUR_API_KEY"}, ) print(response.json()) ``` ```javascript Node.js theme={"theme":{"light":"github-light","dark":"github-dark"}} const response = await fetch("https://api.luminpdf.com/v1/user/info", { headers: { "X-API-KEY": "YOUR_API_KEY" }, }); const data = await response.json(); console.log(data); ``` A successful response returns your user details: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "user": { "id": "usr_a1b2c3d4", "email": "you@example.com", "name": "Your Name" } } ``` If you receive a `401` or `403`, double-check that your API key was copied correctly and that you are passing it in the `X-API-KEY` header. Use `POST /signature_request/send` to send a document to one or more signers. The example below sends a PDF from a public URL to a single signer: ```bash curl theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST "https://api.luminpdf.com/v1/signature_request/send" \ -H "X-API-KEY: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Financial Year-End Report Authorization", "file_url": "https://example.com/path/to/document.pdf", "signers": [ { "email_address": "john.doe@example.com", "name": "John Doe" } ], "expires_at": 1927510980694 }' ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} import requests payload = { "title": "Financial Year-End Report Authorization", "file_url": "https://example.com/path/to/document.pdf", "signers": [ { "email_address": "john.doe@example.com", "name": "John Doe", } ], "expires_at": 1927510980694, } response = requests.post( "https://api.luminpdf.com/v1/signature_request/send", headers={ "X-API-KEY": "YOUR_API_KEY", "Content-Type": "application/json", }, json=payload, ) print(response.json()) ``` ```javascript Node.js theme={"theme":{"light":"github-light","dark":"github-dark"}} const response = await fetch( "https://api.luminpdf.com/v1/signature_request/send", { method: "POST", headers: { "X-API-KEY": "YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ title: "Financial Year-End Report Authorization", file_url: "https://example.com/path/to/document.pdf", signers: [{ email_address: "john.doe@example.com", name: "John Doe" }], expires_at: 1927510980694, }), } ); const data = await response.json(); console.log(data); ``` A `201 Created` response returns the new signature request: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "signature_request": { "signature_request_id": "sr_a1b2c3d4e5f6", "status": "WAITING_FOR_PROCESSING", "created_at": "2026-04-01T10:00:00Z" } } ``` The signer receives an email with a link to review and sign the document. The `status` moves from `WAITING_FOR_PROCESSING` to `NEED_TO_SIGN` once the document is ready. The `expires_at` field is a Unix timestamp in milliseconds. Set it to a date in the future. To send to multiple signers simultaneously, add more objects to the `signers` array and set `signing_type` to `SAME_TIME`. For sequential signing, set `signing_type` to `ORDER` and assign a `group` number to each signer. ## Next steps * [Authentication overview](/tabs/guides/authentication/overview) — learn about API Key and OAuth 2.0 options * [API Reference](/tabs/api-reference/api/lumin-api-reference) — explore all available endpoints * [Webhooks](/tabs/guides/webhooks/overview) — receive real-time event notifications when signers act # Create PDF from Template Source: https://developers.luminpdf.com/tabs/guides/walkthroughs/create-pdf-from-template Generate a filled PDF document from a Lumin template by supplying merge tags, form fields, and variables. Lumin templates are documents built in the Lumin application that contain placeholders (merge tags, form fields, and variables), which you can populate with dynamic data to produce customized PDF documents on demand. The same template can serve two purposes: * **Generate a standalone PDF** — produce a filled document without a signing workflow. * **Send as a signature request** — use the template as the document in a signature request via the `/signature_request/send-from-template` endpoint. Tags and field names use `object.field` naming conventions (for example: `Client.Name`, `Effective.Date`, `Customer.AgreeToTerms`). *** ## Placeholder types | Type | Used in | Description | | -------------- | ---------------------- | -------------------------------------------------------------------------------------------------- | | **Merge tag** | Sign templates | Text placeholder embedded in document content. Replaced with a plain-text value during generation. | | **Form field** | Sign and PDF templates | Interactive input in the document (text box, checkbox). Can be prefilled with a value. | | **Variable** | AgreementGen templates | Dynamic variable rendered as plain text. Used by AgreementGen (`ag_` prefix templates). | Signature, initials and radio button fields **cannot be prefilled**. They will appear empty in the generated document regardless of any value you provide. *** Call `GET /templates` to see the templates available in your workspace. Provide the `X-Lumin-API-Version: 1.1` header and the required `page` and `limit` query parameters. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl "https://api.luminpdf.com/v1/templates?page=1&limit=25" \ -H "Authorization: API-key " \ -H "X-Lumin-API-Version: 1.1" ``` ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "page": 1, "limit": 25, "total_count": 3, "data": [ { "template_id": "sign_123456", "type": "pdf", "name": "Mutual NDA", "created_at": 1748456885430, "updated_at": 1748456885430 }, { "template_id": "ag_456789", "type": "lumin", "name": "Lease Agreement", "created_at": 1748456885430, "updated_at": 1748456885430 }, { "template_id": "pdf_456789", "type": "pdf", "name": "Onboarding Form", "created_at": 1748456885430, "updated_at": 1748456885430 } ] } ``` Note the `template_id` — the prefix tells you the template type: | Prefix | Template type | | ------- | --------------------------------------------------------- | | `sign_` | Lumin Sign template (supports merge tags and form fields) | | `ag_` | AgreementGen template (supports variables) | | `pdf_` | PDF template (supports form fields) | Call `GET /templates/{template_id}` to inspect the specific tags, fields, and variables the template expects. Use this to know exactly what keys to include when you generate the document. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl https://api.luminpdf.com/v1/templates/sign_123456 \ -H "Authorization: API-key " ``` ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "template_id": "sign_123456", "type": "pdf", "name": "Mutual NDA", "signing_type": "ORDER", "signer_roles": [ { "name": "Tenant", "group": 1 }, { "name": "Client", "group": 2 } ], "tags": [ { "name": "Client.Name", "type": "merge_tag", "is_required": true }, { "name": "Effective.Date", "type": "merge_tag", "is_required": false } ], "fields": [ { "name": "Customer.Name", "type": "text", "is_required": true, "assigned_role": "Tenant" }, { "name": "Customer.AgreeToTerms", "type": "checkbox", "is_required": true, "assigned_role": "Tenant" } ], "variables": [], "created_at": 1748456885430, "updated_at": 1748456885430 } ``` Supply values for every item where `is_required: true`. Optional fields can be omitted. Call `POST /templates/{template_id}/generate-document` with the values for your tags, fields, and/or variables. ```bash JSON response theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://api.luminpdf.com/v1/templates/sign_123456/generate-document \ -H "Authorization: API-key " \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{ "tags": { "Client.Name": "Acme Corp", "Effective.Date": "2025-08-01" }, "fields": { "Customer.Name": "John Doe", "Customer.AgreeToTerms": true }, "variables": { "Company.Name": "Acme Corp", "Document.Name": "Mutual NDA" }, "document_name": "NDA_AcmeCorp" }' ``` ```bash PDF binary response theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://api.luminpdf.com/v1/templates/sign_123456/generate-document \ -H "Authorization: API-key " \ -H "Content-Type: application/json" \ -H "Accept: application/pdf" \ -d '{ "tags": { "Client.Name": "Acme Corp", "Effective.Date": "2025-08-01" }, "fields": { "Customer.Name": "John Doe", "Customer.AgreeToTerms": true }, "document_name": "NDA_AcmeCorp" }' \ --output NDA_AcmeCorp.pdf ``` **JSON response** (`Accept: application/json`): ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "document_name": "NDA_AcmeCorp", "signed_url": "https://files.luminpdf.com/download/nda-acmecorp-abc123.pdf?expires=...", "expires_at": 1766726700 } ``` `signed_url` is a pre-signed HTTPS download link that expires in 30 minutes (`expires_at` is a Unix timestamp in seconds). **PDF binary response** (`Accept: application/pdf`): When you set `Accept: application/pdf`, the API returns the raw PDF binary stream instead of JSON. Use this to stream the file directly without a separate download step. *** ## Request body reference ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "tags": { "Client.Name": "Acme Corp", "Effective.Date": "2025-08-01" }, "fields": { "Customer.Name": "John Doe", "Customer.AgreeToTerms": true }, "variables": { "Company.Name": "Acme Corp", "Document.Name": "NDA Document" }, "document_name": "My Contract" } ``` | Field | Type | Description | | --------------- | ------ | ------------------------------------------------------------------------- | | `tags` | object | Merge tag values for Sign templates. Keys must match tag names exactly. | | `fields` | object | Form field values. Supports `text` (string) and `checkbox` (boolean). | | `variables` | object | Variable values for AgreementGen templates. | | `document_name` | string | Optional. Name for the generated document. Defaults to the template name. | # Embedded Signing Source: https://developers.luminpdf.com/tabs/guides/walkthroughs/embedded-signing Embed the Lumin signing experience in your app using the Embed Signing SDK and signing-session API. Mint a per-signer signing session and mount the Lumin signing UI inline with [`@luminpdf/lumin-embed-signing-sdk`](https://www.npmjs.com/package/@luminpdf/lumin-embed-signing-sdk?activeTab=readme). For redirect flows, use [Get Signing Link](/tabs/api-reference/api/signature-requests/get-signing-link) instead. *** ## When to use embedded signing * Signers are already authenticated in your app and you want an in-context experience. * You control when and how the signing prompt appears (modal, inline panel, dedicated step). * You are building for web or mobile webview, not a native-only flow without a hosted shell page. Lumin **does not authenticate signers** in embedded flows. Your app is responsible for verifying that the person opening the signing surface is the intended recipient. *** ## Prerequisites Before you integrate embedded signing, make sure you have: * An API key or OAuth 2.0 app with the `sign:requests` scope. Create these yourself in **Settings → Developer settings** in the Lumin app. See [API Keys](/tabs/guides/authentication/api-key) or [OAuth 2.0](/tabs/guides/authentication/oauth2). * An **embedding domain** allow-listed for that API key or OAuth app. Embedded signing only works when your host page origin matches an allow-listed domain. * Signature requests created through the **public API** (`POST /signature_request/send` or `POST /signature_request/send-from-template`). In-app agreements created from the Lumin UI are not embeddable. Embedding domains are **currently allow-listed by Lumin on request**. Contact [Lumin Support](https://www.luminpdf.com/contact-support) with your Workspace ID/Workspace name and desired embedding domain to request allow-listing for your domain. You cannot mint signing sessions until your domain is allow-listed. *** ## Workflow overview ```mermaid theme={"theme":{"light":"github-light","dark":"github-dark"}} sequenceDiagram participant Signer participant HostFE as Your frontend participant HostBE as Your backend participant API as Lumin API participant SDK as Embed Signing SDK participant Iframe as Lumin Sign page Note over HostBE,API: One-time setup HostBE->>API: API key / OAuth client + allow-listed domain Signer->>HostFE: Click Sign now HostFE->>HostBE: Request signing session HostBE->>API: POST /signature_request/send or send-from-template API-->>HostBE: signature_request_id HostBE->>API: POST /signature_request/{id}/signing-session API-->>HostBE: sign_url, expires_at HostBE-->>HostFE: sign_url HostFE->>SDK: client.open({ signUrl }) SDK->>Iframe: load sign_url in iframe Iframe->>API: validate parent origin against allow-list alt Origin allow-listed API-->>Iframe: render signing surface Iframe-->>SDK: postMessage loaded SDK-->>HostFE: emit loaded Signer->>Iframe: review and sign Iframe-->>SDK: postMessage signed SDK-->>HostFE: emit signed API-->>HostBE: webhook signature_request_signed / approved else Origin not allow-listed API-->>Iframe: 403 origin_not_allowed SDK-->>HostFE: emit error end ``` Webhooks are the **source of truth** for server-side state. SDK events are for host-app UX only, do not persist business state from client-side events alone. *** ## Integration steps Create an API key or OAuth app in **Settings → Developer settings** ([API Keys](/tabs/guides/authentication/api-key), [OAuth 2.0](/tabs/guides/authentication/oauth2)). Then contact [Lumin Support](https://www.luminpdf.com/contact-support) to allow-list embedding domain registration for API key/OAuth client. From your backend, send the document with `POST /signature_request/send` or `POST /signature_request/send-from-template`. See [Send Signature Request](/tabs/guides/walkthroughs/send-signature-request). Call `POST /signature_request/{id}/signing-session` with the signer's email and pass the returned `sign_url` to your frontend. See [Create Signing Session](/tabs/api-reference/api/signature-requests/create-signing-session). Install [`@luminpdf/lumin-embed-signing-sdk`](https://www.npmjs.com/package/@luminpdf/lumin-embed-signing-sdk?activeTab=readme), initialize the client once, then call `client.open({ signUrl })` when the signer is ready. Use SDK events for immediate UX feedback. Use webhooks as the source of truth for server-side state. *** ## API examples ### Create a signature request ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://api.luminpdf.com/v1/signature_request/send \ -H "Authorization: API-key " \ -H "Content-Type: application/json" \ -d '{ "title": "Rental Agreement", "file_url": "https://example.com/rental-agreement.pdf", "signing_type": "SAME_TIME", "expires_at": 1927510980694, "signers": [ { "email_address": "tenant@example.com", "name": "Alex Tenant" } ] }' ``` Save the `signature_request_id` from the response. If the status is `WAITING_FOR_PROCESSING`, poll `GET /signature_request/{id}` until the request is ready to sign. ### Mint a signing session ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://api.luminpdf.com/v1/signature_request/696d007913f3b8.../signing-session \ -H "Authorization: API-key " \ -H "Content-Type: application/json" \ -d '{ "signer_email": "tenant@example.com", "expiry": 900000 }' ``` ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "sign_url": "https://sign.luminpdf.com/embed?session=8647b08b...", "signer_email": "tenant@example.com", "expires_at": 1927510980694, "status": "NEED_TO_SIGN" } ``` Do not use [Get Signing Link](/tabs/api-reference/api/signature-requests/get-signing-link) for iframe embeds. Do not expose your API key to the browser. Mint the session when the signer is ready to sign. Each `sign_url` is single-use and expires after `expiry` milliseconds. See [Create Signing Session](/tabs/api-reference/api/signature-requests/create-signing-session) for request and response fields. *** ## SDK example ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} npm install @luminpdf/lumin-embed-signing-sdk@1.0.0 ``` ```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}} import { LuminSigning } from "@luminpdf/lumin-embed-signing-sdk"; const client = new LuminSigning({ clientId: "your-lumin-app-id" }); client.on("loaded", () => { /* signing surface ready */ }); client.on("signed", () => { /* update UX — wait for webhook */ }); client.on("error", (event) => { console.error(event.code, event.message); }); const session = client.open({ container: "#sign-root", signUrl: signUrlFromBackend, }); ``` ```html theme={"theme":{"light":"github-light","dark":"github-dark"}}
``` *** ## Events and webhooks | Event | When it fires | Recommended action | | ---------- | ------------------------ | ---------------------------------------------------------------- | | `loaded` | Signing UI is ready | Hide your loading state | | `signed` | Signer completed signing | Show a thank-you state; wait for webhook | | `declined` | Signer declined | Show a message; wait for webhook | | `expire` | `sign_url` TTL elapsed | Mint a new session from your backend | | `error` | Signing aborted | See [Domain verification](#domain-verification) for common codes | | `close` | Iframe torn down | Final cleanup | Listen for `signature_request_signed` and `signature_request_approved` webhooks to advance your workflow. See the [Webhooks guide](/tabs/guides/webhooks/overview). *** ## Domain verification Embedded signing validates the **parent page origin** when the iframe loads. The signing surface renders only when the origin matches a domain allow-listed for the API key or OAuth app that minted the session. Common `error` codes related to domains and sessions: | Code | Meaning | | ------------------------------------ | ---------------------------------------------------------- | | `origin_not_allowed` | Parent origin is not on your app's allow-list | | `session_expired` | `sign_url` was used after its TTL — mint a new session | | `session_already_used` | The same `sign_url` was mounted twice — mint a new session | | `signature_request_no_longer_active` | Request cancelled or expired while iframe was open | ### Mobile webview Native apps must load the SDK through a **hosted shell page** on your verified HTTPS domain (for example, `https://app.example.com/sign-shell`). You cannot bundle the SDK as a static `file://` asset, the origin check will fail. Pass `sign_url` from your native backend into the webview shell via a query parameter or JavaScript bridge. The shell mounts the SDK the same way a desktop browser would. *** ## Email delivery Embedded signing does not automatically suppress Lumin email notifications. Signers may still receive email from Lumin. *** ## Related resources * [Send Signature Request](/tabs/guides/walkthroughs/send-signature-request) — create signing requests from a PDF * [Create Signing Session](/tabs/api-reference/api/signature-requests/create-signing-session) — API reference for this endpoint * [Get Signing Link](/tabs/api-reference/api/signature-requests/get-signing-link) — redirect or hosted signing (not for iframe embed) * [Webhooks overview](/tabs/guides/webhooks/overview) — server-side completion events * [Authentication](/tabs/guides/authentication/overview) — API keys and OAuth 2.0 # Send Signature Request Source: https://developers.luminpdf.com/tabs/guides/walkthroughs/send-signature-request Send a document for signing from a file or a Lumin template using the Lumin API. Send a signature request from a PDF file or a Lumin template, then track status and download the signed result. ## Prerequisites * A valid API key or OAuth app with the `sign:requests` scope. Pass credentials as `Authorization: API-key ` or `Bearer ` on every request. * **From file:** a PDF accessible via a public URL, or a file you can upload directly. * **From template:** a Lumin template ID and the `templates` scope (OAuth) or API key access to templates. See [List Templates](/tabs/api-reference/api/templates/list-templates) to list available templates in your Workspace. *** ## Integration steps Use one of two endpoints: | Method | Endpoint | When to use | | ----------------- | -------------------------------------------- | -------------------------------------------------------------------------------- | | **From file** | `POST /signature_request/send` | You already have a PDF — link, upload, or host it yourself. | | **From template** | `POST /signature_request/send-from-template` | You have a Lumin template with merge tags, form fields, or variables to fill in. | See [Send Signature Request](/tabs/api-reference/api/signature-requests/send-signature-request) and [Send Signature Request from Template](/tabs/api-reference/api/signature-requests/send-signature-request-from-template) for full request schemas. Supply the document in one of four ways. Only **one** field is allowed per request. | Field | Type | Description | | ----------- | ------ | ------------------------------------------- | | `file_url` | string | Public HTTPS URL to a single file | | `file_urls` | array | Array of public HTTPS URLs (multiple files) | | `file` | binary | A single uploaded file (multipart) | | `files` | array | Multiple uploaded files (multipart) | Set `use_text_tags: true` if your PDF contains embedded text tags that define signing fields. See the [Text Tags guide](/tabs/guides/walkthroughs/text-tags). 1. Call `GET /templates` to find your `template_id`. See [List Templates](/tabs/api-reference/api/templates/list-templates). 2. Call `GET /templates/{template_id}` to see required `tags`, `fields`, `variables`, `signer_roles`, and `collections`. See [Get Template Details](/tabs/api-reference/api/templates/get-template-details). 3. Include `template_id` and the placeholder values your template expects: | Field | Used for | | ------------- | -------------------------------------------------- | | `tags` | Merge tags in Sign templates (`sign_` prefix) | | `fields` | Form fields in PDF or Sign templates | | `variables` | Variables in AgreementGen templates (`ag_` prefix) | | `collections` | Row-loop tables in AgreementGen templates | Each signer requires `email_address` and `name`. Set `signing_type` to control signing order: | Value | Behaviour | | ----------- | ---------------------------------------------------- | | `SAME_TIME` | All signers receive the request at once. Default. | | `ORDER` | Signers are notified sequentially by `group` number. | **From file** — assign `group` when using `ORDER`: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "signers": [ { "email_address": "alice@example.com", "name": "Alice", "group": 1 }, { "email_address": "bob@example.com", "name": "Bob", "group": 2 } ] } ``` **From template** — include `signer_role` when the template defines roles: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "signers": [ { "signer_role": "Tenant", "email_address": "tenant@example.com", "name": "Alex Tenant" }, { "signer_role": "Landlord", "email_address": "landlord@example.com", "name": "Sam Landlord" } ] } ``` Optionally add `viewers` for recipients who can view but not sign. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://api.luminpdf.com/v1/signature_request/send \ -H "Authorization: API-key " \ -H "Content-Type: application/json" \ -d '{ "title": "Financial Year-End Report Authorization", "file_url": "https://example.com/report.pdf", "signing_type": "ORDER", "expires_at": 1927510980694, "signers": [ { "email_address": "alice@example.com", "name": "Alice", "group": 1 }, { "email_address": "bob@example.com", "name": "Bob", "group": 2 } ] }' ``` ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://api.luminpdf.com/v1/signature_request/send-from-template \ -H "Authorization: API-key " \ -H "Content-Type: application/json" \ -d '{ "template_id": "sign_123456", "title": "Mutual NDA Agreement", "expires_at": 1927510980694, "tags": { "Client.Name": "Acme Corp", "Effective.Date": "2026-01-01" }, "signers": [ { "signer_role": "Tenant", "email_address": "tenant@example.com", "name": "Alex Tenant" }, { "signer_role": "Landlord", "email_address": "landlord@example.com", "name": "Sam Landlord" } ] }' ``` Both endpoints return `201` with the new request ID: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "signature_request": { "signature_request_id": "696d007913f3b8...", "created_at": "1768751225657", "status": "WAITING_FOR_PROCESSING" } } ``` `WAITING_FOR_PROCESSING` is the initial status. Processing is asynchronous, the request transitions to `NEED_TO_SIGN` or `WAITING_FOR_OTHERS` shortly after creation. Poll `GET /signature_request/{id}` to read the current state of the request and each signer: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl https://api.luminpdf.com/v1/signature_request/696d007913f3b8... \ -H "Authorization: API-key " ``` ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "signature_request": { "signature_request_id": "696d007913f3b8...", "title": "Financial Year-End Report Authorization", "status": "WAITING_FOR_OTHERS", "signing_type": "ORDER", "created_at": "1768751225657", "expires_at": "1927510980694", "signers": [ { "email_address": "alice@example.com", "name": "Alice", "group": 1, "status": "APPROVED", "is_approved": true }, { "email_address": "bob@example.com", "name": "Bob", "group": 2, "status": "NEED_TO_SIGN", "is_approved": false } ] } } ``` | Status | Meaning | | ------------------------ | --------------------------------------------------- | | `WAITING_FOR_PROCESSING` | Request received, being prepared | | `NEED_TO_SIGN` | The sender must also sign (when sender is a signer) | | `WAITING_FOR_OTHERS` | Waiting on at least one signer | | `APPROVED` | All signers have signed | | `REJECTED` | A signer declined | | `FAILED` | Processing error — check `reason` | | `CANCELLED` | Manually cancelled | Once the status is `APPROVED`, retrieve the signed document: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl "https://api.luminpdf.com/v1/signature_request/696d007913f3b8.../file?type=agreement" \ -H "Authorization: API-key " ``` ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "signed_url": "https://files.luminpdf.com/download/report-signed.pdf?expires=...", "expires_at": 1766726700 } ``` The `signed_url` is a pre-signed HTTPS download link that expires in 30 minutes. | `type` value | Returns | | ------------ | ----------------------------------------------------- | | `agreement` | The signed agreement PDF (default) | | `coc` | Certificate of Completion PDF | | `merged` | Agreement + Certificate of Completion in a single PDF | *** ## Webhooks Instead of polling, listen for the `signature_request_approved` webhook event to be notified when all signers complete the request. See the [Webhooks guide](/tabs/guides/webhooks/overview) to configure an endpoint. # Text Tags Source: https://developers.luminpdf.com/tabs/guides/walkthroughs/text-tags Embed signing field definitions directly in your PDF using text tags — no interactive document prep required. Text tags let you define signing fields inside the PDF itself. When you send a signature request via the API, Lumin parses the document for any tags that match the expected syntax and converts them into interactive form fields for your signers. This approach is ideal when you generate documents programmatically and want to define field placement without using the Lumin Sign editor. *** ## How to enable text tags Include `"use_text_tags": true` in your `POST /signature_request/send` request body. If you omit this field, it defaults to `false` and tags are ignored. ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "use_text_tags": true, "file_url": "https://example.com/my-document.pdf", ... } ``` *** ## Syntax A text tag is a string placed directly in the body of your PDF document, wrapped in square brackets with identifiers separated by pipe characters: ``` [type|req|signerN|Label|UniqueId] ``` **Example tags:** ``` [sig|req|signer1|Sign here|sig_001] [initial|noreq|signer2|Initials|init_002] [text|req|signer1|Full name|name_003] [date|noreq|signer1|Date signed|date_004] ``` *** ## Identifiers | Identifier | Accepted values | Required | Description | | --------------- | ---------------------------------- | -------- | -------------------------------------------------------------------------------------------------- | | **Field type** | `sig`, `initial`, `text`, `date` | Yes | The type of interactive form field to render | | **Requirement** | `req`, `noreq` | Yes | Whether the signer must complete this field before submitting | | **Assigner** | `signer1`, `signer2`, `signer3`, … | Yes | Maps to the signer at that index in the `signers` array of your API request | | **Label** | Any text | No | Placeholder text shown inside the field. Currently overridden by Lumin Sign's default placeholders | | **Unique ID** | Any text | No | A name you assign to the field for your own reference | ### Field types | Type | Renders as | | --------- | ---------------------- | | `sig` | Signature field | | `initial` | Initials field | | `text` | Single-line text input | | `date` | Date picker | *** ## Important notes Tags are detected by reading actual text in the PDF — not by OCR. If your tag text is embedded in an image, it will not be detected. * **PDF format is required.** Form fields may be placed incorrectly if the document is not a PDF. * **Tags remain in the rendered document.** To hide a tag, set its text color to match the document background color. * **No whitespace allowed inside tags.** If you need extra horizontal space for layout, use underscores (`_`) after the last identifier instead: ``` [sig|req|signer1|____________________] ``` *** ## Form field sizing * **Height** is fixed to accommodate a 12pt font. * **Width** equals the rendered length of the full tag string (including brackets). Add underscores after the last identifier to make a field wider. ``` [sig|req|signer1] ← narrow field [sig|req|signer1|____________] ← wider field ``` *** ## Validation behavior Lumin does not validate tag syntax at the time of the API request. Instead: * A tag missing any **required** identifier (type, requirement, assigner) is silently ignored — it is not converted into a form field and no error is returned. * A tag with an **invalid** value (e.g., wrong requirement type, or a signer index that does not exist in your `signers` array) triggers the `signature_request_invalid` webhook event after submission. You will not receive an immediate API error for malformed tags. Subscribe to the `signature_request_invalid` webhook event to catch these issues in your integration. See the [Webhooks guide](/tabs/guides/webhooks/overview) for setup instructions. ### Validation errors When a text tag is malformed or references invalid data, the `signature_request_invalid` webhook event is fired with an `error_code` and `error_message`. Below are the possible validation errors: | Error type | Meaning | Error message | | ------------------------------ | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | **Missing field type** | Parser cannot infer the field type from the tag text. | `can't detect field type from this text tag` | | **Undefined requirement type** | The requirement token is not a supported value (e.g. a typo of `req`). No form field is created. | `the requirement type is undefined. Form field could not be created from this text tag` | | **Wrong requirement type** | Incompatible pairing — e.g. `noreq` used on a `sig`, `initial`, or `date` field which must be required. | `the requirement type noreq can't be used for signature/initial/date field` | | **Missing requirement type** | No recognizable requirement segment in the tag. | `can't detect requirement type from this text tag` | | **Undefined assigner** | Signer reference (e.g. `signer3`) does not match any signer on the request. | `signer3 is undefined. Form field could not be assigned to this assigner` | | **Assigner index not numeric** | Signer slot is not parseable as a number (e.g. `signerOne` instead of `signer1`). | `Can't detect assigner index from this text tag` | | **Duplicated definition tag** | The unique ID collides with another tag (e.g. checkbox group definitions must be unique). | `definition tag must be unique for each check box group` | ### Example webhook payload When a text tag error is detected, the `signature_request_invalid` webhook event is sent: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "event": { "event_time": 1776408567122, "event_type": "signature_request_invalid", "event_metadata": {} }, "signature_request": { "signature_request_id": "69e1d7f623ab47de200c8903", "error_code": "invalid_request", "error_message": "The requirement type noreq can't be used for signature/initial/date field" } } ``` *** ## Example: complete request with text tags ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://api.luminpdf.com/v1/signature_request/send \ -H "Authorization: API-key " \ -H "Content-Type: application/json" \ -d '{ "title": "Tenant Agreement", "file_url": "https://example.com/tenant-agreement.pdf", "use_text_tags": true, "expires_at": 1927510980694, "signing_type": "SAME_TIME", "signers": [ { "email_address": "tenant@example.com", "name": "Alex Tenant" } ] }' ``` In this example, the PDF at `file_url` might contain: ``` Please sign below: [sig|req|signer1|Signature|sig_001] Date: [date|req|signer1|Date|date_001] Full name: [text|req|signer1|Full name|name_001] ``` `signer1` resolves to `tenant@example.com` because that signer is at index 1 in the `signers` array. # Account Webhooks Source: https://developers.luminpdf.com/tabs/guides/webhooks/account-webhooks Configure a single webhook URL to receive all events across your entire Lumin workspace. An account webhook covers your entire workspace. Once configured, Lumin sends all supported event types to your endpoint — regardless of which API key or user triggered them. Only the **Workspace Owner** can configure or update the account webhook URL. ## Configure an account webhook In Lumin, go to **Settings → Developer settings → API Key tab**. In the **Account callback** section, enter your endpoint URL. The URL must use HTTPS. Click **Save**. Lumin will begin delivering events to your endpoint immediately. ## Events received Your account webhook receives all [supported event types](/tabs/guides/webhooks/overview#supported-event-types) from across your workspace. There is no filtering — you receive every event triggered by any user or API key in the workspace. | Delivery behavior | Detail | | ----------------------- | ----------------------------------------------- | | One request per event | Each event triggers a separate HTTP POST | | Real-time delivery | Events are sent as they occur | | Workspace-wide coverage | All events from all users and API keys | | No opt-out | You cannot filter which event types you receive | ## Verify webhook signatures Every request Lumin sends includes the following headers: * **`User-Agent`**: Always `Lumin Sign API` * **`X-Signature`**: An HMAC-SHA256 hex digest of the request body, signed with your **Primary API key** Always verify the `X-Signature` header before processing a webhook payload. Reject any request where the signature does not match. **To verify a signature:** Extract the `X-Signature` value from the incoming request headers. Compute HMAC-SHA256 of the raw request body using your Primary API key as the secret. Compare your computed value to the `X-Signature` header. Use a constant-time comparison to avoid timing attacks. If the values do not match, reject the request with a non-200 status. **Example using OpenSSL:** ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} api_key='my_primary_api_key' json='{"event":{"event_time":1694664207595,"event_type":"signature_request_sent"},"signature_request":{"signature_request_id":"fa5c8a0b0f492d768749333ad6fcc214c111e967","title":"My first request"}}' echo -n $json | openssl dgst -sha256 -hmac $api_key # Expected X-Signature: 3810cb411041efab279d31698b9584372e5ede9d1641fbb354810f16e51be81c ``` Your Primary API key is available in **Settings → Developer settings → API Key**. ## Respond to events Your endpoint must return HTTP `200 OK` within 30 seconds of receiving a request. No response body is required. ```http theme={"theme":{"light":"github-light","dark":"github-dark"}} HTTP/1.1 200 OK Content-Type: application/json {} ``` Acknowledge the request immediately and process the event asynchronously in a background job. This ensures you stay within the 30-second response window even for complex workflows. ## Error handling and retries If your endpoint does not return `200 OK`, Lumin retries the delivery. The following conditions trigger a retry: * HTTP `4xx` or `5xx` response * No response within 30 seconds (timeout) * Connection failure For the full retry schedule, see [Retry schedule](/tabs/guides/webhooks/overview#retry-schedule). # App Webhooks Source: https://developers.luminpdf.com/tabs/guides/webhooks/app-webhooks Receive webhook events scoped to the OAuth permissions your integration app has been granted. An app webhook is tied to a specific OAuth 2.0 application. Lumin delivers events only for users who have authorized your app, and only for the event types covered by the OAuth scopes they granted. Only the **Workspace Owner** can configure or update the app webhook URL. App webhooks are only available for **Private (Server)** application types. ## How app webhooks differ from account webhooks | | Account webhooks | App webhooks | | ------------------ | ------------------------------------------- | -------------------------------------------- | | **Scope** | All events, all users in the workspace | Events from users who authorized your app | | **Filtering** | None — you receive everything | Limited to the OAuth scopes the user granted | | **Signing secret** | Primary API key | App signing secret | | **Use case** | Internal integrations, workspace monitoring | Third-party OAuth integrations | ## Configure an app webhook In Lumin, go to **Settings → Developer settings → Integration apps tab**. Click the app you want to configure. If you haven't created one yet, click **Create app**. Under the **Receive events on behalf of users** section, enter your endpoint URL. The URL must use HTTPS. Click **Save**. ## Events received Your app webhook receives events based on the OAuth scopes users have granted your app. For example, if a user does not grant the `sign:requests.read` or `sign:requests` scope, your app will not receive signature request events for that user. For the full list of available event types, see [Supported event types](/tabs/guides/webhooks/overview#supported-event-types). | Delivery behavior | Detail | | ------------------------- | --------------------------------------------------- | | One request per event | Each event triggers a separate HTTP POST | | Real-time delivery | Events are sent as they occur | | User-scoped coverage | Only events from users who authorized your app | | Scope-dependent filtering | Events limited to the OAuth scopes the user granted | ## Verify webhook signatures Every request Lumin sends includes the following headers: * **`User-Agent`**: Always `Lumin Sign API` * **`X-Signature`**: An HMAC-SHA256 hex digest of the request body, signed with your **app's signing secret** Always verify the `X-Signature` header before processing a webhook payload. Reject any request where the signature does not match. Your app's signing secret is available in **Settings → Developer settings → Integration apps → Application details**. **To verify a signature:** Extract the `X-Signature` value from the incoming request headers. Compute HMAC-SHA256 of the raw request body using your app's signing secret. Compare your computed value to the `X-Signature` header. Use a constant-time comparison to avoid timing attacks. If the values do not match, reject the request with a non-200 status. **Example using OpenSSL:** ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} signing_secret='your_app_signing_secret' json='{"event":{"event_time":1694664207595,"event_type":"signature_request_sent"},"signature_request":{"signature_request_id":"fa5c8a0b0f492d768749333ad6fcc214c111e967","title":"My first request"}}' echo -n $json | openssl dgst -sha256 -hmac $signing_secret # Expected X-Signature: 3810cb411041efab279d31698b9584372e5ede9d1641fbb354810f16e51be81c ``` ## Respond to events Your endpoint must return HTTP `200 OK` within 30 seconds of receiving a request. No response body is required. ```http theme={"theme":{"light":"github-light","dark":"github-dark"}} HTTP/1.1 200 OK Content-Type: application/json {} ``` Acknowledge the request immediately and process the event asynchronously in a background job. This ensures you stay within the 30-second response window. ## Error handling and retries If your endpoint does not return `200 OK`, Lumin retries the delivery. The following conditions trigger a retry: * HTTP `4xx` or `5xx` response * No response within 30 seconds (timeout) * Connection failure For the full retry schedule, see [Retry schedule](/tabs/guides/webhooks/overview#retry-schedule). # Webhooks Source: https://developers.luminpdf.com/tabs/guides/webhooks/overview Receive real-time push notifications from Lumin when events occur in your workspace — no polling required. Webhooks let your application react to events in Lumin as they happen. Instead of repeatedly calling the API to check for changes, Lumin sends an HTTP POST request to your endpoint with a JSON payload describing the event. Common use cases include: * Downloading signed documents as soon as all parties complete signing * Keeping external systems like CRMs or ERPs in sync with signing status * Notifying your team when a document is declined or cancelled * Triggering internal workflows after a signature request is approved ## How webhooks work When an event occurs in Lumin, the following sequence happens: A user or API action causes an event — for example, all signers complete a document. Lumin POSTs a JSON payload to your registered webhook URL describing the event. Your endpoint returns HTTP `200 OK` to confirm receipt. No response body is required. Your application processes the event — for example, calls `GET /v1/signature_request/{id}/file` to download the signed PDF. ### Example workflow A user sends a signature request in Lumin Sign. Once all signers complete it: 1. Lumin sends a `signature_request_approved` event to your endpoint. 2. Your server returns `200 OK`. 3. Your app calls `GET https://api.luminpdf.com/v1/signature_request/{id}/file` to download the Certificate of Completion and signed PDF. ## Types of webhooks Lumin supports two webhook types, scoped differently: | Type | Scope | Configuration | | -------------------- | ----------------------------------------------- | -------------------------------------------------------------- | | **Account webhooks** | All events across your entire workspace | Settings → Developer settings → API Key → Account callback | | **App webhooks** | Events limited to OAuth scopes granted by users | Settings → Developer settings → Integration apps → App webhook | * [Account webhooks](/tabs/guides/webhooks/account-webhooks) — Configure once and receive all supported events workspace-wide. * [App webhooks](/tabs/guides/webhooks/app-webhooks) — Scoped to the permissions your OAuth app has been granted. ## Supported event types | Event | Description | | ------------------------------------ | -------------------------------------------------------------------------- | | `signature_request_created` | A signature request was created successfully | | `signature_request_viewed` | A signer opened the document | | `signature_request_signed` | A signer signed the document | | `signature_request_approved` | All signers have completed signing | | `signature_request_declined` | A signer declined — the request status changes to `REJECTED` | | `signature_request_downloadable` | The signed document and/or Certificate of Completion is ready for download | | `signature_request_invalid` | An error occurred while processing the request (e.g. invalid text tags) | | `signature_request_canceled` | The signature request was cancelled | | `signature_request_cancel_failed` | The cancellation attempt failed | | `signature_request_due_date_updated` | The due date on the signature request was updated | ## Event payload structure All webhook events share the same JSON structure: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "event": { "event_time": 1758812586180, "event_type": "signature_request_downloadable", "event_metadata": { "workspace_id": "68d417dcdeaecfae84872de8" } }, "signature_request": { "signature_request_id": "68d558daa153cb3c30718518", "title": "Test webhook", "created_at": 1758812378277, "updated_at": 1758812574883, "expires_at": 1827510980694, "status": "APPROVED", "signers": [ { "name": "John Doe", "email": "john.doe@example.com", "status": "APPROVED", "is_approved": true } ] }, "details_url": "https://sign.luminpdf.com/auth?mode=view-contract&token=..." } ``` ## Retry schedule If your endpoint does not return `200 OK`, Lumin retries delivery up to six times: | Retry attempt | Delay after previous attempt | | ------------- | ---------------------------- | | First | 5 minutes | | Second | 15 minutes | | Third | 45 minutes | | Fourth | 2 hours 15 minutes | | Fifth | 6 hours 45 minutes | | Sixth | 20 hours 15 minutes | ## Failure conditions A delivery attempt is considered failed when: * **Timeout** — your endpoint does not respond within 30 seconds * **Non-200 response** — your endpoint returns any HTTP status other than `200` * **Connection failure** — Lumin cannot connect to your endpoint ## Best practices **Respond immediately, process asynchronously.** Return `200 OK` as quickly as possible, then handle the event in a background job. This keeps your response time well under the 30-second timeout. * **Implement idempotency** — Lumin may deliver the same event more than once due to retries. Use `signature_request_id` and `event_type` together as a unique key to detect and ignore duplicates. * **Verify signatures** — Every request includes an `X-Signature` header. Always validate it before processing the payload. See [Account webhooks](/tabs/guides/webhooks/account-webhooks) and [App webhooks](/tabs/guides/webhooks/app-webhooks) for verification details. * **Log all events** — Store raw payloads for debugging and audit purposes. * **Use HTTPS** — Lumin only delivers webhooks to HTTPS endpoints. # Test Locally Source: https://developers.luminpdf.com/tabs/guides/webhooks/test-locally Use ngrok to expose your local server and test Lumin webhooks during development. Lumin webhooks require a publicly accessible HTTPS URL. During development, your local server isn't reachable from the internet. A tunneling tool like **ngrok** solves this by creating a secure public URL that forwards requests to your local machine. ## Prerequisites Before you start: * A local web server running on a known port (e.g., Express.js on port 3000) * An [ngrok account](https://ngrok.com) — the free tier is sufficient * A webhook endpoint in your application that accepts POST requests ## Set up your local server If you don't have a webhook endpoint yet, here's a minimal example: ```javascript theme={"theme":{"light":"github-light","dark":"github-dark"}} const express = require("express"); const app = express(); app.use(express.json()); app.post("/webhook", (req, res) => { console.log("Webhook received:", JSON.stringify(req.body, null, 2)); res.status(200).json({ status: "ok" }); }); const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); }); ``` Log the full payload to your console so you can inspect the event structure as you test. ## Expose your local server with ngrok Follow the [ngrok installation guide](https://ngrok.com/docs/getting-started/) for your operating system. Make sure your application is running. For the example above: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} node server.js ``` Run ngrok against the port your server is listening on: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} ngrok http 3000 ``` ngrok displays a public URL in the terminal output — for example: ``` Forwarding https://abc123.ngrok-free.app -> http://localhost:3000 ``` Use the `https://` URL. Do not use the `http://` URL — Lumin requires HTTPS. 1. Go to **Settings → Developer settings → API Key tab → Account callback section**. 2. Enter your full ngrok URL including your endpoint path — for example, `https://abc123.ngrok-free.app/webhook`. 3. Click **Save**. Perform an action in Lumin that fires an event — for example, create a signature request to trigger `signature_request_created`. Watch your terminal for the incoming request. ## Example payload When a webhook arrives, your console will show a payload like this: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "event": { "event_time": 1758812613960, "event_type": "signature_request_created", "event_metadata": { "signer_email": "test@luminpdf.com", "workspace_id": "68d417..." } }, "signature_request": { "signature_request_id": "68d559...", "title": "Test webhook", "created_at": 1758812612210, "updated_at": 1758812612248, "expires_at": 1827510980694, "status": "NEED_TO_SIGN", "signers": [ { "name": "Signer 1", "email": "signer1@luminpdf.com", "status": "NEED_TO_SIGN", "is_approved": false } ], "details_url": "https://sign.luminpdf.com/auth?mode=view-contract&token=..." } } ``` ## Tips for debugging **Log the raw request body.** Before parsing JSON, log the raw body string — this is what Lumin signs with HMAC-SHA256 for the `X-Signature` header. Parsing and re-serializing can alter whitespace and break signature verification. **Check the `X-Signature` header.** Use the verification steps in [Account webhooks](/tabs/guides/webhooks/account-webhooks#verify-webhook-signatures) to confirm your signature validation logic works correctly before deploying. **Watch the ngrok inspector.** ngrok provides a local web interface at `http://localhost:4040` that shows each request and response in detail, including headers and body. This is useful for debugging without modifying your server code. ## Important limitations with ngrok Free ngrok sessions expire after 2 hours, and each restart generates a new URL. Remember to update your webhook URL in Lumin every time you restart ngrok. * Always use the `https://` URL, not `http://` * The free tier has rate limits on incoming requests * ngrok URLs are not suitable for production — deploy to a stable HTTPS endpoint before going live ## Next steps Once your local testing is working: 1. Deploy your webhook handler to a production server with a stable HTTPS URL. 2. Update the webhook URL in **Settings → Developer settings**. 3. Set up monitoring and alerting for failed deliveries. See [Account webhooks](/tabs/guides/webhooks/account-webhooks) and [App webhooks](/tabs/guides/webhooks/app-webhooks) for full configuration details. # Common MCP Client Source: https://developers.luminpdf.com/tabs/mcp/client We are providing a common MCP client for Lumin MCP. You can use this client to connect your AI tool to Lumin. ## Cursor Users Add the following configuration to your `.cursor/mcp.json` file: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "mcpServers": { "Lumin": { "command": "npx", "args": ["mcp-remote", "https://mcp.luminpdf.com/mcp"] } } } ``` ## Claude Users Add the following configuration to your `claude_desktop_config.json` file: **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`\ **Windows:** `%APPDATA%\Claude\claude_desktop_config.json` ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "mcpServers": { "Lumin": { "command": "npx", "args": ["mcp-remote", "https://mcp.luminpdf.com/mcp"] } } } ``` Installation instructions for other MCP clients will be provided soon. ## Troubleshooting If you encounter issues during installation with Cursor, try clearing cached credentials located at: ``` /Users//.mcp-auth/ ``` # Connecting to Lumin MCP Source: https://developers.luminpdf.com/tabs/mcp/connect Learn how to get started and plug Lumin into your AI tool. This guide walks you through connecting your AI tool to Lumin using the Model Context Protocol (MCP). Once connected, your tool can request live context from a user’s Lumin based on their access and permissions. ## Connect through your AI tool We are providing a Streamable HTTP protocol for Lumin MCP. You can use this protocol to connect your AI tool to Lumin. To connect, search for "Lumin MCP" in your tool's MCP directory or use the following connection method: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "mcpServers": { "Lumin": { "url": "https://mcp.luminpdf.com/mcp" } } } ``` ## Troubleshooting connection issues If you're experiencing issues connecting your AI tool to Lumin MCP, here are some common solutions: **1. Confirm MCP Client Compatibility**: ensure your AI tool includes built-in MCP client functionality and can connect to MCP servers. Not all tools currently offer this capability. **2. Check Remote Server Connectivity**: some AI tools support MCP clients but cannot connect to remote servers. **3. Ask for MCP Support if Needed**: if your AI tool lacks MCP support entirely, consider contacting its developers to request MCP server integration. Encouraging broader MCP compatibility helps strengthen the overall ecosystem. # Supported Tools Source: https://developers.luminpdf.com/tabs/mcp/supportedTool We are supporting the following tools with Lumin MCP: ## User | Tool Name | Description | Sample Prompt | | --------------------------------- | ------------------------------------------------------------------------------------------ | -------------------------------- | | **get\_lumin\_user\_information** | Get the current authenticated user's profile, including their ID, name, and email address. | "Get my Lumin user information." | ## Workspace | Tool Name | Description | Sample Prompt | | ------------------------ | ---------------------------------------------------------------------------------------------- | ---------------------------- | | **get\_workspace\_info** | Get details about the current Workspace, including name, owner, member count, and Space count. | "Show me my workspace info." | ## Documents | Tool Name | Description | Sample Prompt | | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | | **upload\_file\_to\_lumin** | Upload a document to the workspace from a file URL (`file-upload` method) or create from a template (`template` method). | "Upload this PDF to my Lumin workspace." | | **lumin\_markdown2pdf** | Convert markdown content into a downloadable PDF. Returns a temporary URL (valid for 1 hour) that can be passed to other tools. | "Convert this markdown to a PDF." | ## Templates | Tool Name | Description | Sample Prompt | | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | | **lumin\_get\_templates** | List all available templates in the current workspace. | "List all my Lumin templates." | | **lumin\_get\_template\_details** | Get full details of a specific template, including signer roles, fields, tags, and variables. | "Show me the details for template `sign_abc123`." | | **lumin\_generate\_document\_from\_template** | Generate a PDF document from a template, optionally filling in tags, fields, and variables. Returns a download URL. | "Generate a document from template `pdf_12345` with these values." | ## Signature Requests | Tool Name | Description | Sample Prompt | | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | **send\_signature\_request\_on\_lumin** | Create and send a new signature request from a file URL. | "Send a signature request using this file to [john@example.com](mailto:john@example.com)." | | **lumin\_send\_signature\_request\_from\_template** | Send a signature request directly from a template in one step — combines template generation and signature sending. | "Send a signature request from template `sign_abc123` to [john@example.com](mailto:john@example.com)." | | **get\_signature\_request\_on\_lumin\_by\_id** | Retrieve details of a specific signature request by its ID, including status, signers, and expiration. | "Show me the signature request with ID `sr_12345`." | | **lumin\_get\_signature\_request\_file** | Get a temporary download URL for a completed signature request's file (`agreement`, `coc`, or `merged`). | "Download the signed agreement for request `sr_12345`." | | **cancel\_signature\_request\_on\_lumin\_by\_id** | Cancel an active signature request. This action is irreversible. | "Cancel the signature request with ID `sr_12345`." | ## Agreements | Tool Name | Description | Sample Prompt | | ------------------------------- | --------------------------------------------------------------------- | ------------------------------------------------ | | **lumin\_post\_agreement** | Create a new AgreementGen document from a template with dynamic data. | "Create an agreement from template `ag_abc123`." | | **lumin\_get\_agreement\_file** | Get a temporary download URL for an agreement's PDF file. | "Download the PDF for agreement `ag_12345`." |