openapi: 3.1.0 info: title: Paraph API version: 1.0.0 contact: name: Paraph Support url: https://paraph.dev email: support@paraph.dev termsOfService: https://paraph.dev/terms description: | Document API for developers. Upload PDF templates, fill them via API, and send for signing when you need to. Both workflows use the same endpoint. ## Quick Start Upload a PDF template, then create requests against it. All requests require `Authorization: Bearer `. ### Fill a PDF 1. [Upload a template](#tag/templates/POST/templates) with a PDF that has named form fields. The response includes the template `id` and the detected fields. 2. [Create a request](#tag/requests/POST/requests) with the `template_id` and a `fields` map. Status is `success` immediately. 3. [Download the filled PDF](#tag/requests/GET/requests/{id}/download). --- ### Fill + send for signing Same as above, but add signers. You'll need to set up signature placements on the template first (one-time). 1. [Upload a template](#tag/templates/POST/templates). 2. [Update the template](#tag/templates/PUT/templates/{id}) with `signature_placements` (page, coordinates, and which signer label goes where). 3. [Create a request](#tag/requests/POST/requests) with a `fields` map and a `signers` map keyed by label. Each signer gets an email with a signing link. Status is `pending` until everyone signs. 4. [Download the signed PDF](#tag/requests/GET/requests/{id}/download) once all signers have completed. Use [webhooks](#tag/webhooks) to get notified instead of polling. --- ### Pre-signed documents If you already have a signer's signature image (captured in your own app, for example), pass `override_signature_url` in the `SignerInput` when [creating a request](#tag/requests/POST/requests). That signer is marked as signed immediately and no email is sent. You can mix this with regular email-based signing on the same request. --- ### Webhooks 1. [Create a webhook](#tag/webhooks/POST/webhooks) with a URL and the events you care about. 2. Each delivery is a POST with `Content-Type: application/json`. See the `WebhookDelivery` schema for the payload format. 3. Verify deliveries using the `secret` returned at creation time. Each delivery includes an `X-Signature-256` header with an HMAC-SHA256 signature. --- ### Metadata Pass a `metadata` object (up to 10 key-value pairs) when [creating a request](#tag/requests/POST/requests) to tag it with your own identifiers. Metadata is returned on all [request endpoints](#tag/requests) and in webhook deliveries. --- ### Sandbox mode Sandbox requests **bypass plan quotas** and produce PDFs with a diagonal **SAMPLE** watermark. A request is sandbox when either: - The API key used is a sandbox key (`api_key_sandbox: true` on [account](#tag/account/GET/account)), OR - The team has sandbox mode enabled team-wide (`sandbox_mode: true`) New accounts are created with a default sandbox API key so first requests don't count toward the plan. Create additional sandbox or production keys from the admin dashboard. A key's mode is set at creation and cannot be changed later — create a new key instead. --- ### Salesforce integration Paraph offers a managed package on the Salesforce AppExchange. The SF package talks to this same API — no special endpoints, no separate auth. **Setup (one-time per SF org):** 1. In Paraph: create an API key (Admin → API Keys). Name it something like "Salesforce production". 2. In Salesforce: open the paraph managed package's setup wizard and paste the API key. The package stores it in a Named Credential. 3. Done. Every Apex callout auto-injects `Authorization: Bearer ` via the Named Credential's custom header. **What Apex code looks like:** ```apex HttpRequest req = new HttpRequest(); req.setEndpoint('callout:Paraph_Named_Credential/api/v1/requests'); req.setMethod('POST'); req.setHeader('Content-Type', 'application/json'); req.setBody(payload); HttpResponse res = new Http().send(req); ``` **Rotation:** create a second API key, paste the new one into SF, delete the old one from Paraph. Both are valid simultaneously so there is no downtime window. **Revocation:** delete the key in Paraph. The next SF callout fails with `401`; the admin pastes a fresh key. --- ### Common headers All API responses include: - `X-Request-Id` — unique identifier for the request, useful for debugging and support. - `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` — rate limit status. servers: - url: https://paraph.dev/api/v1 description: Production security: - bearerAuth: [] tags: - name: Templates description: | A template is a PDF you upload once and reuse for every request. Paraph reads the PDF's AcroForm fields (text, date, checkbox, etc.) and returns them so you know what values to pass when creating a request. If the template needs signatures, add signature placements to define where each signer signs on the page. PDFs using XFA forms (Adobe LiveCycle) are not supported. - name: Requests description: | A request fills a template's fields and optionally sends the document for signing. Requests without signers produce a filled PDF immediately (status `success`). Requests with signers send each signer an email containing a signing link (status `pending` until all sign). Use the download endpoint to get the final PDF. **Data retention:** Requests are subject to plan-based retention limits. On the Free plan, requests older than 30 days are no longer accessible. Pro plans retain requests for 5 years. Enterprise plans have unlimited retention. - name: Signers description: | Each signer on a request has a label (matching a signature placement on the template), an email address, and a status. Signers receive an email with a link to sign. You can resend the email or download a signer's signature image after they complete signing. - name: Account description: | View your team's plan, usage, and limits. - name: Webhooks description: | Subscribe to events via webhook URLs. components: securitySchemes: bearerAuth: type: http scheme: bearer description: API key from the Paraph dashboard (Admin → API Keys) headers: X-Request-Id: description: Unique identifier for this request. Include in support tickets for faster debugging. schema: type: string example: "req_abc123def456" schemas: Account: type: object required: [team_name, plan, sandbox_mode, api_key_name, api_key_sandbox, limits, usage] properties: team_name: type: string plan: type: string enum: [free, pro, enterprise] sandbox_mode: type: boolean description: | Team-wide sandbox toggle. When true, every request from this team (web or API) is treated as sandbox regardless of which key is used — plan quotas are bypassed and PDFs get a SAMPLE watermark. api_key_name: type: string description: Name of the API key used for this request api_key_sandbox: type: boolean description: | Whether the API key used for this request is a sandbox key. Sandbox keys are always in sandbox regardless of `sandbox_mode`. A request is effectively sandbox when either flag is true. Mode is set at key creation time and cannot be changed. limits: type: object required: [requests_per_month, signing_requests_per_month, max_templates, max_members] properties: requests_per_month: type: integer description: "-1 means unlimited" signing_requests_per_month: type: integer description: "-1 means unlimited" max_templates: type: integer description: "-1 means unlimited" max_members: type: integer description: "-1 means unlimited" usage: type: object required: [requests, signing_requests, templates, members] properties: requests: type: integer description: Requests created in the last 30 days signing_requests: type: integer description: Signing tasks created in the last 30 days templates: type: integer description: Total templates in the account members: type: integer description: Total team members example: team_name: "Acme Corp" plan: "pro" sandbox_mode: false api_key_name: "production-key" api_key_sandbox: false limits: requests_per_month: 1000 signing_requests_per_month: -1 max_templates: -1 max_members: 10 usage: requests: 342 signing_requests: 15 templates: 3 members: 2 Error: type: object description: | All error responses use this format. required: [error] properties: error: type: object required: [error_name, error_msg] properties: error_name: type: string enum: - unauthorized - not_found - bad_request - exceeded_quota - exceeded_rate - unknown - merge_failed - invalid_status error_msg: type: string example: error: error_name: "bad_request" error_msg: "invalid email address" ListInfo: type: object required: [page, num_pages, num_results, page_size] properties: page: type: integer num_pages: type: integer num_results: type: integer page_size: type: integer example: page: 1 num_pages: 3 num_results: 48 page_size: 20 Template: type: object required: [id, name, created_at, updated_at] properties: id: type: string format: uuid name: type: string maxLength: 255 metadata: type: object additionalProperties: x-additionalPropertiesName: key type: string maxLength: 1024 maxProperties: 10 description: Arbitrary key-value pairs (max 10 keys, key max 128 chars, value max 1024 chars) created_at: type: string format: date-time updated_at: type: string format: date-time example: id: "d290f1ee-6c54-4b01-90e6-d701748f0851" name: "Employment Agreement" metadata: {"category": "hr"} created_at: "2026-03-15T10:30:00Z" updated_at: "2026-03-15T10:30:00Z" TemplateDetail: allOf: - $ref: '#/components/schemas/Template' - type: object required: [fields, signature_placements] properties: fields: type: array description: Form fields detected in the uploaded PDF items: $ref: '#/components/schemas/Field' signature_placements: type: array description: Signature placement regions configured on this template items: $ref: '#/components/schemas/SignaturePlacement' example: id: "d290f1ee-6c54-4b01-90e6-d701748f0851" name: "Employment Agreement" metadata: {"category": "hr"} created_at: "2026-03-15T10:30:00Z" updated_at: "2026-03-15T10:30:00Z" fields: - id: "f47ac10b-58cc-4372-a567-0e02b2c3d479" name: "employee_name" type: "text" options: [] - id: "7c9e6679-7425-40de-944b-e07fc1f90ae7" name: "start_date" type: "date" options: [] - id: "550e8400-e29b-41d4-a716-446655440000" name: "department" type: "combobox" options: ["Engineering", "Sales", "Marketing"] signature_placements: - id: "c1d2e3f4-5678-9abc-def0-1234567890ab" signer_label: "Employee" page_number: 1 x: 100.0 y: 650.0 width: 200.0 height: 50.0 Field: type: object required: [id, name, type, options] properties: id: type: string format: uuid name: type: string description: Field name as defined in the PDF form type: type: string enum: [text, date, checkbox, combobox, listbox, radio_group] description: AcroForm field type options: type: array items: type: string description: Available options for ComboBox, ListBox, or RadioButtonGroup fields example: id: "f47ac10b-58cc-4372-a567-0e02b2c3d479" name: "employee_name" type: "text" options: [] SignaturePlacement: type: object required: [id, signer_label, page_number, x, y, width, height] properties: id: type: string format: uuid signer_label: type: string maxLength: 128 description: Label identifying which signer this placement belongs to page_number: type: integer x: type: number description: X coordinate in PDF points from the left edge of the page y: type: number description: Y coordinate in PDF points from the bottom edge of the page width: type: number height: type: number example: id: "c1d2e3f4-5678-9abc-def0-1234567890ab" signer_label: "Employee" page_number: 1 x: 100.0 y: 650.0 width: 200.0 height: 50.0 SignaturePlacementInput: type: object required: [signer_label, page_number, x, y, width, height] properties: signer_label: type: string maxLength: 128 page_number: type: integer x: type: number y: type: number width: type: number height: type: number WebhookEvent: type: string enum: - request.created - request.success - request.cancelled - request.error - signer.viewed - signer.signed - signer.declined - webhook.test description: > Event types. `request.created`, `request.success`, `request.cancelled`, and `request.error` are dispatched for request lifecycle transitions. `signer.viewed` fires each time a signer loads their signing link, `signer.signed` fires when a signer completes signing, and `signer.declined` fires when a signer explicitly declines to sign. `webhook.test` is sent when you use the test endpoint to verify your webhook URL. Webhook: type: object required: [id, url, events, active, created_at, updated_at] properties: id: type: string format: uuid url: type: string format: uri events: type: array items: $ref: '#/components/schemas/WebhookEvent' active: type: boolean created_at: type: string format: date-time updated_at: type: string format: date-time example: id: "b1c2d3e4-5678-9abc-def0-1234567890ab" url: "https://example.com/webhook" events: ["request.success", "signer.signed"] active: true created_at: "2026-03-23T10:00:00Z" updated_at: "2026-03-23T10:00:00Z" WebhookDelivery: type: object description: | Payload delivered to your webhook URL when an event occurs. **Verifying deliveries**: Paraph follows the [Standard Webhooks](https://standardwebhooks.com) spec. Each delivery carries three headers: - `webhook-id` — unique UUID for this delivery (use to deduplicate retries). - `webhook-timestamp` — Unix seconds when the delivery was sent. Reject deliveries whose timestamp is too far from now to guard against replays. - `webhook-signature` — `v1,` where `` is the HMAC-SHA256 of `"{webhook-id}.{webhook-timestamp}.{body}"` using your webhook's secret as the key. Language libraries that implement the spec can verify deliveries in a few lines — see `@standard-webhooks/webhooks` on npm and equivalents for other runtimes. Failed deliveries are retried up to 5 times with exponential backoff (1s, 2s, 4s, 8s, 16s). Each attempt has a 10-second timeout. After all retries are exhausted the delivery is marked as failed. required: [event, timestamp, data] properties: event: $ref: '#/components/schemas/WebhookEvent' timestamp: type: string format: date-time data: type: object description: Event-specific payload properties: request_id: type: string format: uuid template_id: type: string format: uuid signer_id: type: string format: uuid description: Present for signer.* events signer_label: type: string description: Present for signer.* events recipient_email: type: string format: email description: Present for signer.* events examples: - event: "request.success" timestamp: "2026-03-24T14:22:00Z" data: request_id: "a1b2c3d4-5678-9abc-def0-1234567890ab" template_id: "d290f1ee-6c54-4b01-90e6-d701748f0851" - event: "signer.signed" timestamp: "2026-03-24T14:22:00Z" data: request_id: "a1b2c3d4-5678-9abc-def0-1234567890ab" template_id: "d290f1ee-6c54-4b01-90e6-d701748f0851" signer_id: "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d" signer_label: "Employee" recipient_email: "jane.doe@example.com" - event: "signer.viewed" timestamp: "2026-03-24T14:18:12Z" data: request_id: "a1b2c3d4-5678-9abc-def0-1234567890ab" signer_id: "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d" signer_label: "Employee" - event: "signer.declined" timestamp: "2026-03-24T14:20:05Z" data: request_id: "a1b2c3d4-5678-9abc-def0-1234567890ab" signer_id: "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d" signer_label: "Employee" RequestStatus: type: string enum: [success, error, pending, cancelled] description: | Lifecycle status of a document request. - `success` — fill completed (and all signers signed, if any) - `error` — terminal failure - `pending` — awaiting signer action - `cancelled` — caller cancelled before completion SignerStatus: type: string enum: [pending, signed, expired, error, cancelled] description: | Per-signer status. - `pending` — signing link sent, awaiting signer - `signed` — signer completed - `expired` — signer did not sign before `expires_at` - `error` — terminal failure for this signer - `cancelled` — parent request was cancelled DocumentRequestSummary: type: object description: Compact request representation returned in list endpoints. Use GET /requests/{id} for full details including inputs, metadata, and signers. required: [id, template_id, status, has_signing, created_at, updated_at] properties: id: type: string format: uuid template_id: type: string format: uuid title: type: string maxLength: 255 status: $ref: '#/components/schemas/RequestStatus' has_signing: type: boolean created_at: type: string format: date-time updated_at: type: string format: date-time example: id: "a1b2c3d4-5678-9abc-def0-1234567890ab" template_id: "d290f1ee-6c54-4b01-90e6-d701748f0851" title: "Employment Agreement - Jane Doe" status: "success" has_signing: true created_at: "2026-03-22T10:30:00Z" updated_at: "2026-03-22T10:30:00Z" DocumentRequest: type: object description: Full request representation returned by detail and create endpoints. Includes inputs, metadata, and signers. required: [id, template_id, status, has_signing, created_at, updated_at] properties: id: type: string format: uuid template_id: type: string format: uuid title: type: string maxLength: 255 description: Display title for the request message: type: string maxLength: 1024 description: Custom message included in signing emails status: $ref: '#/components/schemas/RequestStatus' has_signing: type: boolean description: Whether this request includes signers created_at: type: string format: date-time updated_at: type: string format: date-time inputs: type: object additionalProperties: x-additionalPropertiesName: field_name type: string description: Field values used to fill the template metadata: type: object additionalProperties: x-additionalPropertiesName: key type: string maxLength: 1024 maxProperties: 10 description: Arbitrary key-value pairs for your own use (max 10 keys, key max 128 chars, value max 1024 chars) signers: type: array description: Signers attached to this request, if any items: $ref: '#/components/schemas/Signer' example: id: "a1b2c3d4-5678-9abc-def0-1234567890ab" template_id: "d290f1ee-6c54-4b01-90e6-d701748f0851" title: "Employment Agreement - Jane Doe" status: "success" has_signing: true created_at: "2026-03-22T10:30:00Z" updated_at: "2026-03-22T10:30:00Z" inputs: employee_name: "Jane Doe" start_date: "2026-04-01" signers: - id: "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d" signer_label: "Employee" recipient_email: "jane.doe@example.com" status: "signed" expires_at: "2026-04-05T10:30:00Z" signed_at: "2026-03-23T14:22:00Z" Signer: type: object required: [id, signer_label, recipient_email, status] properties: id: type: string format: uuid signer_label: type: string description: Role label for this signer (e.g. "Employee", "Manager") recipient_email: type: string format: email status: $ref: '#/components/schemas/SignerStatus' expires_at: type: string format: date-time description: When the signing link expires signed_at: type: string format: date-time description: When the signer completed signing example: id: "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d" signer_label: "Employee" recipient_email: "jane.doe@example.com" status: "pending" expires_at: "2026-04-05T10:30:00Z" signed_at: null SignerInput: type: object required: [email] properties: email: type: string format: email maxLength: 254 override_signature_url: type: string format: uri description: | URL to a PNG signature image. When provided, the image is downloaded and stored, the signer is immediately marked as signed, and no signing email is sent. TemplateListResponse: type: object properties: templates: type: array items: $ref: '#/components/schemas/Template' list_info: $ref: '#/components/schemas/ListInfo' RequestListResponse: type: object properties: requests: type: array items: $ref: '#/components/schemas/DocumentRequestSummary' list_info: $ref: '#/components/schemas/ListInfo' WebhookListResponse: type: object properties: webhooks: type: array items: $ref: '#/components/schemas/Webhook' list_info: $ref: '#/components/schemas/ListInfo' TemplateResponse: type: object properties: template: $ref: '#/components/schemas/TemplateDetail' RequestResponse: type: object properties: request: $ref: '#/components/schemas/DocumentRequest' WebhookResponse: type: object properties: webhook: $ref: '#/components/schemas/Webhook' parameters: page: name: page in: query schema: type: integer default: 1 minimum: 1 page_size: name: page_size in: query schema: type: integer default: 20 minimum: 1 maximum: 100 responses: BadRequest: description: Invalid input content: application/json: schema: $ref: '#/components/schemas/Error' Unauthorized: description: Missing or invalid API key content: application/json: schema: $ref: '#/components/schemas/Error' NotFound: description: Resource not found content: application/json: schema: $ref: '#/components/schemas/Error' Conflict: description: Invalid state transition content: application/json: schema: $ref: '#/components/schemas/Error' TooManyRequests: description: Rate limit or quota exceeded content: application/json: schema: $ref: '#/components/schemas/Error' InternalError: description: Internal server error content: application/json: schema: $ref: '#/components/schemas/Error' BadGateway: description: Upstream delivery failed content: application/json: schema: $ref: '#/components/schemas/Error' paths: /account: get: tags: [Account] summary: Get account info operationId: getAccount description: Returns the current team's plan, usage counters, and limits for the API key used in the request. responses: '200': description: Account info content: application/json: schema: type: object required: [account] properties: account: $ref: '#/components/schemas/Account' example: account: team_name: "Acme Corp" plan: "pro" sandbox_mode: false api_key_name: "production-key" api_key_sandbox: false limits: requests_per_month: 1000 signing_requests_per_month: -1 max_templates: -1 max_members: 10 usage: requests: 342 signing_requests: 15 templates: 3 members: 2 '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/InternalError' /templates: get: tags: [Templates] summary: List templates operationId: listTemplates description: Returns all templates in your account, newest first. parameters: - $ref: '#/components/parameters/page' - $ref: '#/components/parameters/page_size' responses: '200': description: Paginated list of templates content: application/json: schema: $ref: '#/components/schemas/TemplateListResponse' example: templates: - id: "d290f1ee-6c54-4b01-90e6-d701748f0851" name: "Employment Agreement" metadata: {"category": "hr"} created_at: "2026-03-15T10:30:00Z" updated_at: "2026-03-28T17:35:16-05:00" - id: "e7f3a1b2-4c56-4d89-a012-3456789abcde" name: "Invoice" metadata: {"category": "billing"} created_at: "2026-03-10T08:15:00Z" updated_at: "2026-03-28T17:35:16-05:00" list_info: page: 1 num_pages: 1 num_results: 2 page_size: 20 '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/InternalError' post: tags: [Templates] summary: Create template operationId: createTemplate description: | Upload a PDF with named form fields. Paraph detects the fields automatically. **Provide exactly one of `file` or `file_url`.** Requests with neither or both return `400 Bad Request`. This constraint is validated server-side — OpenAPI doesn't support expressing `oneOf` inside a multipart request body, so it's documented in prose. requestBody: required: true content: multipart/form-data: schema: type: object description: Upload a PDF template with named form fields. Provide exactly one of `file` or `file_url`. required: [name] properties: name: type: string maxLength: 255 description: Display name for the template file: type: string format: binary description: PDF file with AcroForm fields (mutually exclusive with `file_url`) file_url: type: string format: uri description: URL to fetch the PDF from (mutually exclusive with `file`) responses: '201': description: Template created content: application/json: schema: $ref: '#/components/schemas/TemplateResponse' example: template: id: "d290f1ee-6c54-4b01-90e6-d701748f0851" name: "Employment Agreement" metadata: {} created_at: "2026-03-15T10:30:00Z" updated_at: "2026-03-28T17:35:16-05:00" fields: - id: "f47ac10b-58cc-4372-a567-0e02b2c3d479" name: "employee_name" type: "text" options: [] - id: "7c9e6679-7425-40de-944b-e07fc1f90ae7" name: "start_date" type: "date" options: [] signature_placements: [] '400': description: Validation error or PDF could not be parsed content: application/json: schema: $ref: '#/components/schemas/Error' examples: unsupported_pdf: summary: Unsupported form fields value: error: error_name: "bad_request" error_msg: "This PDF could not be parsed. It may use unsupported form field features." not_a_pdf: summary: Not a valid PDF value: error: error_name: "bad_request" error_msg: "File is not a valid PDF." name_too_long: summary: Name exceeds length limit value: error: error_name: "bad_request" error_msg: "name exceeds 255 characters" invalid_file_url: summary: Invalid or unreachable file URL value: error: error_name: "bad_request" error_msg: "invalid or unreachable URL" file_url_download_failed: summary: Failed to download PDF from URL value: error: error_name: "bad_request" error_msg: "failed to download PDF from URL" '429': $ref: '#/components/responses/TooManyRequests' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalError' /templates/{id}: get: tags: [Templates] summary: Get template detail operationId: getTemplate description: Returns the template with its detected form fields and signature placements. parameters: - name: id in: path required: true schema: type: string format: uuid responses: '200': description: Template detail with fields content: application/json: schema: $ref: '#/components/schemas/TemplateResponse' example: template: id: "d290f1ee-6c54-4b01-90e6-d701748f0851" name: "Employment Agreement" metadata: {"category": "hr"} created_at: "2026-03-15T10:30:00Z" updated_at: "2026-03-28T17:35:16-05:00" fields: - id: "f47ac10b-58cc-4372-a567-0e02b2c3d479" name: "employee_name" type: "text" options: [] - id: "7c9e6679-7425-40de-944b-e07fc1f90ae7" name: "start_date" type: "date" options: [] - id: "550e8400-e29b-41d4-a716-446655440000" name: "department" type: "combobox" options: ["Engineering", "Sales", "Marketing"] signature_placements: [] '404': $ref: '#/components/responses/NotFound' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/InternalError' patch: tags: [Templates] summary: Update template operationId: updateTemplate description: Change the template name, metadata, or signature placements. Omitted fields are left unchanged. parameters: - name: id in: path required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: type: object properties: name: type: string maxLength: 255 description: New template name (omit to leave unchanged) metadata: type: object additionalProperties: x-additionalPropertiesName: key type: string maxLength: 1024 maxProperties: 10 description: Replace all metadata (omit to leave unchanged, key max 128 chars, value max 1024 chars) signature_placements: type: array items: $ref: '#/components/schemas/SignaturePlacementInput' description: Replace all signature placements (omit to leave unchanged) example: name: "Employment Agreement v2" metadata: {"category": "hr", "year": "2026"} signature_placements: - signer_label: "Employee" page_number: 1 x: 100.0 y: 650.0 width: 200.0 height: 50.0 responses: '200': description: Updated template content: application/json: schema: $ref: '#/components/schemas/TemplateResponse' example: template: id: "d290f1ee-6c54-4b01-90e6-d701748f0851" name: "Employment Agreement v2" metadata: {"category": "hr", "year": "2026"} created_at: "2026-03-15T10:30:00Z" updated_at: "2026-03-28T17:35:16-05:00" fields: - id: "f47ac10b-58cc-4372-a567-0e02b2c3d479" name: "employee_name" type: "text" options: [] - id: "7c9e6679-7425-40de-944b-e07fc1f90ae7" name: "start_date" type: "date" options: [] signature_placements: [] '400': description: Validation error content: application/json: schema: $ref: '#/components/schemas/Error' examples: name_too_long: summary: Name exceeds length limit value: error: error_name: "bad_request" error_msg: "name exceeds 255 characters" signer_label_required: summary: Missing signer label in placement value: error: error_name: "bad_request" error_msg: "signer_label is required" signer_label_too_long: summary: Signer label exceeds length limit value: error: error_name: "bad_request" error_msg: "signer_label exceeds 128 characters" signer_label_invalid_chars: summary: Signer label contains invalid characters value: error: error_name: "bad_request" error_msg: "signer_label contains invalid characters" '404': $ref: '#/components/responses/NotFound' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/InternalError' delete: tags: [Templates] summary: Delete template operationId: deleteTemplate description: Permanently removes the template. Existing requests created from it are not affected. parameters: - name: id in: path required: true schema: type: string format: uuid responses: '204': description: Template deleted '404': $ref: '#/components/responses/NotFound' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/InternalError' /templates/{id}/download: get: tags: [Templates] summary: Download template PDF operationId: downloadTemplate description: Returns the original PDF that was uploaded when the template was created. parameters: - name: id in: path required: true schema: type: string format: uuid responses: '200': description: PDF file headers: Content-Disposition: schema: type: string description: 'attachment; filename="template.pdf"' content: application/pdf: schema: type: string format: binary '404': $ref: '#/components/responses/NotFound' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/InternalError' /requests: post: tags: [Requests] summary: Create request operationId: createRequest description: | Fills the template's form fields and creates a request. If `signers` is provided and the template has signature placements configured, each signer receives a signing link via email. To provide a signer's signature directly (skipping the email), use `override_signature_url` in the signer object. Omitting `signers` produces a fill-only PDF with no signing flow. Use `GET /requests/{id}/download` to retrieve the filled or signed PDF. parameters: - name: Idempotency-Key in: header required: false schema: type: string maxLength: 255 description: > Optional. If provided, ensures the request is processed at most once within 24 hours. Retries with the same key and body return the original response without creating a duplicate. Using the same key with a different request body returns 409 Conflict. requestBody: required: true content: application/json: schema: type: object required: [template_id] properties: template_id: type: string format: uuid description: ID of the template to fill fields: type: object additionalProperties: x-additionalPropertiesName: field_name type: string description: Map of field names to values. Text fields accept strings. Checkbox fields accept "checked" or "unchecked". signers: type: object additionalProperties: x-additionalPropertiesName: signer_label allOf: - $ref: '#/components/schemas/SignerInput' description: > Map of signer label to signer configuration. Each key is a signer label defined in the template's signature placements. Omit entirely for fill-only requests. title: type: string maxLength: 255 description: Display title for the request. Defaults to the template name. Shown in emails and dashboards. message: type: string maxLength: 1024 description: Custom message included in signing emails sent to signers. metadata: type: object additionalProperties: x-additionalPropertiesName: key type: string maxLength: 1024 maxProperties: 10 description: Arbitrary key-value pairs for your own use (max 10 keys, key max 128 chars, value max 1024 chars) allow_typed_signature: type: boolean default: true description: > Whether signers can type their name as a signature instead of drawing or uploading one. Defaults to true. examples: fill_only: summary: Fill without signing value: template_id: "d290f1ee-6c54-4b01-90e6-d701748f0851" fields: employee_name: "Jane Doe" start_date: "2026-04-01" department: "Engineering" single_signer: summary: Fill and send for signing value: template_id: "d290f1ee-6c54-4b01-90e6-d701748f0851" fields: employee_name: "Jane Doe" start_date: "2026-04-01" signers: Employee: email: "jane.doe@example.com" multi_signer: summary: Multiple signers value: template_id: "d290f1ee-6c54-4b01-90e6-d701748f0851" fields: employee_name: "Jane Doe" start_date: "2026-04-01" signers: Employee: email: "jane.doe@example.com" Manager: email: "bob.smith@example.com" pre_signed: summary: Fill with pre-provided signatures value: template_id: "d290f1ee-6c54-4b01-90e6-d701748f0851" fields: employee_name: "Jane Doe" start_date: "2026-04-01" signers: Employee: email: "jane.doe@example.com" override_signature_url: "https://example.com/signatures/jane.png" responses: '201': description: Request created content: application/json: schema: $ref: '#/components/schemas/RequestResponse' examples: fill_only: summary: Fill without signing value: request: id: "a1b2c3d4-5678-9abc-def0-1234567890ab" template_id: "d290f1ee-6c54-4b01-90e6-d701748f0851" title: "Invoice #1042" status: "success" has_signing: false created_at: "2026-03-22T10:30:00Z" updated_at: "2026-03-28T17:35:16-05:00" with_signing: summary: Fill and send for signing value: request: id: "a1b2c3d4-5678-9abc-def0-1234567890ab" template_id: "d290f1ee-6c54-4b01-90e6-d701748f0851" title: "Employment Agreement - Jane Doe" status: "pending" has_signing: true created_at: "2026-03-22T10:30:00Z" updated_at: "2026-03-28T17:35:16-05:00" signers: - id: "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d" signer_label: "Employee" recipient_email: "jane.doe@example.com" status: "pending" expires_at: "2026-04-05T10:30:00Z" '400': description: Invalid request or unfillable PDF content: application/json: schema: $ref: '#/components/schemas/Error' examples: bad_request: summary: Invalid input value: error: error_name: "bad_request" error_msg: "template_id is required" unfillable: summary: PDF form fields could not be filled value: error: error_name: "bad_request" error_msg: "This PDF's form fields could not be filled. The template may use an unsupported form structure." title_too_long: summary: Title exceeds length limit value: error: error_name: "bad_request" error_msg: "title exceeds 255 characters" message_too_long: summary: Message exceeds length limit value: error: error_name: "bad_request" error_msg: "message exceeds 1024 characters" invalid_email: summary: Invalid signer email value: error: error_name: "bad_request" error_msg: "invalid email for signer \"Employee\"" invalid_override_url: summary: Invalid override signature URL value: error: error_name: "bad_request" error_msg: "invalid override_signature_url for signer \"Employee\"" '409': $ref: '#/components/responses/Conflict' '429': $ref: '#/components/responses/TooManyRequests' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalError' get: tags: [Requests] summary: List requests operationId: listRequests description: Returns all requests in your account, newest first. parameters: - $ref: '#/components/parameters/page' - $ref: '#/components/parameters/page_size' - name: status in: query required: false schema: $ref: '#/components/schemas/RequestStatus' description: Filter by request status - name: from in: query required: false schema: type: string format: date-time description: Only return requests created on or after this timestamp (RFC 3339) - name: to in: query required: false schema: type: string format: date-time description: Only return requests created on or before this timestamp (RFC 3339) - name: metadata_key in: query required: false schema: type: string description: Filter by metadata key. Must be used together with metadata_value. - name: metadata_value in: query required: false schema: type: string description: Filter by metadata value (exact match). Must be used together with metadata_key. responses: '200': description: Paginated list of requests content: application/json: schema: $ref: '#/components/schemas/RequestListResponse' example: requests: - id: "a1b2c3d4-5678-9abc-def0-1234567890ab" template_id: "d290f1ee-6c54-4b01-90e6-d701748f0851" title: "Employment Agreement - Jane Doe" status: "success" has_signing: true created_at: "2026-03-22T10:30:00Z" updated_at: "2026-03-28T17:35:16-05:00" - id: "b2c3d4e5-6789-0abc-def1-234567890abc" template_id: "e7f3a1b2-4c56-4d89-a012-3456789abcde" title: "Invoice #1042" status: "success" has_signing: false created_at: "2026-03-21T15:00:00Z" updated_at: "2026-03-28T17:35:16-05:00" list_info: page: 1 num_pages: 1 num_results: 2 page_size: 20 '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/InternalError' /requests/{id}: get: tags: [Requests] summary: Get request detail operationId: getRequest description: Returns the request with its field inputs and signer statuses. parameters: - name: id in: path required: true schema: type: string format: uuid description: Request ID responses: '200': description: Request detail including inputs and signers content: application/json: schema: $ref: '#/components/schemas/RequestResponse' example: request: id: "a1b2c3d4-5678-9abc-def0-1234567890ab" template_id: "d290f1ee-6c54-4b01-90e6-d701748f0851" title: "Employment Agreement - Jane Doe" status: "success" has_signing: true created_at: "2026-03-22T10:30:00Z" updated_at: "2026-03-28T17:35:16-05:00" inputs: employee_name: "Jane Doe" start_date: "2026-04-01" signers: - id: "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d" signer_label: "Employee" recipient_email: "jane.doe@example.com" status: "signed" expires_at: "2026-04-05T10:30:00Z" signed_at: "2026-03-23T14:22:00Z" '404': $ref: '#/components/responses/NotFound' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/InternalError' /requests/{id}/download: get: tags: [Requests] summary: Download request PDF operationId: downloadRequest description: | Returns the filled PDF. If all signers have completed, signatures are applied to the PDF. If signing is still pending, you get the filled PDF without signatures. parameters: - name: id in: path required: true schema: type: string format: uuid description: Request ID responses: '200': description: PDF file headers: Content-Disposition: schema: type: string description: 'attachment; filename="generated.pdf" or "signed.pdf"' content: application/pdf: schema: type: string format: binary '404': $ref: '#/components/responses/NotFound' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/InternalError' /requests/{id}/cancel: post: tags: [Requests] summary: Cancel request operationId: cancelRequest description: | Cancels all pending signers on the request and sets the request status to `cancelled`. Signers who have already signed are not affected. Only requests with status `pending` can be cancelled. parameters: - name: id in: path required: true schema: type: string format: uuid description: Request ID responses: '200': description: Request cancelled content: application/json: schema: $ref: '#/components/schemas/RequestResponse' example: request: id: "a1b2c3d4-5678-9abc-def0-1234567890ab" template_id: "d290f1ee-6c54-4b01-90e6-d701748f0851" title: "Employment Agreement - Jane Doe" status: "cancelled" has_signing: true created_at: "2026-03-22T10:30:00Z" updated_at: "2026-03-28T17:35:16-05:00" '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/InternalError' /requests/{id}/signers/{sid}/resend: post: tags: [Signers] summary: Resend signing link operationId: resendSigningLink description: | Generates a new signing link and resends the signing email. The previous link is invalidated. Only works for signers with status `pending`. parameters: - name: id in: path required: true schema: type: string format: uuid description: Request ID - name: sid in: path required: true schema: type: string format: uuid description: Signer ID responses: '200': description: New signing link sent content: application/json: schema: type: object properties: signer: $ref: '#/components/schemas/Signer' example: signer: id: "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d" signer_label: "Employee" recipient_email: "jane.doe@example.com" status: "pending" expires_at: "2026-04-12T10:30:00Z" '409': $ref: '#/components/responses/Conflict' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/InternalError' /requests/{id}/signers/{sid}/signature: get: tags: [Signers] summary: Download signer signature operationId: downloadSignerSignature description: | Returns the stored signature image (PNG) for a signer who has completed signing, either via the signing page or a pre-provided override_signature_url. parameters: - name: id in: path required: true schema: type: string format: uuid description: Request ID - name: sid in: path required: true schema: type: string format: uuid description: Signer ID responses: '200': description: Signature image content: image/png: schema: type: string format: binary '404': $ref: '#/components/responses/NotFound' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/InternalError' /webhooks: post: tags: [Webhooks] summary: Create webhook operationId: createWebhook description: Registers a URL to receive event notifications. requestBody: required: true content: application/json: schema: type: object required: [url, events] properties: url: type: string format: uri description: URL to receive webhook POST requests events: type: array items: $ref: '#/components/schemas/WebhookEvent' description: Event types to subscribe to example: url: "https://example.com/webhook" events: ["request.success", "signer.signed"] responses: '201': description: Webhook created content: application/json: schema: type: object properties: webhook: allOf: - $ref: '#/components/schemas/Webhook' - type: object properties: secret: type: string description: > HMAC secret for verifying webhook deliveries. Only returned once, when the webhook is created. Store it securely. '400': $ref: '#/components/responses/BadRequest' '429': $ref: '#/components/responses/TooManyRequests' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalError' get: tags: [Webhooks] summary: List webhooks operationId: listWebhooks description: Returns all webhooks registered in your account, newest first. parameters: - $ref: '#/components/parameters/page' - $ref: '#/components/parameters/page_size' responses: '200': description: Paginated list of webhooks for the current group content: application/json: schema: $ref: '#/components/schemas/WebhookListResponse' example: webhooks: - id: "b1c2d3e4-5678-9abc-def0-1234567890ab" url: "https://example.com/webhook" events: ["request.success", "signer.signed"] active: true created_at: "2026-03-23T10:00:00Z" updated_at: "2026-03-28T17:35:16-05:00" list_info: page: 1 num_pages: 1 num_results: 1 page_size: 20 '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/InternalError' /webhooks/{id}: get: tags: [Webhooks] summary: Get webhook operationId: getWebhook description: Returns a single webhook. parameters: - name: id in: path required: true schema: type: string format: uuid responses: '200': description: Webhook details content: application/json: schema: $ref: '#/components/schemas/WebhookResponse' example: webhook: id: "b1c2d3e4-5678-9abc-def0-1234567890ab" url: "https://example.com/webhook" events: ["request.success", "signer.signed"] active: true created_at: "2026-03-23T10:00:00Z" updated_at: "2026-03-28T17:35:16-05:00" '404': $ref: '#/components/responses/NotFound' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/InternalError' patch: tags: [Webhooks] summary: Update webhook operationId: updateWebhook description: Change the webhook URL, subscribed events, or active status. Omitted fields are left unchanged. parameters: - name: id in: path required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: type: object properties: url: type: string format: uri events: type: array items: $ref: '#/components/schemas/WebhookEvent' active: type: boolean responses: '200': description: Updated webhook content: application/json: schema: $ref: '#/components/schemas/WebhookResponse' example: webhook: id: "b1c2d3e4-5678-9abc-def0-1234567890ab" url: "https://example.com/webhook-v2" events: ["request.success"] active: true created_at: "2026-03-23T10:00:00Z" updated_at: "2026-03-28T17:35:16-05:00" '404': $ref: '#/components/responses/NotFound' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/InternalError' delete: tags: [Webhooks] summary: Delete webhook operationId: deleteWebhook description: Permanently removes the webhook. No further deliveries will be sent. parameters: - name: id in: path required: true schema: type: string format: uuid responses: '204': description: Webhook deleted '404': $ref: '#/components/responses/NotFound' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/InternalError' /webhooks/{id}/test: post: tags: [Webhooks] summary: Test webhook operationId: testWebhook description: | Sends a test event payload to the webhook URL. Use this to verify your endpoint is correctly receiving and processing webhook deliveries. parameters: - name: id in: path required: true schema: type: string format: uuid responses: '204': description: Test delivery succeeded '404': $ref: '#/components/responses/NotFound' '502': $ref: '#/components/responses/BadGateway' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/InternalError'