# Add a document to a signing request Source: https://docs.firma.dev/api-reference/v01.40.04/signing-requests/add-a-document-to-a-signing-request api-reference/v01.40.04/openapi-v01.40.04.json post /signing-requests/{id}/documents Adds a document (a temporary upload from `POST /documents`) to the signing request's document set, at `position` or last. Documents at that position and after it move up by one. At most 20 documents and 100 MB per signing request. A sent, finished, cancelled or declined signing request answers `409 DOCUMENT_SET_LOCKED`. **Available to every company.** This operation is part of the multi-document contract (API 01.39.00). For a signing request or template the API key cannot see, it answers `404` with code `NOT_FOUND` ("Endpoint not found"), exactly as an unknown endpoint does. While multi-document editing is suspended, this operation returns `409 MULTI_DOCUMENT_READ_ONLY` for parents on the multi-document read-only path and `404 NOT_FOUND` for parents on the single-document path. # Cancel a signing request Source: https://docs.firma.dev/api-reference/v01.40.04/signing-requests/cancel-a-signing-request api-reference/v01.40.04/openapi-v01.40.04.json post /signing-requests/{id}/cancel Cancels a signing request that hasn't been completed yet. Can only cancel requests that have been sent and are not already finished or cancelled. # Comprehensive Update Signing Request Source: https://docs.firma.dev/api-reference/v01.40.04/signing-requests/comprehensive-update-signing-request api-reference/v01.40.04/openapi-v01.40.04.json put /signing-requests/{id} Perform comprehensive updates to a signing request including properties, recipients, fields, and reminders. Cannot update after signing request has been sent, completed, or cancelled. All sections are optional but at least one must be provided. **Temporary ID Pattern for New Recipients**: When adding new recipients in a comprehensive update, use the '_temp_id' field (format: 'temp_X') instead of 'id' to establish relationships with fields and reminders. This allows you to create new recipients and reference them in fields/reminders in a single request. Use the 'id' field to update existing recipients. Validation rules: (1) Temporary IDs must start with 'temp_'; (2) Each temporary ID must be unique within the request; (3) Fields and reminders can reference temporary IDs in their recipient_id property; (4) The API will automatically resolve temporary IDs to real UUIDs after recipient creation. # Create and Send Signing Request (Atomic) Source: https://docs.firma.dev/api-reference/v01.40.04/signing-requests/create-and-send-signing-request-atomic api-reference/v01.40.04/openapi-v01.40.04.json post /signing-requests/create-and-send Create and immediately send a signing request in a single atomic operation. This endpoint combines the functionality of POST /signing-requests and POST /signing-requests/{id}/send. **Key Benefits:** - Single API call instead of two separate requests - Validates all send requirements BEFORE creating the signing request - Atomic credit deduction - only charges if everything succeeds - Returns status: 'sent' immediately with first signer details - More efficient (saves 1 API call + round-trip time) **Validation:** - All standard creation validations (document/template, recipients, fields) - Additional send validations: - All signers must have first_name and valid email - Required read-only fields must have final_value populated - Prefilled data fields (variable_name) must have corresponding user data - Company must have sufficient credits (≥1) **Atomicity:** - If any validation fails, nothing is created - If the send step fails before any part of the send has started, the signing request is removed and an error is returned: the send step's `400`, `402` or `422`, some `409` codes, otherwise `500`. No draft is left behind - If the send had already started, the request is kept and the response is a `409` carrying its `signing_request_id`: code `SEND_EFFECTS_EXIST`, or `ALREADY_SENT` if the send completed. Recover that request instead of retrying the create. A `409` with code `SEND_IN_PROGRESS` also leaves the request in place **Temporary ID Pattern:** For document-based creation, use temporary IDs (format: 'temp_X') to reference recipients before creation. The API validates all references and automatically maps temporary IDs to real UUIDs. **Rate Limit:** 120 requests/minute (same as write operations) # Create Signing Request Source: https://docs.firma.dev/api-reference/v01.40.04/signing-requests/create-signing-request api-reference/v01.40.04/openapi-v01.40.04.json post /signing-requests Create a new signing request either from a PDF document (document-based) or from an existing template (template-based). For document-based creation, allow_editing_before_sending is automatically set to true. For template-based creation, properties are inherited from the template and can be overridden. **Temporary ID Pattern**: For document-based creation, you can reference recipients before they're created using temporary IDs (format: 'temp_X' where X is any identifier, e.g., 'temp_1', 'temp_alice'). Use these temporary IDs in recipient.id, field.recipient_id, and reminder.recipient_id. The API validates all references and automatically maps temporary IDs to real UUIDs after recipients are created. The response contains only real UUIDs. **Temporary ID Validation**: Temporary IDs must start with 'temp_', be unique across all recipients in the request, and all field/reminder references must point to recipients defined in the same request. Invalid format, duplicate IDs, or missing recipient references return a 400 error with detailed validation messages. # Delete a signing request Source: https://docs.firma.dev/api-reference/v01.40.04/signing-requests/delete-a-signing-request api-reference/v01.40.04/openapi-v01.40.04.json delete /signing-requests/{id} Deletes an unsent (draft) signing request. Only signing requests that have not been sent can be deleted. For sent signing requests, use the cancel endpoint instead. # Download one document of a signing request Source: https://docs.firma.dev/api-reference/v01.40.04/signing-requests/download-one-document-of-a-signing-request api-reference/v01.40.04/openapi-v01.40.04.json get /signing-requests/{id}/documents/{document_id}/download `type=original` (the default): a signed URL to the document as uploaded (`404 DOCUMENT_ORIGINAL_NOT_AVAILABLE` when it is no longer stored). `type=document`: a signed URL to the individually sealed document, available once the certificate is bound (`404 DOCUMENT_SIGNED_NOT_AVAILABLE` before). The URL is valid for 1 hour. **Available to every company.** This operation is part of the multi-document contract (API 01.39.00). For a signing request or template the API key cannot see, it answers `404` with code `NOT_FOUND` ("Endpoint not found"), exactly as an unknown endpoint does. While multi-document editing is suspended, this operation remains available for parents on the multi-document read-only path; parents on the single-document path answer `404 NOT_FOUND`. # Download Signing Request Document Source: https://docs.firma.dev/api-reference/v01.40.04/signing-requests/download-signing-request api-reference/v01.40.04/openapi-v01.40.04.json get /signing-requests/{id}/download Retrieve a download URL for a signing request's signed document. For a completed signing request, returns the final signed PDF with the completion certificate. For a sent signing request that is not completed (in progress, expired, cancelled or declined), returns a URL to a partial-progress snapshot of the signers who have completed so far. **Partial downloads:** Available for every sent signing request that is not completed; there is no setting to enable. If the snapshot is still being generated, a `503` is returned: retry after the `Retry-After` interval. **URL expiry:** The `download_url` is a pre-signed URL that expires. The expiry timestamp is provided in `expires_at`. # Get Signing Request Source: https://docs.firma.dev/api-reference/v01.40.04/signing-requests/get-signing-request api-reference/v01.40.04/openapi-v01.40.04.json get /signing-requests/{id} Retrieve a specific signing request by ID # Get signing request fields Source: https://docs.firma.dev/api-reference/v01.40.04/signing-requests/get-signing-request-fields api-reference/v01.40.04/openapi-v01.40.04.json get /signing-requests/{id}/fields Retrieve all fields for a specific signing request with their values # Get signing request users Source: https://docs.firma.dev/api-reference/v01.40.04/signing-requests/get-signing-request-users api-reference/v01.40.04/openapi-v01.40.04.json get /signing-requests/{id}/users Retrieve all recipients/users for a specific signing request # List Signing Requests Source: https://docs.firma.dev/api-reference/v01.40.04/signing-requests/list-signing-requests api-reference/v01.40.04/openapi-v01.40.04.json get /signing-requests Retrieve a paginated list of signing requests # Partially Update Signing Request Source: https://docs.firma.dev/api-reference/v01.40.04/signing-requests/partially-update-signing-request api-reference/v01.40.04/openapi-v01.40.04.json patch /signing-requests/{id} Update signing request properties, a single recipient, OR a single field. Cannot update multiple entity types in one request. Cannot update after signing request has been sent, completed, or cancelled, except for a seal swap (see `seal_participant`). A `seal_participant` that names a seal answers 404 `NOT_FOUND` when the seal does not exist or belongs to another company, and 400 `SEAL_UNAVAILABLE` when the seal is revoked, deleted or not the current version of its lineage, and also when it is limited to a workspace other than the signing request's. These answers are for adding a seal participant and for changing its seal. A seal swap (`swap_to_seal_id`) answers differently for the new seal: 404 `NOT_FOUND` when it does not exist, belongs to another company or is limited to a workspace other than the signing request's; 409 `SEAL_UNAVAILABLE` when it is revoked, deleted or not the current version of its lineage; 400 `SEAL_FIELD_REQUIRED` when the seal participant has no stamp field on the signing request. Adding a seal participant or changing its seal answers 503 `SEALS_DISABLED` when organization seals are not enabled. Adding a seal participant requires `order` (see the order contract). A field that belongs to a seal participant accepts only `position` and `page_number` changes; every other key of `field`, `recipient_id` included, is ignored. # Remove a document from a signing request Source: https://docs.firma.dev/api-reference/v01.40.04/signing-requests/remove-a-document-from-a-signing-request api-reference/v01.40.04/openapi-v01.40.04.json delete /signing-requests/{id}/documents/{document_id} Removes a document from the signing request. The last remaining document cannot be removed (`400 MIN_DOCUMENTS_REQUIRED`). A sent, finished, cancelled or declined signing request answers `409 DOCUMENT_SET_LOCKED`. **Available to every company.** This operation is part of the multi-document contract (API 01.39.00). For a signing request or template the API key cannot see, it answers `404` with code `NOT_FOUND` ("Endpoint not found"), exactly as an unknown endpoint does. While multi-document editing is suspended, this operation returns `409 MULTI_DOCUMENT_READ_ONLY` for parents on the multi-document read-only path and `404 NOT_FOUND` for parents on the single-document path. # Reorder a signing request's documents Source: https://docs.firma.dev/api-reference/v01.40.04/signing-requests/reorder-a-signing-requests-documents api-reference/v01.40.04/openapi-v01.40.04.json put /signing-requests/{id}/documents/order Sets the order of the signing request's documents. `document_ids` must name every document of the signing request exactly once: a missing or repeated id answers `400 INVALID_DOCUMENT_ORDER`, an id that is not a document of this signing request `404 DOCUMENT_NOT_FOUND`. A sent, finished, cancelled or declined signing request answers `409 DOCUMENT_SET_LOCKED`. **Available to every company.** This operation is part of the multi-document contract (API 01.39.00). For a signing request or template the API key cannot see, it answers `404` with code `NOT_FOUND` ("Endpoint not found"), exactly as an unknown endpoint does. While multi-document editing is suspended, this operation returns `409 MULTI_DOCUMENT_READ_ONLY` for parents on the multi-document read-only path and `404 NOT_FOUND` for parents on the single-document path. # Replace or rename a signing request document Source: https://docs.firma.dev/api-reference/v01.40.04/signing-requests/replace-or-rename-a-signing-request-document api-reference/v01.40.04/openapi-v01.40.04.json patch /signing-requests/{id}/documents/{document_id} Send exactly one of `document_id` (replace the document with a temporary upload from `POST /documents`: the same number of pages, every page the same size within 1 pt) or `label` (rename). Both, or neither, answers `400 VALIDATION_ERROR`. A document placed with anchor tags is replaced only when the anchors of the replacement resolve exactly as stored; otherwise `400 ANCHOR_MISMATCH`. A sent, finished, cancelled or declined signing request answers `409 DOCUMENT_SET_LOCKED`. **Available to every company.** This operation is part of the multi-document contract (API 01.39.00). For a signing request or template the API key cannot see, it answers `404` with code `NOT_FOUND` ("Endpoint not found"), exactly as an unknown endpoint does. While multi-document editing is suspended, this operation returns `409 MULTI_DOCUMENT_READ_ONLY` for parents on the multi-document read-only path and `404 NOT_FOUND` for parents on the single-document path. # Resend signing request to specific recipients Source: https://docs.firma.dev/api-reference/v01.40.04/signing-requests/resend-signing-request-to-specific-recipients api-reference/v01.40.04/openapi-v01.40.04.json post /signing-requests/{id}/resend Resends signing request notifications to one or more recipients who are currently eligible to sign. For requests with signing order enabled, can only resend to recipients at the current active order. Cannot resend to recipients who have already signed. # Retrieve Signer-Uploaded File Source: https://docs.firma.dev/api-reference/v01.40.04/signing-requests/retrieve-signer-file api-reference/v01.40.04/openapi-v01.40.04.json get /signing-requests/{id}/signers/{signer_id}/files/{field_id} Retrieve a file a signer uploaded to a file field. Returns a short-lived (300s) pre-signed download URL, not the bytes. `field_id` is required (from GET /signing-requests/{id}/fields, filtering type=file). Every retrieval is audit-logged; 60/min limit. Download promptly server-side before the URL expires. # Retrieve Signer Initials Image Source: https://docs.firma.dev/api-reference/v01.40.04/signing-requests/retrieve-signer-initials api-reference/v01.40.04/openapi-v01.40.04.json get /signing-requests/{id}/signers/{signer_id}/initials Retrieve a signer's adopted initials as a base64 PNG data URI. One initials mark per signer (no field_id). Same response shape, framing, audit, and rate limit as the signature endpoint. # Retrieve Signer Signature Image Source: https://docs.firma.dev/api-reference/v01.40.04/signing-requests/retrieve-signer-signature api-reference/v01.40.04/openapi-v01.40.04.json get /signing-requests/{id}/signers/{signer_id}/signature Retrieve a signer's adopted signature as a base64 PNG data URI, separate from the signed PDF. A signer adopts one signature applied to all their signature fields, so no field_id is needed. Drawn signatures return the captured image; typed signatures are rendered to PNG server-side. The `image` is a `data:image/png;base64,...` URI usable directly in an ``. By default a signer-ID frame is overlaid (`include_frame=false` to omit). Every successful retrieval is audit-logged. Rate limited to 60/min. Legal validity rests on the sealed PDF and completion certificate, not this image. # Retrieve Signer Stamp Image Source: https://docs.firma.dev/api-reference/v01.40.04/signing-requests/retrieve-signer-stamp api-reference/v01.40.04/openapi-v01.40.04.json get /signing-requests/{id}/signers/{signer_id}/stamps/{field_id} Retrieve a signer's stamp for a specific stamp field as a base64 PNG data URI. Stamps are per-field, so `field_id` is required (obtain it from GET /signing-requests/{id}/fields, filtering type=stamp). Same framing, audit, and 60/min limit. # Send signing request Source: https://docs.firma.dev/api-reference/v01.40.04/signing-requests/send-signing-request api-reference/v01.40.04/openapi-v01.40.04.json post /signing-requests/{id}/send Send a signing request to all recipients via email. This triggers email delivery and sets the sent_on timestamp. # Upload a document Source: https://docs.firma.dev/api-reference/v01.40.04/signing-requests/upload-document api-reference/v01.40.04/openapi-v01.40.04.json post /documents Request a presigned upload URL for a document. Upload the file directly to the returned URL, then pass the document_id when creating a signing request. Use this for documents larger than 5MB that exceed the inline base64 request size limit. # Add a document to a template Source: https://docs.firma.dev/api-reference/v01.40.04/templates/add-a-document-to-a-template api-reference/v01.40.04/openapi-v01.40.04.json post /templates/{id}/documents Adds a document (a temporary upload from `POST /documents`) to the template's document set, at `position` or last. Documents at that position and after it move up by one. At most 20 documents and 100 MB per template. **Available to every company.** This operation is part of the multi-document contract (API 01.39.00). For a signing request or template the API key cannot see, it answers `404` with code `NOT_FOUND` ("Endpoint not found"), exactly as an unknown endpoint does. While multi-document editing is suspended, this operation returns `409 MULTI_DOCUMENT_READ_ONLY` for parents on the multi-document read-only path and `404 NOT_FOUND` for parents on the single-document path. # Comprehensive Template Update Source: https://docs.firma.dev/api-reference/v01.40.04/templates/comprehensive-template-update api-reference/v01.40.04/openapi-v01.40.04.json put /templates/{id} Comprehensive update of template including properties, users, fields, and reminders. Supports user deletion with field reassignment or deletion. All sections are optional but at least one must be provided. **Not atomic.** The parts of this call are not applied as one update. Each entry of `users` or `deleted_users` that adds, moves or removes a signer or an approver is saved on its own, in the order of the request. If one of these saves is refused, the call answers at once with that error: what was saved before it stays applied and the rest of the request is not processed. Other problems (for example a user that is not found, or an invalid field or reminder) are collected and answered as one 400 `VALIDATION_ERROR` at the end of the call. Once a problem was collected, some of the remaining changes are skipped (the `users`, `fields` and `reminders` entries that follow it) and others can still be applied before the 400, and what was saved before the problem stays applied. The 400 does not always say which entries failed. Read the template again after any non-2xx response. If the read of the template's seal participants for the response fails after the writes, the call answers 500 although the writes were applied. **Seal participants.** The request property `seal_participants` sets the seal participants of the template. When the key is absent, the seal participants of the template are left unchanged. When it is an array, the array replaces the whole set: every seal participant the template had is removed together with the fields it owned, and one seal participant is created for each entry, so `[]` removes them all. Each entry is an object with `seal_id` (required, the ID of an organization seal), `order` (required, a positive integer that no other entry repeats and that is not greater than the number of recipients of the template plus the number of entries) and an optional `temp_id`. The recipients keep their relative order and each seal is placed at its `order`. `null`, any other value that is not an array and an entry without `seal_id` answer 400 `VALIDATION_ERROR`; an `order` that breaks the rules above answers 400 `INVALID_SIGNING_ORDER`; a `seal_id` that is not a UUID answers 404 `NOT_FOUND`; a seal of your company that is revoked or deleted answers 400 `SEAL_UNAVAILABLE`. Each of these answers is immediate and leaves the seal participants unchanged. `seal_participants` is processed after the other parts of the call, also when they collected problems, so it can be applied in a call that then answers 400 `VALIDATION_ERROR`. # Copy template to another workspace Source: https://docs.firma.dev/api-reference/v01.40.04/templates/copy-template-to-workspace api-reference/v01.40.04/openapi-v01.40.04.json post /templates/{id}/copy Deep-copies a template (including fields, recipients, CC, reminders, custom field definitions, and the PDF document) to a target workspace within the same company. Requires a company-level (protected) API key. Multi-document (available to every company): the copy of a template with more than one document is a template with ONE document, the merged document of the source; its content and the absolute page numbers of its fields are kept, the per-document labels and positions are not carried. While multi-document editing is suspended, copying a template on the multi-document read-only path answers `409 MULTI_DOCUMENT_READ_ONLY`; other templates use the single-document path. # Create Template Source: https://docs.firma.dev/api-reference/v01.40.04/templates/create-template api-reference/v01.40.04/openapi-v01.40.04.json post /templates Create a new template with a base64-encoded PDF document. The API automatically extracts the page count from the document. `seal_participants`, `recipients` and `fields` are not accepted keys on this operation: they are ignored and nothing is created from them. When the request body contains any of them (the key being present is enough, whatever its value, including `null` or `[]`), the 201 response carries `warnings`, one string per ignored key. Add participants and fields with `PATCH /templates/{id}` or `PUT /templates/{id}`. # Delete a template Source: https://docs.firma.dev/api-reference/v01.40.04/templates/delete-a-template api-reference/v01.40.04/openapi-v01.40.04.json delete /templates/{id} Soft delete a template by ID. This marks the template as deleted but retains the data. # Duplicate template into signing request Source: https://docs.firma.dev/api-reference/v01.40.04/templates/duplicate-template-into-signing-request api-reference/v01.40.04/openapi-v01.40.04.json post /templates/{id}/duplicate Creates a new signing request by duplicating an existing template, including all fields, users, reminders, and settings. **Seals.** The new signing request carries the template's seal participants and the template fields that belong to them. This operation takes no `seal_participants` key. While multi-document editing is suspended, templates on the multi-document read-only path answer 409 `MULTI_DOCUMENT_READ_ONLY`. For templates on the single-document path during that suspension, a template seal follows the current version of its lineage: if that current version is revoked or deleted, or the lineage has no current version, the call answers 409 `SEAL_UNAVAILABLE` and creates nothing; a seal that the API key's company cannot see, or that is limited to a workspace other than the target workspace, is not copied and neither are its fields, and the 201 lists it in `seal_participants` as a dropped entry; if a seal would be kept while organization seals are not enabled, the call answers 503 `SEALS_DISABLED`; the 201 always carries `seal_participants` (an empty array when the template has no seal participants). For every company during normal operation, the copy attaches the template's stored seal version as it is, the 201 does not carry `seal_participants`, and a seal that was replaced, revoked or deleted since the template was saved fails when the request is sent. # Get Template Source: https://docs.firma.dev/api-reference/v01.40.04/templates/get-template api-reference/v01.40.04/openapi-v01.40.04.json get /templates/{id} Retrieve a specific template by ID. The response includes `seal_participants`: the template's seal participants in order, limited to those whose seal belongs to the company of the API key. A seal participant whose seal is missing or belongs to another company is omitted, so an empty or shorter list does not prove that the template has no other seal participant stored. # Get template fields Source: https://docs.firma.dev/api-reference/v01.40.04/templates/get-template-fields api-reference/v01.40.04/openapi-v01.40.04.json get /templates/{id}/fields Retrieve all fields configured for a specific template # Get template reminders Source: https://docs.firma.dev/api-reference/v01.40.04/templates/get-template-reminders api-reference/v01.40.04/openapi-v01.40.04.json get /templates/{id}/reminders Retrieve all reminders configured for a specific template # Get template users Source: https://docs.firma.dev/api-reference/v01.40.04/templates/get-template-users api-reference/v01.40.04/openapi-v01.40.04.json get /templates/{id}/users Retrieve all recipients/users associated with a specific template # List Templates Source: https://docs.firma.dev/api-reference/v01.40.04/templates/list-templates api-reference/v01.40.04/openapi-v01.40.04.json get /templates Retrieve a paginated list of templates # Partially Update Template Source: https://docs.firma.dev/api-reference/v01.40.04/templates/partially-update-template api-reference/v01.40.04/openapi-v01.40.04.json patch /templates/{id} Update template properties, a single user, OR a single field. Cannot mix multiple entity types in one request. Use PUT for comprehensive updates including users, fields, and reminders. A `seal_participant` that names a seal answers 404 `NOT_FOUND` when the seal does not exist or belongs to another company, and 400 `SEAL_UNAVAILABLE` when the seal is revoked, deleted or not the current version of its lineage. Adding a seal participant or changing its seal answers 503 `SEALS_DISABLED` when organization seals are not enabled. Adding a seal participant requires `order` (see the order contract). # Remove a document from a template Source: https://docs.firma.dev/api-reference/v01.40.04/templates/remove-a-document-from-a-template api-reference/v01.40.04/openapi-v01.40.04.json delete /templates/{id}/documents/{document_id} Removes a document from the template. The last remaining document cannot be removed (`400 MIN_DOCUMENTS_REQUIRED`). **Available to every company.** This operation is part of the multi-document contract (API 01.39.00). For a signing request or template the API key cannot see, it answers `404` with code `NOT_FOUND` ("Endpoint not found"), exactly as an unknown endpoint does. While multi-document editing is suspended, this operation returns `409 MULTI_DOCUMENT_READ_ONLY` for parents on the multi-document read-only path and `404 NOT_FOUND` for parents on the single-document path. # Reorder a template's documents Source: https://docs.firma.dev/api-reference/v01.40.04/templates/reorder-a-templates-documents api-reference/v01.40.04/openapi-v01.40.04.json put /templates/{id}/documents/order Sets the order of the template's documents. `document_ids` must name every document of the template exactly once: a missing or repeated id answers `400 INVALID_DOCUMENT_ORDER`, an id that is not a document of this template `404 DOCUMENT_NOT_FOUND`. **Available to every company.** This operation is part of the multi-document contract (API 01.39.00). For a signing request or template the API key cannot see, it answers `404` with code `NOT_FOUND` ("Endpoint not found"), exactly as an unknown endpoint does. While multi-document editing is suspended, this operation returns `409 MULTI_DOCUMENT_READ_ONLY` for parents on the multi-document read-only path and `404 NOT_FOUND` for parents on the single-document path. # Replace or rename a template document Source: https://docs.firma.dev/api-reference/v01.40.04/templates/replace-or-rename-a-template-document api-reference/v01.40.04/openapi-v01.40.04.json patch /templates/{id}/documents/{document_id} Send exactly one of `document_id` (replace the document with a temporary upload from `POST /documents`: the same number of pages, every page the same size within 1 pt) or `label` (rename). Both, or neither, answers `400 VALIDATION_ERROR`. **Available to every company.** This operation is part of the multi-document contract (API 01.39.00). For a signing request or template the API key cannot see, it answers `404` with code `NOT_FOUND` ("Endpoint not found"), exactly as an unknown endpoint does. While multi-document editing is suspended, this operation returns `409 MULTI_DOCUMENT_READ_ONLY` for parents on the multi-document read-only path and `404 NOT_FOUND` for parents on the single-document path. # Replace template document Source: https://docs.firma.dev/api-reference/v01.40.04/templates/replace-template-document api-reference/v01.40.04/openapi-v01.40.04.json post /templates/{id}/replace-document Replaces the PDF document of an existing template while preserving all field placements. The replacement document must have the same page count and matching page dimensions (within 1pt tolerance) as the original. # Create a new workspace Source: https://docs.firma.dev/api-reference/v01.40.04/workspaces/create-a-new-workspace api-reference/v01.40.04/openapi-v01.40.04.json post /workspaces Create a new workspace for the authenticated company. All workspaces are created as non-protected by default. The protected status can only be set during company account creation. # Expire pending API keys Source: https://docs.firma.dev/api-reference/v01.40.04/workspaces/expire-pending-api-keys api-reference/v01.40.04/openapi-v01.40.04.json post /workspaces/{id}/api-key/expire Immediately expires all pending API keys (those with expires_at set) for a workspace. Useful after verifying that the new key works correctly. Cannot be used on protected workspaces. Rate limited to 1 request per minute. # Get a workspace Source: https://docs.firma.dev/api-reference/v01.40.04/workspaces/get-a-workspace api-reference/v01.40.04/openapi-v01.40.04.json get /workspaces/{id} Retrieve a specific workspace by ID including its API key # List workspaces Source: https://docs.firma.dev/api-reference/v01.40.04/workspaces/list-workspaces api-reference/v01.40.04/openapi-v01.40.04.json get /workspaces Retrieve a paginated list of workspaces for the authenticated company # Partially update a workspace Source: https://docs.firma.dev/api-reference/v01.40.04/workspaces/partially-update-a-workspace api-reference/v01.40.04/openapi-v01.40.04.json patch /workspaces/{id} Update specific workspace fields. Only the name can be updated via API. The protected field is read-only and can only be set during company account creation. # Regenerate workspace API key Source: https://docs.firma.dev/api-reference/v01.40.04/workspaces/regenerate-workspace-api-key api-reference/v01.40.04/openapi-v01.40.04.json post /workspaces/{id}/api-key/regenerate Regenerates the API key for a workspace. Sets 24-hour expiration on existing active keys and creates a new key. Cannot be used on protected workspaces. Rate limited to 1 request per minute. # Remove Workspace Logo Source: https://docs.firma.dev/api-reference/v01.40.04/workspaces/remove-workspace-logo api-reference/v01.40.04/openapi-v01.40.04.json delete /workspaces/{id}/logo Remove the workspace logo. Certificates will fall back to the company logo, or the Firma default. # Update a workspace Source: https://docs.firma.dev/api-reference/v01.40.04/workspaces/update-a-workspace api-reference/v01.40.04/openapi-v01.40.04.json put /workspaces/{id} Update an existing workspace's information. This is a full replacement operation requiring the name field. # Upload Workspace Logo Source: https://docs.firma.dev/api-reference/v01.40.04/workspaces/upload-workspace-logo api-reference/v01.40.04/openapi-v01.40.04.json post /workspaces/{id}/logo Upload a logo for a specific workspace. Overrides the company logo on certificates and emails for this workspace. Accepts PNG or JPEG, max 2MB. # a0.dev Source: https://docs.firma.dev/guides/a0dev-integration Add legally binding e-signatures to any a0.dev mobile app using a backend endpoint and an in-app WebView. Firma lets you add legally binding e-signatures to any React Native / Expo app generated with a0.dev. Mobile apps need signing constantly: service agreements, parental consent, gig-worker contracts, rental and delivery confirmations. Because a0.dev generates Expo apps, you can present the Firma signing experience in a native WebView while keeping your API key safe on a backend you control. This guide covers two integration paths: 1. **Backend endpoint + WebView (recommended)** - Send signing requests from a server-side endpoint and present the signing experience in a native WebView. Best for production mobile apps. 2. **Deep link (lightweight)** - Open the Firma signing URL in the system browser. Best for very simple flows. ## Prerequisites * A [Firma account](https://app.firma.dev) with an API key * An a0.dev project (uses Expo + React Native) * `react-native-webview` installed in the project (add it via a0.dev's AI chat or the import panel) * At least one Firma template with signing fields configured * A backend service for your API calls (e.g., Express, Fastify, or a Supabase Edge Function) > [!NOTE] > > Firma uses the raw API key as the `Authorization` header value - do not prefix it with `Bearer`. This differs from many other APIs. ## Getting started **Backend endpoint + WebView** This is the recommended approach. Your app calls a server-side endpoint to create the signing request, then opens the signing URL in a WebView that lives inside your app. Because a0.dev apps are frontend-only, you need an external backend to keep your API key secure. ### Step 1: Set up a backend endpoint Create a backend endpoint that calls the Firma API. This can be an Express server, a Supabase Edge Function, or any server-side environment where you can store secrets. Here is an example using Express: ```typescript theme={null} import express from "express"; const FIRMA_API = "https://api.firma.dev/functions/v1/signing-request-api"; const app = express(); app.use(express.json()); app.post("/api/signing-requests", async (req, res) => { const { name, template_id, signer_email, signer_first_name, signer_last_name } = req.body; const response = await fetch( `${FIRMA_API}/signing-requests/create-and-send`, { method: "POST", headers: { Authorization: process.env.FIRMA_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ name, template_id, recipients: [ { first_name: signer_first_name, last_name: signer_last_name, email: signer_email, designation: "Signer", order: 1, }, ], }), } ); const data = await response.json(); if (!response.ok) return res.status(response.status).json({ error: data }); res.json({ signing_request_id: data.id, signing_request_user_id: data.first_signer.id, signing_link: data.first_signer.signing_link, }); }); app.listen(3000); ``` The `create-and-send` endpoint creates the signing request and sends it to recipients atomically. If you need to review or modify the request before sending, use `POST /signing-requests` to create a draft, then `POST /signing-requests/{id}/send` separately. > [!WARNING] > > Never expose your API key in client-side React Native code. Always call the Firma API from a backend where secrets are kept secure. ### Step 2: Store your backend URL in the app In your a0.dev project, create a `.env` file and add your backend URL: ```text theme={null} BACKEND_URL=https://your-backend.example.com ``` Make sure `.env` is in `.gitignore`. ### Step 3: Call the endpoint and open the WebView From any React Native screen, call the backend endpoint and store the returned `signing_request_user_id`. Then render the Firma signing experience inside a WebView: ```typescript theme={null} import { useState } from "react"; import { View, Pressable, Text } from "react-native"; import { WebView } from "react-native-webview"; export default function SignContractScreen() { const [signingUserId, setSigningUserId] = useState(null); async function sendForSigning() { const res = await fetch(`${process.env.BACKEND_URL}/api/signing-requests`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "Consulting Agreement", template_id: "your-template-id", signer_email: "alice@example.com", signer_first_name: "Alice", signer_last_name: "Johnson", }), }); const data = await res.json(); setSigningUserId(data.signing_request_user_id); } if (signingUserId) { return ( ); } return ( Send contract for signature ); } ``` You can also ask the a0.dev AI chat to wire this up: "When the user taps Send Contract on the contract screen, call my backend at /api/signing-requests with the template ID and the user's details, then show the Firma signing WebView." **Deep link** If you do not want to host a WebView, open the signing URL in the system browser using `expo-linking`. The signer completes the document in the browser and your app handles the return via webhook. ```typescript theme={null} import * as Linking from "expo-linking"; async function sendForSigning() { const res = await fetch(`${process.env.BACKEND_URL}/api/signing-requests`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "Consulting Agreement", template_id: "your-template-id", signer_email: "alice@example.com", signer_first_name: "Alice", signer_last_name: "Johnson", }), }); const data = await res.json(); await Linking.openURL(data.signing_link); } ``` This is fine for low-stakes flows but loses the in-app experience. The WebView path is preferred for production. ## Webhook integration To track when documents are signed, add a webhook endpoint to your backend: ```typescript theme={null} app.post("/api/firma-webhook", async (req, res) => { const { type, data } = req.body; if (type === "signing_request.completed") { const signingRequestId = data.signing_request.id; // Update your database or trigger the next workflow step. } res.json({ received: true }); }); ``` In the Firma dashboard under **Settings > Webhooks**, register a webhook pointing to your endpoint URL. Firma sends events for key state changes. See the [webhooks guide](/guides/webhooks) for all event types and signature verification. > [!WARNING] > > Always verify the webhook signature using your Firma webhook signing secret in production. See the [webhooks guide](/guides/webhooks) for implementation details. ## Embedded signing The WebView shown in the getting started section is the mobile equivalent of Firma's embedded signing experience. Once you have the recipient's `signing_request_user_id` from the API (returned as `first_signer.id` in the `create-and-send` response), load it in a WebView: ```typescript theme={null} ``` See the [embedded signing guide](/guides/embeddable-signing) for full setup instructions including security best practices. ## Next steps * [API authentication](/guides/authentication) - API keys and workspace scoping * [Webhooks guide](/guides/webhooks) - Event types, payloads, and signature verification * [Embedded signing](/guides/embeddable-signing) - In-app signing experience * [Creating workspaces](/guides/creating-workspaces) - Multi-tenant setups for SaaS apps * [Complete setup guide](/guides/complete-setup-guide) - End-to-end Firma integration walkthrough * [API reference](/api-reference) - Full endpoint documentation # Anchor Tags Source: https://docs.firma.dev/guides/anchor-tags Place fields automatically by matching literal marker text in an uploaded PDF or DOCX, instead of specifying pixel coordinates yourself. Anchor tags let you position fields on a document by matching text that's already in the file, instead of calculating x/y coordinates. You upload a PDF or DOCX containing marker text - commonly written as `{{SIGN_HERE}}` or similar, though any literal string works - pass an `anchor_tags` array in your create request, and Firma searches the document for each string and places a field wherever it finds a match. > [!NOTE] > > Anchor tags only work with document-based creation - a request that includes `document` (base64) or `document_id`. They have no effect on `template_id`-based requests, because the anchor pipeline searches the uploaded document itself; a template's fields are already positioned. ## How matching works `anchor_string` is matched as literal, case-insensitive-by-default substring text anywhere in the document - there's no required delimiter syntax. `{{...}}` is just a convention that's visually easy to spot in a document and unlikely to collide with real content; `"Sign Here:"` or `"X_____"` work exactly the same way. Each anchor tag supports: | Property | Default | Effect | | :- | :- | :- | | `case_sensitive` | `false` | Whether the match respects letter case | | `match_whole_word` | `true` | Requires non-word characters on both sides of the match | | `occurrence` | `0` | `0` places a field at **every** match; `1`, `2`, etc. place a field only at that specific (1-indexed) occurrence | | `ignore_if_not_present` | `false` | If the string isn't found: `true` skips it silently, `false` fails the whole request | | `x_offset` / `y_offset` | `0` | Shifts the placed field away from the matched text | | `offset_units` | `percent` | `percent` of page dimensions, or `pixels` (PDF points, 72 DPI) | > [!WARNING] > > If the document has no extractable text at all - a scanned or image-based PDF, for example - no anchor can match. Set `ignore_if_not_present: true` if you want the request to continue anyway (the field is simply never placed); otherwise the request fails validation. ### Default field sizes If you don't pass `width`/`height` on an anchor tag, the field is sized by `type` (as a percentage of the page): | Type | Width | Height | | :- | :- | :- | | `signature` | 25% | 5% | | `initial` / `initials` | 10% | 5% | | `date` | 20% | 3% | | `text` / `textarea` / `text_area` / `dropdown` / `url` | 20% | 3% | | `checkbox` / `radio` / `radio_buttons` | 3% | 3% | > [!WARNING] > > `stamp`, `file`, `approval_signature`, `approval_checkmark`, and `approval_date` are all accepted as anchor `type` values by the server, but none of the five has a dedicated entry in this table - they silently fall back to the `text` default (20% × 3%). That's usually too small for a stamp, file-upload box, or approval signature. Always pass explicit `width`/`height` when anchoring any of these types. ## Hiding anchor text Two independent, combinable options control what happens to the marker text and the area around it once a field is placed: | Option | Default | What it actually does | | :- | :- | :- | | `remove_anchor_text` | `true` | Sets the anchor string's glyphs to **invisible PDF text rendering mode** in place. The characters stay in the content stream (so surrounding text doesn't reshuffle) but nothing is painted for them. | | `add_white_background` | `false` | Draws an **opaque white rectangle** over the field's entire bounding box, covering whatever document content sits underneath - not just the anchor string. | > [!WARNING] > > The public API reference currently describes `remove_anchor_text` as "drawing a white rectangle over" the anchor text. That description is stale - it matches an earlier implementation. The current behavior is invisible-text rendering (above), which leaves the glyphs in place rather than painting over them. Drawing a rectangle is what `add_white_background` does, and it acts on the whole field box, not the anchor string specifically. > > If marker text uses an embedded subset (CID) font or is split across PDF text-show operators, Firma may be unable to hide it. Firma then draws a white box over the matched marker area automatically, even when `add_white_background` is not set. A `no glyph advances for font` error means the marker was inside a PDF Form XObject or transparency group whose glyph positions could not be replayed. Because `remove_anchor_text` only changes how the text is *painted*, it never removes the string from the document's text layer: > [!NOTE] > > A document processed with `remove_anchor_text: true` (the default) looks like the anchor text is gone in any PDF viewer - but running text extraction (`pdftotext`, a PDF library's `extractText`, etc.) against the same file still returns the literal anchor string. This applies to the finished, signed document too, not just the version before signing. If you see a support report that anchor text is "still there" after processing, this is almost always the explanation: the text is invisible, not deleted, and the signature itself is a separate image layer placed at the anchor's coordinates. If you need the area behind a field visually blanked (for example, covering a printed placeholder box, not just the marker text inside it), combine both options - `remove_anchor_text` hides the marker glyphs, `add_white_background` covers the full field footprint. ## DOCX documents There's no DOCX-specific anchor-matching logic. A DOCX upload is fully converted to PDF first, and the exact same text-search pipeline described above then runs against the resulting PDF: 1. The first bytes of the uploaded file are sniffed to detect DOCX (a ZIP-format file) vs. PDF. 2. A DOCX is converted via `mammoth` (DOCX → HTML) and then re-laid-out from scratch onto a fixed A4 page with fixed margins and a fixed font-size table - it is **not** a rasterization of the original Word pagination. 3. Anchor matching runs against this freshly-rendered PDF. > [!WARNING] > > Because the DOCX→PDF conversion re-lays out content rather than preserving Word's original layout, an anchor's position after conversion depends on where the converter's own renderer places that text - not on where it appeared in the original Word document. Page breaks and line wraps can shift. **Images embedded in the DOCX are dropped entirely during conversion** - the HTML parsing step only handles headings, paragraphs, lists, and tables, with no image support. If an anchor sits near an image in your source DOCX, expect the image to be missing from the signing document, not just repositioned. Supported field types are identical to PDF anchors - by the time anchor matching runs, the file is already a PDF, so there's no DOCX-specific restriction on which `type` values you can use. ## PDF documents For a native PDF upload, anchor matching runs directly against the document: positioned text is extracted page by page, adjacent text fragments on the same line are merged (so an anchor string split across separate PDF text-show operators by the original PDF producer is still found as one match), and each match is converted from PDF points into a page-relative percentage position for the new field. > [!NOTE] > > Match position is approximated proportionally from character index within a text run, not from exact per-glyph kerning. On proportional (non-monospace) fonts, a placed field can be very slightly off-center relative to the exact anchor text. This is rarely visible at normal field sizes, but worth knowing if you need sub-pixel placement accuracy. ### PDF vs. DOCX at a glance | | PDF | DOCX | | :- | :- | :- | | Original layout preserved | Yes - matching runs on the exact uploaded file | No - the file is reflowed onto a fixed A4 layout before matching runs | | Images | Untouched by anchor processing | Dropped during DOCX→PDF conversion | | Matching pipeline | Runs directly | Runs against the converted PDF (identical logic) | ## Complete example This request creates and sends a document with three anchor-placed fields: a signature, a date defaulting to the day of signing, and a read-only text field pulling from a fixed value. ```bash cURL theme={null} curl -X POST "https://api.firma.dev/functions/v1/signing-request-api/signing-requests/create-and-send" \ -H "Authorization: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Employment Contract", "document_id": "c251c2c0-a184-4f8c-8e65-be433e6a714a", "recipients": [ { "first_name": "Alice", "last_name": "Johnson", "email": "alice@example.com", "designation": "Signer" } ], "anchor_tags": [ { "anchor_string": "{{SIGN_HERE}}", "type": "signature", "recipient_id": "temp_1" }, { "anchor_string": "{{DATE}}", "type": "date", "recipient_id": "temp_1", "date_signing_default": true }, { "anchor_string": "{{CONTRACT_REF}}", "type": "text", "recipient_id": "temp_1", "read_only": true, "read_only_value": "Contract #12345" } ] }' ``` ```javascript Node.js theme={null} const response = await fetch( 'https://api.firma.dev/functions/v1/signing-request-api/signing-requests/create-and-send', { method: 'POST', headers: { 'Authorization': process.env.FIRMA_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Employment Contract', document_id: 'c251c2c0-a184-4f8c-8e65-be433e6a714a', recipients: [ { first_name: 'Alice', last_name: 'Johnson', email: 'alice@example.com', designation: 'Signer' } ], anchor_tags: [ { anchor_string: '{{SIGN_HERE}}', type: 'signature', recipient_id: 'temp_1' }, { anchor_string: '{{DATE}}', type: 'date', recipient_id: 'temp_1', date_signing_default: true }, { anchor_string: '{{CONTRACT_REF}}', type: 'text', recipient_id: 'temp_1', read_only: true, read_only_value: 'Contract #12345' } ] }) } ) const result = await response.json() ``` ```python Python theme={null} import os import requests response = requests.post( 'https://api.firma.dev/functions/v1/signing-request-api/signing-requests/create-and-send', headers={ 'Authorization': os.environ['FIRMA_API_KEY'], 'Content-Type': 'application/json' }, json={ 'name': 'Employment Contract', 'document_id': 'c251c2c0-a184-4f8c-8e65-be433e6a714a', 'recipients': [ { 'first_name': 'Alice', 'last_name': 'Johnson', 'email': 'alice@example.com', 'designation': 'Signer' } ], 'anchor_tags': [ { 'anchor_string': '{{SIGN_HERE}}', 'type': 'signature', 'recipient_id': 'temp_1' }, { 'anchor_string': '{{DATE}}', 'type': 'date', 'recipient_id': 'temp_1', 'date_signing_default': True }, { 'anchor_string': '{{CONTRACT_REF}}', 'type': 'text', 'recipient_id': 'temp_1', 'read_only': True, 'read_only_value': 'Contract #12345' } ] } ) result = response.json() ``` > [!NOTE] > > `recipient_id` uses a temporary id (`temp_1`) here because the recipient is defined in the same request (document-based creation). Fields resolved from anchor tags are merged with any manually specified `fields` in the same request, and once created they're ordinary field rows - the response doesn't distinguish an anchor-placed field from a manually positioned one, and doesn't expose which anchor string or occurrence produced it. ## Gotchas ### Removed anchor text is hidden, not deleted - plan for it showing up in extraction As covered above, `remove_anchor_text` never deletes characters from the PDF; it only stops them from being painted. If your own compliance or redaction requirements mean a marker string can never appear in programmatic text extraction of the final signed document, anchor tags as implemented today can't satisfy that - choose anchor strings you're comfortable having permanently present (invisibly) in the file, or don't rely on this API to scrub them. ### Reprocessing an already-anchored document re-matches the same anchors Because the anchor text is only visually hidden, a document you've already run through anchor-tag processing still matches the same `anchor_string` values if you pass it (or a copy of it) back into a *new* create request with the same `anchor_tags`. The invisible text is indistinguishable from visible text to the matching step. Always run anchor tags against your original, unprocessed source document - not against a document you already generated from a previous anchor-tag request. ### Anchor-tag font styling mostly doesn't persist An anchor tag accepts `font_family`, `font_size`, `font_color`, and `text_align`. Only `font_size` actually reaches the created field - it's merged into `format_rules.fontSize` (clamped to 8–48). `font_family`, `font_color`, and `text_align` are resolved internally but dropped before the field is saved, so setting them on an anchor tag has no visible effect. ### `stamp`, `file`, and `approval_*` anchor types need explicit sizing `stamp`, `file`, `approval_signature`, `approval_checkmark`, and `approval_date` all pass server-side validation as anchor `type` values, but none has a dedicated default-dimensions entry, so all five silently inherit the `text` default (20% × 3%). Pass `width` and `height` explicitly for these types. ## Next steps * [Sending a signing request](/guides/sending-signing-request) for the full recipient and field creation flow * [Field prefilling](/guides/field-prefilling) - the `read_only`/`read_only_value`/`format_rules.prefilledData` behavior that anchor-placed fields also follow once created * [Webhooks](/guides/webhooks) - subscribe to webhook events to react as anchor-placed fields are completed # Antigravity Source: https://docs.firma.dev/guides/antigravity-integration Add legally binding e-signatures to any project using Google Antigravity CLI or Antigravity Desktop with Firma's MCP servers. Firma lets you add legally binding e-signatures to anything you build with Google Antigravity. Connect Firma's MCP servers to Antigravity CLI or Antigravity Desktop and the agent can generate accurate Firma integration code, manage your signing requests, and operate on your Firma data directly from the terminal or the IDE. This guide covers two ways to use Firma with Antigravity: 1. **Antigravity CLI** - Terminal-based agent with MCP support. Best for developers who work in the terminal. 2. **Antigravity Desktop** - Standalone IDE with the same MCP configuration. Best for developers who prefer a visual editor. ## Prerequisites * A [Firma account](https://app.firma.dev) with an API key * Antigravity CLI or Antigravity Desktop installed * At least one Firma template with signing fields configured > [!NOTE] > > Firma uses the raw API key as the `Authorization` header value - do not prefix it with `Bearer`. This differs from many other APIs. ## Getting started **Antigravity CLI** Antigravity CLI (`agy`) is a terminal-based AI agent that supports MCP servers for external tool integration. Connecting Firma's MCP servers lets the agent build Firma integrations and operate on your Firma data. ### Step 1: Install Antigravity CLI If you haven't already, install the CLI: ```bash theme={null} curl -fsSL https://antigravity.google/cli/install.sh | bash ``` On Windows (PowerShell): ```powershell theme={null} irm https://antigravity.google/cli/install.ps1 | iex ``` The binary installs as `agy`. You may need to add `~/.local/bin` to your PATH. ### Step 2: Add the Firma MCP servers Create or edit `~/.gemini/antigravity/mcp_config.json` and add both Firma servers: ```json theme={null} { "mcpServers": { "firma-api": { "serverUrl": "https://mcp.firma.dev/mcp" }, "firma-docs": { "serverUrl": "https://docs.firma.dev/mcp" } } } ``` * **firma-api** gives Antigravity direct access to the Firma API - 84 tools across signing requests, templates, workspaces, and webhooks. * **firma-docs** gives Antigravity access to Firma's full documentation so it generates accurate integration code. Restart the CLI after saving the config. On first use of a Firma tool, Antigravity will walk you through signing in with your Firma account via OAuth. > [!NOTE] > > When to use which server: `firma-api` is for doing things (sending signing requests, managing templates). `firma-docs` is for building things (generating integration code with accurate API details). Most developers want both connected. ### Step 3: Use Firma from the CLI Once connected, you can ask Antigravity to build Firma integrations or operate on your data: **Generate a backend integration:** ```text theme={null} Using the Firma API docs, create an Express route that: 1. Accepts a name, template_id, signer email, and signer name 2. Uses the create-and-send endpoint to send a signing request 3. Returns the signing request ID and the signer's signing link Store the API key in process.env.FIRMA_API_KEY. ``` **List your signing requests:** ```text theme={null} List all pending signing requests in my default workspace. ``` **Send a signing request:** ```text theme={null} Create a signing request from the NDA template and send it to jane@example.com ``` **Add embedded signing to a React app:** ```text theme={null} Using the Firma docs, add an embedded signing view to this React app. After a signing request is created, render the signer's signing_link in an iframe. ``` **Wire up webhooks:** ```text theme={null} Using the Firma docs, create a webhook handler that listens for signing_request.completed events and updates the contract status in our Postgres database. ``` Referencing "the Firma docs" explicitly tells the agent to query the `firma-docs` MCP server before writing code. Without that, it may fall back to general knowledge and miss endpoint or auth details. **Antigravity Desktop** Antigravity Desktop is a standalone IDE that uses the same MCP configuration as the CLI. If you have already configured the CLI, the Desktop app picks up the same servers automatically. ### Step 1: Install Antigravity Desktop Download and install Antigravity Desktop from [antigravity.google](https://antigravity.google). ### Step 2: Add the Firma MCP servers Antigravity Desktop reads the same config file as the CLI. Create or edit `~/.gemini/antigravity/mcp_config.json`: ```json theme={null} { "mcpServers": { "firma-api": { "serverUrl": "https://mcp.firma.dev/mcp" }, "firma-docs": { "serverUrl": "https://docs.firma.dev/mcp" } } } ``` Restart Antigravity Desktop after saving the config. ### Step 3: Build Firma integrations from the IDE With the MCP servers connected, use the agent chat in Antigravity Desktop the same way you would in the CLI. The agent can read your project files, generate Firma integration code, and wire up endpoints - all within the IDE. The same prompts from the CLI tab work in the Desktop chat. The Desktop agent has the additional advantage of seeing your full project context, so it can generate code that fits your existing architecture. ## What the agent generates When you ask Antigravity to build a Firma integration, the generated backend code should look like this: ```typescript theme={null} import express from "express"; const FIRMA_API = "https://api.firma.dev/functions/v1/signing-request-api"; const app = express(); app.use(express.json()); app.post("/api/signing-requests", async (req, res) => { const { name, template_id, signer_email, signer_first_name, signer_last_name } = req.body; const response = await fetch( `${FIRMA_API}/signing-requests/create-and-send`, { method: "POST", headers: { Authorization: process.env.FIRMA_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ name, template_id, recipients: [ { first_name: signer_first_name, last_name: signer_last_name, email: signer_email, designation: "Signer", order: 1, }, ], }), } ); const data = await response.json(); if (!response.ok) return res.status(response.status).json({ error: data }); res.json({ signing_request_id: data.id, signing_request_user_id: data.first_signer.id, signing_link: data.first_signer.signing_link, }); }); app.listen(3000); ``` The `create-and-send` endpoint creates the signing request and sends it to recipients atomically. If you need to review or modify the request before sending, use `POST /signing-requests` to create a draft, then `POST /signing-requests/{id}/send` separately. > [!WARNING] > > Never expose your API key in frontend code. Always call the Firma API from a backend where secrets are kept secure. ## Webhook integration To track signing events in real time, ask Antigravity to add a webhook handler: ```text theme={null} Using the Firma docs, create a webhook handler that receives Firma events. When signing_request.completed fires, update the contract record in the database. ``` The agent will generate something like this: ```typescript theme={null} app.post("/api/firma-webhook", async (req, res) => { const { type, data } = req.body; if (type === "signing_request.completed") { const signingRequestId = data.signing_request.id; // Update your database or trigger the next workflow step. } res.json({ received: true }); }); ``` In the Firma dashboard under **Settings > Webhooks**, register your endpoint URL. Firma sends events for all major state changes. See the [webhooks guide](/guides/webhooks) for the full event list and signature verification. > [!WARNING] > > Always verify the webhook signature using your Firma webhook signing secret in production. See the [webhooks guide](/guides/webhooks) for implementation details. ## Embedded signing For apps where signers complete documents inside your UI instead of opening a Firma-hosted page, the `create-and-send` response includes `first_signer.id` (the `signing_request_user_id`) and a ready-made `first_signer.signing_link`. Load the signer URL in an iframe: ```html theme={null} ``` See the [embedded signing guide](/guides/embeddable-signing) for full setup including security best practices. ## Migrating from Gemini CLI If you previously used Firma with Gemini CLI, here is what changed: | Gemini CLI | Antigravity CLI | | - | - | | `~/.gemini/settings.json` | `~/.gemini/antigravity/mcp_config.json` | | `httpUrl` for remote servers | `serverUrl` for remote servers | | `gemini` binary | `agy` binary | Import your existing Gemini CLI plugins: ```bash theme={null} agy plugin import gemini ``` > [!NOTE] > > Gemini CLI stops serving for individual-tier users on June 18, 2026. If you are on a Gemini Code Assist Standard/Enterprise license, the Gemini CLI IDE extensions continue to work. ## Tips * **Use `firma-docs` when building, `firma-api` when operating.** The docs server helps the agent write correct integration code. The API server lets the agent manage signing requests, templates, and workspaces directly. * **Pass `template_id` explicitly.** Templates are the safest way to constrain what the agent can send. Let the agent pick a template ID from a short, named list you control. * **Validate before sending.** For higher-stakes documents, ask the agent to create a draft with `POST /signing-requests`, then confirm before calling `POST /signing-requests/{id}/send`. * **Workspaces for multi-tenant apps.** If you are building a SaaS product, give each end customer their own Firma workspace so templates and usage stay isolated. ## Next steps * [API authentication](/guides/authentication) - API keys and workspace scoping * [Webhooks guide](/guides/webhooks) - Event types, payloads, and signature verification * [Embedded signing](/guides/embeddable-signing) - In-app signing experience * [Creating workspaces](/guides/creating-workspaces) - Multi-tenant setups for SaaS apps * [MCP integration](/guides/mcp) - Full MCP server reference with all 84 tools * [Complete setup guide](/guides/complete-setup-guide) - End-to-end Firma integration walkthrough * [API reference](/api-reference) - Full endpoint documentation # API Changelog Source: https://docs.firma.dev/guides/api-changelog A comprehensive changelog of all API versions and their changes This document provides a detailed changelog of all Firma API versions, documenting new features, breaking changes, and improvements between each release. ## Version Summary | Version | Date | Release Type | Key Changes | | - | - | - | - | | [v1.40.4](#v1-40-4) | 2026-10-23 | Patch Release | Reference corrections: webhook secret rotation once per hour with one previous secret kept, `409 ALREADY_SENT` and the success example of `POST /signing-requests/{id}/send`, create-and-send failure behavior, `signer_email` partial match and `exclude_deleted` on the list, partial downloads, `document_only_hash`; no API behavior change | | [v1.40.3](#v1-40-3) | 2026-10-20 | Patch Release | Audit endpoint returns `otp_verified` events (one-time-code evidence) with details; `verify-signing-otp` error responses tightened; no schema change | | [v1.40.2](#v1-40-2) | 2026-10-19 | Patch Release | Webhook event list corrected: the `events` list of `POST /webhooks` and `PUT /webhooks/{id}` now names the 26 events Firma.dev sends; no API behavior change | | [v1.40.1](#v1-40-1) | 2026-10-18 | Patch Release | Damaged PDFs: `400 MALFORMED_PDF` with `malformed_pages` when a page of an uploaded PDF has no valid content, on the 11 routes that accept a document; an unreadable PDF returns `400 VALIDATION_ERROR` instead of `500` | | [v1.40.0](#v1-40-0) | 2026-10-17 | Minor Release | Signing page buttons: `allow_decline` and `show_save_for_later` on company settings, workspace settings, templates, and signing requests hide Decline to Sign and Save & Finish Later (a hidden Decline also refuses declines). `413 REQUEST_TOO_LARGE` for request bodies over 6 MiB | | [v1.39.1](#v1-39-1) | 2026-10-12 | Patch Release | Organization seals from templates: `POST /signing-requests` and `/create-and-send` with `template_id` inherit the template's seals (an array replaces, `[]` creates without seals), `dropped` entries, `seal_attribute`, `seal_participants` on `GET /templates/{id}`, `PUT /templates/{id}`, and `POST /templates/{id}/duplicate` (except for companies enabled for multi-document features), `warnings` on `POST /templates`, order kept relative to template recipients; `seal_participants` filled for every company | | [v1.39.0](#v1-39-0) | 2026-10-12 | Minor Release | Multiple documents per template and signing request (available to every company): `document_ids` on create, `/documents` routes to add, replace, rename, reorder, remove, and download documents, per-document anchor tags (`document_index`), `documents_layout_version` on field writes, and new error codes. Participant order: one order space for signers, approvers, and seals, shift-insert moves, `INVALID_SIGNING_ORDER`, `SEAL_ORDER_COLLISION`, `SIGNING_ORDER_LOCKED_APPLIED`. Organization seals return `authority_name` | | [v1.38.0](#v1-38-0) | 2026-09-17 | Minor Release | Catalan (`ca`) support: the platform's supported language set grows to 15, accepted everywhere a language code is accepted, including workspace and company settings, per-signing-request email language, signer terms, email template defaults, and the organization-seal statement language | | [v1.37.1](#v1-37-1) | 2026-09-15 | Patch Release | CC recipients: accepted on `PUT`/`PATCH` of templates and signing requests, returned in every recipient list, and copied from the template on template-based creation | | [v1.37.0](#v1-37-0) | 2026-09-14 | Minor Release | Organization seals: seal lifecycle endpoints under `/seals`, `seal_participants[]` on templates and signing requests, seal swap on sent requests, seven new webhook events | | [v1.36.0](#v1-36-0) | 2026-08-31 | Minor Release | Anchor tags: `add_white_background`; structured error responses (`code`, `errors[]`); `warnings[]` on create-and-send responses | | [v1.35.0](#v1-35-0) | 2026-08-13 | Minor Release | Cross-workspace template copy: new `POST /templates/{id}/copy` endpoint deep-copies a template to another workspace (protected API key required) | | [v1.34.0](#v1-34-0) | 2026-08-07 | Minor Release | Completion page customization: custom title, message, redirect URL, and redirect delay at four cascading levels (company, workspace, template, signing request) | | [v1.33.0](#v1-33-0) | 2026-08-03 | Minor Release | Five new languages — Czech (`cs`), Swedish (`sv`), Dutch (`nl`), Romanian (`ro`), Norwegian Bokmål (`nb`) — accepted everywhere a language code is accepted: workspace/company settings, per-signing-request email language, signer terms, and email template defaults | | [v1.32.0](#v1-32-0) | 2026-07-22 | Minor Release | Explicit field font size: optional `fontSize` (8-48px) in `format_rules` for text-bearing fields, honored in the signing view and the sealed PDF with auto-shrink preserved; new `font_size` property on anchor tags | | [v1.31.0](#v1-31-0) | 2026-07-14 | Minor Release | Opt-in per-signing-request email language: new `language` field on create-and-send and update sets the language for all signer-facing emails (falls back to workspace/company default when omitted) | | [v1.30.0](#v1-30-0) | 2026-07-08 | Minor Release | Signer mark retrieval: signature / initials / stamp images (base64 PNG) and signer-uploaded file download, per signer, via the API | | [v1.29.0](#v1-29-0) | 2026-07-06 | Minor Release | Two-step document upload (`POST /documents`) for large files up to 50MB; new `document_id` field on create and create-and-send | | [v1.28.0](#v1-28-0) | 2026-07-05 | Minor Release | TypeScript SDK code samples (`@firma-dev/sdk`) added to every endpoint; embedded signing-request and legacy `/documents` endpoints removed from the reference | | [v1.27.0](#v1-27-0) | 2026-06-29 | Patch Release | Settings schemas documented: signing-request responses now expose `show_qr_code`, `allow_presigning_download`, `identity_editable_fields`, `notify_identity_change_email`; template responses expose `show_qr_code`; `cancel` returns `emails_sent`; `resend` returns `recipients_failed`; new `TemplateSettings` and `LegacyDocumentSettings` schemas | | [v1.26.0](#v1-26-0) | 2026-06-23 | Minor Release | Workspace settings: signing button label overrides, QR code toggle, presigning download toggle, partial watermark toggle | | [v1.25.2](#v1-25-2) | 2026-06-18 | Patch Release | Provided fields that match no template field now return `400` (previously ignored silently) on `POST /signing-requests` and `/create-and-send`; field `type` values are matched case-insensitively | | [v1.25.1](#v1-25-1) | 2026-06-14 | Patch Release | `create-and-send` send-step failures now return the real status (`400`/`402`/`422`) with a machine-readable `code`, instead of masking them as `500` | | [v1.25.0](#v1-25-0) | 2026-06-14 | Minor Release | Editor brand colors: 5 new appearance fields (`color_accent`, `color_accent_fg`, `color_canvas`, `color_muted`, `color_muted_fg`) on company + workspace settings; resolved `color_palette` now returns 11 keys | | [v1.24.0](#v1-24-0) | 2026-06-09 | Minor Release | Workspace test API keys: create returns a live+test pair, reads return both, key\_type-aware regenerate/expire | | [v1.23.0](#v1-23-0) | 2026-06-04 | Minor Release | Signer terms endpoints, company settings endpoints, terms acceptance settings | | [v1.22.1](#v1-22-1) | 2026-06-02 | Patch Release | Primary/only custom email domain can now be deleted (DELETE no longer returns 400) | | [v1.22.0](#v1-22-0) | 2026-05-30 | Minor Release | Delete signing request endpoint, signing\_request.deleted webhook event | | [v1.21.1](#v1-21-1) | 2026-05-19 | Patch Release | Workspace webhook activation via API | | [v1.21.0](#v1-21-0) | 2026-05-14 | Minor Release | Custom email sender local-part setting | | [v1.20.0](#v1-20-0) | 2026-05-12 | Minor Release | Guided navigation control setting | | [v1.19.0](#v1-19-0) | 2026-05-12 | Minor Release | Logo management endpoints, workspace icon\_url | | [v1.18.0](#v1-18-0) | 2026-05-06 | Minor Release | Identity editable fields, editable prefilled data, field value validation | | [v1.17.0](#v1-17-0) | 2026-05-01 | Minor Release | Color customization for signing experience | | [v1.16.0](#v1-16-0) | 2026-04-27 | Minor Release | Download Signing Request endpoint | | v1.15.3 | 2026-04-27 | Minor | Clean field aliases | | [v01.15.02](#v01-15-02) | 2026-04-23 | Patch Release | Added missing field types to create-and-send fields schema | | [v01.15.01](#v01-15-01) | 2026-04-17 | Patch Release | Added Russian and Polish language support to API schemas | | [v01.15.00](#v01-15-00) | 2026-04-16 | Minor Release | Workspace Webhooks, Secret Rotation/Status Endpoints | | [v01.14.00](#v01-14-00) | 2026-04-14 | Minor Release | Approver/CC Designations, Approval Field Types | | [v01.13.00](#v01-13-00) | 2026-04-12 | Minor Release | Stamp Field Type | | [v01.12.01](#v01-12-01) | 2026-04-02 | Patch Release | Custom Field Variable Names (`variable_defined_name`) | | [v01.12.00](#v01-12-00) | 2026-03-26 | Minor Release | MCP Server Integration | | [v01.11.00](#v01-11-00) | 2026-03-19 | Minor Release | File Upload Fields, Anchor Tags, Field Background Colors, Signing Order Default Change | | [v01.10.00](#v01-10-00) | 2026-03-10 | Minor Release | Conditional Fields, DOCX Support, Audit Trail, Signature Frame Control | | [v01.09.00](#v01-09-00) | 2026-03-05 | Minor Release | OTP Verification, Replace Template Document | | [v01.08.00](#v01-08-00) | 2026-03-03 | Minor Release | Email Templates API, Language Support, Webhook Observability | | [v01.07.00](#v01-07-00) | 2026-02-25 | Minor Release | Hand-Drawn Signatures, Legacy Settings Fields | | [v01.06.00](#v01-06-00) | — | Minor Release | Split PDF Downloads, Field Type Normalization | | [v01.05.00](#v01-05-00) | — | Patch Release | Email Validation Warnings | | [v01.04.00](#v01-04-00) | — | Minor Release | New Field Types, PATCH Field Operations, Template User Matching | | [v01.03.00](#v01-03-00) | — | Minor Release | Email Domains API, Credit Cost, Declined Status | | [v01.02.00](#v01-02-00) | — | Minor Release | Enhanced User Fields, Ready-to-Send Indicators | | [v01.01.00](#v01-01-00) | — | Minor Release | Template CRUD, API Key Management, Comprehensive Updates | | [v01.00.02](#v01-00-02) | — | Patch Release | Custom Fields API | | [v01.00.01](#v01-00-01) | — | Initial Release | Core API Features | *** ## v1.40.4 **Release Date:** October 23, 2026 **Release Type:** Patch Release **OpenAPI Spec:** [Download v1.40.4 spec](/api-reference/v01.40.04/openapi-v01.40.04.json) ### Reference corrections The OpenAPI spec now describes what the API already does in these places. The API itself has not changed. * **Webhook secret rotation.** `POST /webhooks/rotate-secret` and `POST /workspaces/{id}/webhooks/rotate-secret` allow one rotation per hour (the spec said one per minute); a second call within the hour returns `429` with code `COOLDOWN_ACTIVE`. Only one previous secret is kept: rotating again within the 7-day overlap replaces it. The `X-RateLimit-Limit` header of `POST /webhooks/rotate-secret` now says 60 requests per minute. * **Sending a request.** `POST /signing-requests/{id}/send` on a request that was already sent returns `409` with code `ALREADY_SENT` (the spec said `400`), and `409` with code `SEND_IN_PROGRESS` while another send is running. A cancelled or declined request returns `400`. The success example now matches the response: `success`, `message`, `sentTo`, `sentAt`. Its `X-RateLimit-Limit` header now says 120 requests per minute. * **Create and send.** When the send step of `POST /signing-requests/create-and-send` fails before any part of the send has started, the signing request is removed and an error is returned; no draft is left behind. If the send had already started, the request is kept and the `409` response carries its `signing_request_id`. * **Listing signing requests.** On `GET /signing-requests`, `signer_email` is a case-insensitive partial match (the spec said exact). Soft-deleted requests are included by default with status `deleted`; the `exclude_deleted=true` parameter, now documented, leaves them out. * **Partial downloads.** `GET /signing-requests/{id}/download` returns a partial-progress snapshot for any sent request that is not finished. There is no `allow_partial_download` setting. A finished request whose completion certificate is still being generated returns `409`. * **`document_only_hash`.** The description now says which file it hashes: the sealed per-document PDF, not the uploaded file and not the final PDF with the certificate. ### Migration notes * No change to requests, responses, or webhook deliveries. * If your retry logic for `POST /signing-requests/{id}/send` expected `400` for an already-sent request, handle `409 ALREADY_SENT` instead. *** ## v1.40.3 **Release Date:** October 20, 2026 **Release Type:** Patch Release ### One-time-code evidence in the audit trail The audit trail now records that a signer entered the one-time code, with a reference to the verified code record, and `verify-signing-otp` answers more precisely when a code cannot be verified. * **Audit events.** `GET /signing-requests/{id}/audit` returns `otp_verified` events with `details.channel = "email"` and `details.otp_code_id`, described as `Entered the one-time code sent by email`. `document_finished` events carry `details.otp_code_id` when the finishing session was linked to a verified code. This applies to requests under 1,000 tracking rows; larger requests keep today's truncation. * **`verify-signing-otp` responses.** * A database fault returns `500 INTERNAL` instead of `OTP_NOT_FOUND`. * An unknown signer returns `404 SIGNING_USER_NOT_FOUND`. * A signer of a deleted request returns `404 SIGNING_REQUEST_NOT_FOUND`. Before, a live code on a deleted request still verified. * A malformed `signingUserId` returns `400 INVALID_INPUT`. * A request that can no longer be verified (cancelled, declined, expired, already signed, not yet sent, not the signer's turn, or seal paused) returns `200` with `code: "SIGNING_STATE_CHANGED"` and `state`. * A correct code with a transient fault no longer consumes an attempt. * **No OpenAPI schema change.** `event` remains free text. This release adds no reference version and links no OpenAPI document. * **Admin audit descriptions.** `GET /signing-requests/{id}/audit` and the dashboard audit trail describe a cancel from the dashboard as `Signing Request Cancellation requested` and a delete from the dashboard as `Signing Request Deletion requested`. A certificate retry adds `Certificate regeneration requested`. Firma.dev writes each line before the change, so the line stays when the action then fails or loses a race; "requested" does not confirm completion. Rows written before this release keep the old wording (`Signing Request Cancelled` / `Signing Request Deleted`). *** ## v1.40.2 **Release Date:** October 19, 2026 **Release Type:** Patch Release **OpenAPI Spec:** [Download v1.40.2 spec](/api-reference/v01.40.02/openapi-v01.40.02.json) ### Webhook event list The `events` list of `POST /webhooks` and `PUT /webhooks/{id}` in the OpenAPI spec now names the 26 events Firma.dev sends, the same events the [Webhooks guide](/guides/webhooks) describes. Since v1.37.0 the list had named events the API refuses and left out events it sends. The API itself has not changed. * Added, already sent: `signing_request.updated`, `signing_request.certificate.generated`, `signing_request.reminder.sent`, `signing_request.recipient.signed`, `signing_request.recipient.declined`, and `signing_request.recipient.identity_changed`. * Removed, never accepted by the API (a request that names one returns `400 VALIDATION_ERROR`): `signing_request.declined`, `signing_request.voided`, `signing_request.field.completed`, `signing_request.resent`, and `signing_request.credits.purchased`. For a signer who declines, subscribe to `signing_request.recipient.declined`; for a cancelled request, to `signing_request.cancelled`. * Removed, accepted by the API but never sent: `template.created`, `template.deleted`, `template.field.added`, and `workspace.deleted`. A webhook that names them stays valid, but no event of these types arrives. ### Migration notes * No change to requests, responses, or webhook deliveries. * If you generated a client or validation from the spec, regenerate it: the old list refused events you can subscribe to and offered events the API refuses. *** ## v1.40.1 **Release Date:** October 18, 2026 **Release Type:** Patch Release **OpenAPI Spec:** [Download v1.40.1 spec](/api-reference/v01.40.01/openapi-v01.40.01.json) ### Damaged PDFs Since October 18, 2026, Firma.dev refuses a PDF in which a page has no valid content, for example a page whose content points to another page's data instead of its own drawing instructions. Such a page renders blank, and Firma.dev cannot generate the completion certificate of a request that contains it. The request returns `400 MALFORMED_PDF`: ```json theme={null} { "error": "The PDF is damaged: page 12 has no valid content. Repair or re-export the file, then upload it again.", "code": "MALFORMED_PDF", "malformed_pages": [12], "document_position": 1 } ``` * `malformed_pages` lists the 1-based numbers of the damaged pages. The message names the first. * On `POST /signing-requests`, `POST /signing-requests/create-and-send`, and `POST /templates`, `document_position` (1-based, in the request's document order) names the document, and `document_id` is added when the document came from `document_id` or `document_ids`. * Nothing is written: no template, no signing request, no credit charge, no email. * On `POST /signing-requests`, `POST /signing-requests/create-and-send`, and `POST /templates`, an upload named by `document_id` or `document_ids` is used up when this error is returned: upload the repaired file with `POST /documents` again for a new id. On the `/documents` add and replace routes, the upload is kept. * Re-exporting the file from the application that created it, or saving it again from a PDF editor, usually repairs it. The routes that return it are the ones that accept a document: `POST /templates`, `PATCH /templates/{id}`, `PUT /templates/{id}`, `POST /templates/{id}/replace-document`, `POST /templates/{id}/documents`, `PATCH /templates/{id}/documents/{document_id}`, `POST /signing-requests`, `POST /signing-requests/create-and-send`, `PATCH /signing-requests/{id}`, `POST /signing-requests/{id}/documents`, and `PATCH /signing-requests/{id}/documents/{document_id}`. ### Unreadable PDFs Since October 17, 2026, a document that cannot be read as a PDF, including an encrypted PDF, returns `400 VALIDATION_ERROR` with the message `The uploaded document is not a readable PDF` on the same routes. Before, it returned `500`. The `/documents` add and replace routes already answered this way, and now also do for a file that starts like a PDF but whose page tree cannot be read. ### Migration notes * Requests with valid PDFs behave as before. * Where you upload documents, handle `400 MALFORMED_PDF`: show the message and ask for a repaired file. Sending the same file again returns the same error. * If you parse error responses strictly, accept `malformed_pages`, `document_position`, and `document_id`. * An unreadable PDF now returns `400`, not `500`. Do not retry it. *** ## v1.40.0 **Release Date:** October 17, 2026 **Release Type:** Minor Release **OpenAPI Spec:** [Download v1.40.0 spec](/api-reference/v01.40.00/openapi-v01.40.00.json) ### Signing page buttons Two new settings control the buttons that signers and approvers see on the signing page, hosted or embedded: * `allow_decline` shows the **Decline to Sign** button. When it is `false`, the button is hidden for every signer and approver, and Firma.dev also refuses a decline. No one can decline the signing request in Firma.dev, so it can only be stopped by cancelling it. * `show_save_for_later` shows the **Save & Finish Later** button. When it is `false`, only the button is hidden. Firma.dev uses the signing request's value. When it is `null`, the workspace's value applies, and when that is `null` too, the company's. The company default is `true`, so signing pages are unchanged until you set a value. | Level | Set with | Values | | - | - | - | | Company | `PUT /company/settings` | `true` or `false`. Default `true` | | Workspace | `PUT /workspace/{workspace_id}/settings` | `true`, `false`, or `null` to use the company value | | Template | `settings` on `POST /templates` and `PATCH /templates/{id}`; `template_properties.settings` on `PUT /templates/{id}` | `true`, `false`, or `null` | | Signing request | `settings` on `POST /signing-requests`, `POST /signing-requests/create-and-send`, and `PATCH /signing-requests/{id}`; `signing_request_properties.settings` on `PUT /signing-requests/{id}` | `true`, `false`, or `null` to use the workspace value | * A signing request created from a template (`template_id` on `POST /signing-requests` and `POST /signing-requests/create-and-send`, or `POST /templates/{id}/duplicate`) copies the template's values when it is created. On the two create routes, a value in the request's `settings` replaces the template's; `null` there means the workspace value. `POST /templates/{id}/duplicate` always copies the template's values. Changing the template later does not change signing requests already created from it. * `POST /templates/{id}/copy` copies both values to the new template. * A change to the workspace or company value applies to every signing request whose own value is `null`, including requests already sent and requests created before this release. * Responses return the values as `true`, `false`, or `null`: in `settings` on templates (`GET /templates`, `GET /templates/{id}`, `POST /templates`, `PUT /templates/{id}`) and signing requests (`GET /signing-requests`, `GET /signing-requests/{id}`, and the create responses), and at the top level of `GET /company/settings` and `GET /workspace/{workspace_id}/settings`. The company values are always `true` or `false`. * Any other value, such as a string or a number, returns `400 VALIDATION_ERROR` naming the key (for example `settings.allow_decline must be a boolean or null`) and writes nothing. `PUT /company/settings` also refuses `null`. ### Request size limit Since October 13, 2026, a request body larger than 6,291,456 bytes (6 MiB) returns `413 REQUEST_TOO_LARGE` with the standard `error` and `code` body. Before, the gateway answered `502`. The API reference documents it on `POST /templates`, `POST /signing-requests`, `POST /signing-requests/create-and-send`, and `POST /documents`. To send a larger document, request an upload URL with `POST /documents`, upload the file to it, and pass `document_id` or `document_ids` instead of inline base64. ### Migration notes * The change is additive. Requests that never send `allow_decline` or `show_save_for_later` behave as before, and both buttons stay visible until a value is set. * If you parse responses strictly, accept the two new keys in template, signing request, workspace settings, and company settings responses. * If you send large inline base64 documents, handle `413 REQUEST_TOO_LARGE`, or upload through `POST /documents`. *** ## v1.39.1 **Release Date:** October 12, 2026 **Release Type:** Patch Release **OpenAPI Spec:** [Download v1.39.1 spec](/api-reference/v01.39.01/openapi-v01.39.01.json) ### Update, October 13, 2026 * On `POST /signing-requests` and `POST /signing-requests/create-and-send` with `template_id`, when the template has no recipients of its own and `seal_participants` is absent or `null`, inherited seals now keep their template order first (`1..k`), followed by the request's recipients (`k+1..`). Explicit replacement arrays, templates with recipients, and requests without seals retain their existing order behavior. * Replacements through `PATCH /seals/{id}` now require `statement` with `accepted: true`, `version: 1`, `language`, `text` exactly equal to the stored `statement_text` from `GET /seals/{id}`, and `signatory_name` (`signatory_title` optional). This applies to changes in `image`, `typed`, `display_name`, `signatory_title`, or `statement`. Without `statement`, the request returns `400 SEAL_STATEMENT_REQUIRED` and writes nothing. `POST /seals` creation behavior is unchanged. * Each replacement records its own attestation time, caller, and supplied signatory. Statement text, hash, language, statement version, and `authority_name` remain those of seal version 1. Requests already sent keep their pinned version; requests sent after replacement use the new version, whose signatory and attestation date appear on their certificates. * Seal objects in `POST /seals`, `GET /seals`, `GET /seals/{id}`, `PATCH /seals/{id}`, and the `organization_seal.created`, `organization_seal.updated`, and `organization_seal.deleted` webhooks now include `statement_text` and `statement_hash`. Attester user and API key IDs, IP address, and user agent are excluded from these seal objects. ### Organization seals from templates `POST /signing-requests` and `POST /signing-requests/create-and-send` with a `template_id` now copy the template's seal participants and their seal fields. The `seal_participants` key decides what happens: * Absent or `null`: the request inherits the template's seals. * An array: the array replaces the template's seals. The template's seal fields are not copied. * `[]`: the request is created without seals. * Any other type returns `400 VALIDATION_ERROR`. This is checked on every create route, with or without `template_id`. Body fields only override template fields, so on a template that has fields a replacement array's seals cannot get a stamp field in that call, and sending returns `400 SEAL_NO_IMAGE_FIELD`. To use your own seals with their fields, create from a document, or, on `POST /signing-requests` only, use a template without fields and assign body fields with `seal_participant_temp_id`. Otherwise inherit the template's seals. An inherited seal uses the current version of its seal. If the seal has no current version, or that version is revoked or deleted, the create returns `409 SEAL_UNAVAILABLE` and nothing is written. If at least one inherited seal is kept and organization seals are not enabled, the create returns `503 SEALS_DISABLED`. An inherited seal that is limited to another workspace than the request's, or that the API key's company cannot see, is left out, along with its fields. It is listed in the response with `dropped: true`, after the other entries. Every other entry carries `dropped: false`. ```json theme={null} { "id": null, "order": null, "seal": { "id": "", "lineage_id": "", "name": "Acme Corp", "version": 2, "display_name": "Acme Corp" }, "seal_out_of_scope": true, "dropped": true } ``` `seal` is `null` when the API key's company cannot see the seal. A company-wide seal is never left out. #### Order with a template When a create with `template_id` resolves at least one seal, recipients and seals are renumbered `1..n`. Inherited seals keep their positions relative to the template's recipients. Seals in a replacement array are placed by their `order`. A seal comes first when it ties with a recipient. Such a create returns `201` where it used to return `400 INVALID_SIGNING_ORDER` or `400 SEAL_ORDER_COLLISION`. The response reports the renumbered orders. * Since October 13, 2026, if the template has no recipients and `seal_participants` is absent or `null`, inherited seals keep their template order first (`1..k`); the request's recipients follow (`k+1..`). * On `POST /signing-requests`, a numeric `recipient_id` on a field keeps counting recipients as they would be numbered without the seal. * Document-based creates (`document`, `document_id`, `document_ids`) with explicit seal participants are unchanged: the raw order checks apply. #### Seal fields and sending A seal needs at least one stamp field. Create does not check this, but sending a signing request whose seal has no stamp field returns `400 SEAL_NO_IMAGE_FIELD`. Give every seal at least one stamp field. ### Duplicating a template Except for companies enabled for multi-document features, `POST /templates/{id}/duplicate` copies the template's seal participants and their fields into the new signing request, and returns them in `seal_participants` (`[]` when there are none). Each kept seal uses the current version of its seal. If the seal has no current version, or that version is revoked or deleted, the route returns `409 SEAL_UNAVAILABLE`. A seal limited to another workspace than the target workspace, or that the API key's company cannot see, is left out and listed with `dropped: true`. If at least one seal is kept and organization seals are not enabled, the route returns `503 SEALS_DISABLED`. Both errors are returned before anything is written. For companies enabled for multi-document features, this route attaches the template's stored seal version as is. A seal that was replaced, revoked, or deleted since then fails when you send. The response has no `seal_participants` key. ### seal\_attribute A seal-owned text field takes `seal_attribute`: `display_name` or `signatory_title`. It sets which seal property the field prints. `POST /signing-requests` and `POST /signing-requests/create-and-send` accept it, from a document or a template. Any other value returns `400 VALIDATION_ERROR`; `null` is accepted. * `POST /signing-requests/create-and-send` from a document now keeps `seal_attribute`. Before, it was dropped. * A seal field copied from a template keeps the template field's formatting. `seal_attribute` on the request replaces only which property it prints. ### Templates * `GET /templates/{id}` and `PUT /templates/{id}` return `seal_participants`, ordered by `order`, `[]` when the template has none. Each entry has the shape of seal participants elsewhere. * `POST /templates` does not accept `seal_participants`, `recipients`, or `fields`. It ignores them and returns a `warnings` array with one string per key present, for example `seal_participants is not accepted on POST /templates and was ignored; add it with PATCH or PUT /templates/{id}`. Without those keys the response is unchanged. ### Other changes * Fixed: `seal_participants` on `GET /signing-requests/{id}`, `GET /signing-requests`, `POST /signing-requests`, and `POST /signing-requests/create-and-send` now returns each organization seal participant with its seal (id, lineage, version, name and display name) for every company. Previously this list was empty, even though the seal itself was saved and applied correctly on the document. This fix changes only what the API returns; stored documents, signatures, and webhooks are not changed by it. * Completion certificates now draw the applied seal's stamp, date, and text fields, and list each seal participant in the signers table, in signing order. The certificate is not generated while a seal is still outstanding (not applied, or paused). A seal applied automatically at completion, without a triggering recipient, no longer blocks the certificate; its audit line reads "after all preceding participants completed" (in the certificate's language). ### Documentation and API reference The API reference for this version documents the following. They describe answers the API already gave. * Each entry of `seal_participants` on `POST /signing-requests` and `POST /signing-requests/create-and-send` requires `temp_id` and `order`. `seal_id` is optional: without it the workspace's default seal is used or, when the workspace has none, the company's default seal, and the call returns `400 SEAL_UNAVAILABLE` when neither exists. The reference had `seal_id` as required and `temp_id` as optional. * The `seal_participant` body of `PATCH /signing-requests/{id}` and `PATCH /templates/{id}` is documented as the API reads it: `id`, `seal_id`, `order`, and on signing requests `swap_to_seal_id`. The key `participant_id` that earlier references showed was never read; use `id`. Adding a seal participant answers `201` and updating or swapping one answers `200`, both with `{ "seal_participant": ... }`. These routes return `503 SEALS_DISABLED` when organization seals are not enabled and a seal participant is added or its seal is changed. On `PATCH /signing-requests/{id}`, a field that belongs to a seal participant accepts only position and page changes; other keys, `recipient_id` included, are ignored. * A seal swap on `PATCH /signing-requests/{id}` documents its answers: `403 PROTECTED_KEY_REQUIRED` when the API key is not of the protected workspace; `404 NOT_FOUND` when the new seal does not exist, belongs to another company, or is limited to another workspace; `409 SEAL_UNAVAILABLE` when the new seal is revoked, deleted, or not the current version; `409 SEAL_MUTATION_NOT_ALLOWED` when the request was not sent or the participant is not paused; `400 SEAL_FIELD_REQUIRED` when the participant has no stamp field. * The `200` body of `DELETE /signing-requests/{id}/seal-participants/{participant_id}` is `{ "deleted": true }`. * The `fields` array of the `POST /signing-requests/create-and-send` response lists all active fields, including the fields owned by seal participants. Each item is the stored field record, not the `Field` shape used in requests (for example the type is in `field_type`). * `POST /templates/{id}/copy` documents its `seal_participants` (unchanged behaviour). * `PUT /templates/{id}` documents its `seal_participants` request array (`TemplateSealParticipantInput`), which replaces the template's seal set. * The `400` of `POST /signing-requests/create-and-send` lists `SEAL_NO_IMAGE_FIELD` (a seal without a stamp field refused when sending). * Settings whose `null` has a meaning (`require_otp_verification`, `require_terms_acceptance`, `allow_presigning_download`, `terms_url`, a field's `recipient_id` or `assigned_to_user_id`, `identity_editable_fields`, and the workspace settings `show_signature_frame` and `show_partial_watermark`) now state what `null` does. ### Migration notes * For seal replacements from October 13, 2026 onward, read `GET /seals/{id}` and send `statement` on `PATCH /seals/{id}`: `accepted: true`, `version: 1`, `language`, the exact stored `statement_text` as `text`, and `signatory_name` (`signatory_title` optional). Handle `400 SEAL_STATEMENT_REQUIRED` and accept `statement_text` and `statement_hash` in seal objects. * A `POST /signing-requests` or `POST /signing-requests/create-and-send` with `template_id` and no `seal_participants` key now inherits the template's seals. Send `"seal_participants": []` to create without them. `null` inherits too (including a client that sends `null` for an unset field). * A `seal_participants` value that is not an array or `null` now returns `400 VALIDATION_ERROR` on `POST /signing-requests` and `POST /signing-requests/create-and-send`; before, it was ignored. * If you parse responses strictly, accept the new keys: `dropped` on create responses, `warnings` on `POST /templates`, and `seal_participants` on `GET /templates/{id}`, `PUT /templates/{id}`, and `POST /templates/{id}/duplicate`. *** ## v1.39.0 > [!NOTE] > > **Update, October 13, 2026:** Multi-document features are available to every company. No enablement is needed. The enablement restrictions in the original release notes below describe the initial rollout. **Release Date:** October 12, 2026 **Release Type:** Minor Release **OpenAPI Spec:** [Download v1.39.0 spec](/api-reference/v01.39.00/openapi-v01.39.00.json) ### Multiple documents per template and signing request A template or signing request can now hold up to 20 documents, with a limit of 100 MB per document set. You can create one from several uploads, then add, replace, rename, reorder, remove, and download its documents one at a time. > [!NOTE] > > At launch, before October 13, 2026, multi-document features required enablement for your workspace's company. This covered the `/documents` routes, `document_ids` with more than one id, per-document anchor tags (`anchor_tags[].document_index`), and the `documents`, `documents_version`, and `documents_layout_version` response keys. At that time, if a company was not enabled: > > * The `/documents` routes return `404 NOT_FOUND`, exactly like an unknown endpoint. > * The three response keys are absent. > * `document_index` on an anchor tag is not applied: a single-document create accepts it with an unknown-property warning and uses the only document. > * `POST /templates`, `POST /signing-requests`, and `POST /signing-requests/create-and-send` with more than one entry in `document_ids` (repeated ids count) return `409 MULTI_DOCUMENT_READ_ONLY` before anything is created. While multi-document editing is suspended, writes to a multi-document template or signing request return `409 MULTI_DOCUMENT_READ_ONLY`. Reads keep working. #### Creating with several documents `POST /templates`, `POST /signing-requests`, and `POST /signing-requests/create-and-send` accept `document_ids`. Pass 1 to 20 temporary upload ids from `POST /documents`, in document order. * Send exactly one document source: `document`, `document_id`, or `document_ids` (or `template_id` on the signing-request routes). Sending more than one returns `400 CONFLICTING_DOCUMENT_SOURCE`. * A repeated id returns `400 DUPLICATE_DOCUMENT_ID`. More than 20 ids return `400 MAX_DOCUMENTS_EXCEEDED`. An unknown or expired upload returns `400 DOCUMENT_UPLOAD_NOT_FOUND`. A set above 100 MB returns `413 DOCUMENT_SET_TOO_LARGE`. * `document` is no longer marked as required on `POST /templates`, because `document_id` or `document_ids` can take its place. #### Per-document anchor tags For enabled companies, anchor tags take a new `document_index`: the 1-based position in `document_ids` of the document to search. It is only meaningful for multi-document requests. * With one document, `document_index` is optional and defaults to `1`. * With more than one document, it is required. A missing value returns `400 ANCHOR_DOCUMENT_REQUIRED`; a value out of range returns `400 VALIDATION_ERROR`. #### New endpoints | Method | Path | Purpose | | - | - | - | | `POST` | `/templates/{id}/documents` | Add a document (a temporary upload from `POST /documents`) at `position`, or last | | `PATCH` | `/templates/{id}/documents/{document_id}` | Replace the document with a new upload (`document_id`) or rename it (`label`); send exactly one | | `PUT` | `/templates/{id}/documents/order` | Reorder: `document_ids` must list every document of the template exactly once | | `DELETE` | `/templates/{id}/documents/{document_id}` | Remove a document; the last remaining document cannot be removed | | `POST` | `/signing-requests/{id}/documents` | Add a document at `position`, or last | | `PATCH` | `/signing-requests/{id}/documents/{document_id}` | Replace or rename a document | | `PUT` | `/signing-requests/{id}/documents/order` | Reorder the signing request's documents | | `DELETE` | `/signing-requests/{id}/documents/{document_id}` | Remove a document | | `GET` | `/signing-requests/{id}/documents/{document_id}/download` | Signed URL (valid for 1 hour) to one document: `type=original` (default) for the document as uploaded, `type=document` for the individually sealed document once the certificate is bound | Every add, replace, rename, reorder, and remove returns the full document set after the change, with `documents`, `documents_version`, and `documents_layout_version`. * A replacement must have the same number of pages, and every page must be the same size within 1 pt. Otherwise it returns `400 PAGE_COUNT_MISMATCH` or `400 DIMENSION_MISMATCH`. On a signing request, a document placed with anchor tags is replaced only when the replacement's anchors resolve exactly as stored. Otherwise it returns `400 ANCHOR_MISMATCH`. * A sent, finished, cancelled, or declined signing request returns `409 DOCUMENT_SET_LOCKED` on every document write. * A document id that is not part of the template or signing request returns `404 DOCUMENT_NOT_FOUND`. A missing or repeated id in a reorder returns `400 INVALID_DOCUMENT_ORDER`. Removing the last document returns `400 MIN_DOCUMENTS_REQUIRED`. * The download returns `404 DOCUMENT_ORIGINAL_NOT_AVAILABLE` when the uploaded file is no longer stored, and `404 DOCUMENT_SIGNED_NOT_AVAILABLE` before the certificate is bound. #### Documents in responses For enabled companies, these responses carry `documents`, `documents_version`, and `documents_layout_version`: `GET /templates`, `GET /templates/{id}`, `POST /templates`, `PUT /templates/{id}`, `GET /signing-requests`, and `GET /signing-requests/{id}`. * Each entry of `documents` has `document_id`, `position`, `label`, `original_filename`, `page_count`, and `document_url_kind`. Outside list responses, each entry also includes a signed `document_url`, valid for 1 hour. Signing requests also include `document_only_hash`, the SHA-256 of the individually sealed document once the certificate is bound. * A template or signing request whose document set has never been changed returns its one document with `document_id: null`. In list responses, it shows `documents: []`. * `documents_version` increases with every document change. `documents_layout_version` increases when documents are added, removed, or reordered. #### Field writes on multi-document templates and signing requests `PUT` and `PATCH` on `/templates/{id}` and `/signing-requests/{id}` accept `documents_layout_version` alongside field writes. Send the value from your last read. * It is required for a field write when there is more than one document or the layout version is above 1. A missing value returns `409 DOCUMENTS_LAYOUT_VERSION_REQUIRED`. * A stale value returns `409 DOCUMENT_SET_VERSION_CONFLICT`. Reload and retry. * A value that is not a non-negative integer returns `400 VALIDATION_ERROR` with the key `documents_layout_version`. A field placed on a page beyond its document's page count returns `400 VALIDATION_ERROR` with the key `page`. * An inline `document` on `PATCH /signing-requests/{id}` for a request with more than one document returns `409 MULTI_DOCUMENT_REPLACE_UNSUPPORTED`. Use the `/documents` routes instead. #### Templates with several documents * `POST /templates/{id}/copy` copies a template with more than one document as a template with one merged document. The content and the absolute page numbers of its fields are kept. The per-document labels and positions are not carried over. #### Create and send conflicts `POST /signing-requests/create-and-send` can return `409` with `MULTI_DOCUMENT_READ_ONLY`, `SEND_IN_PROGRESS`, `SEND_EFFECTS_EXIST`, `ALREADY_SENT`, `DOCUMENT_SET_BUSY`, `DOCUMENT_SET_INCONSISTENT`, `DOCUMENT_SET_VERSION_CONFLICT`, or `DOCUMENT_SET_DISPATCH_STALE`. `SEND_EFFECTS_EXIST` and `ALREADY_SENT` can also carry `signing_request_id`. It is present when the signing request this call created was kept. Recover that request instead of retrying, because a retry creates a second request. Without `signing_request_id`, no request was kept. #### Error codes New codes, all returned as `{ error, code }`: | Code | Status | Meaning | | - | - | - | | `MULTI_DOCUMENT_READ_ONLY` | 409 | Multi-document editing is suspended, or a create with more than one entry in `document_ids` for a company that is not enabled | | `CONFLICTING_DOCUMENT_SOURCE` | 400 | More than one of `document`, `document_id`, `document_ids`, `template_id` | | `DUPLICATE_DOCUMENT_ID` | 400 | The same upload id appears twice in `document_ids` | | `MAX_DOCUMENTS_EXCEEDED` | 400 | More than 20 documents | | `MIN_DOCUMENTS_REQUIRED` | 400 | The last remaining document cannot be removed | | `DOCUMENT_UPLOAD_NOT_FOUND` | 400 | Unknown or expired temporary upload | | `DOCUMENT_SET_TOO_LARGE` | 413 | The document set would exceed 100 MB | | `ANCHOR_DOCUMENT_REQUIRED` | 400 | An anchor tag without `document_index` on a multi-document create | | `PAGE_COUNT_MISMATCH` | 400 | The replacement document has a different number of pages | | `DIMENSION_MISMATCH` | 400 | The replacement document has different page dimensions | | `ANCHOR_MISMATCH` | 400 | The replacement's anchors do not resolve as stored | | `INVALID_DOCUMENT_ORDER` | 400 | A reorder that misses or repeats a document | | `DOCUMENT_NOT_FOUND` | 404 | No document with this id on the template or signing request | | `DOCUMENT_ORIGINAL_NOT_AVAILABLE` | 404 | The uploaded file is no longer stored | | `DOCUMENT_SIGNED_NOT_AVAILABLE` | 404 | The individually sealed document is not available yet | | `DOCUMENT_SET_LOCKED` | 409 | The signing request was sent, finished, cancelled, or declined | | `DOCUMENTS_LAYOUT_VERSION_REQUIRED` | 409 | A field write without `documents_layout_version` | | `DOCUMENT_SET_VERSION_CONFLICT` | 409 | The documents changed since your last read; reload and retry | | `DOCUMENT_SET_BUSY` | 409 | Another action is changing the documents; retry in a moment | | `DOCUMENT_SET_DISPATCH_STALE` | 409 | The document settings changed during the request; reload and retry | | `DOCUMENT_CANDIDATE_EXPIRED` | 409 | The uploaded document expired; upload it again | | `DOCUMENT_SET_INCONSISTENT` | 409 | The documents are in an inconsistent state; contact support | | `MULTI_DOCUMENT_REPLACE_UNSUPPORTED` | 409 | An inline document replacement that a multi-document set does not support; use the `/documents` routes | | `SIGNED_DATA_IMMUTABLE` | 409 | Data that has already been signed cannot be changed | | `SEND_EFFECTS_EXIST` | 409 | Sending has already started and cannot be undone | | `SEND_IN_PROGRESS` | 409 | A send of this signing request is in progress | The `MultiDocumentErrorCode` schema in the API reference lists all 42 codes that the multi-document routes and the routes they change can return. A server fault returns `500` with `INTERNAL_ERROR` or `INTERNAL`, depending on the route (the `/documents` routes use `INTERNAL`); neither carries detail. ### Participant order Signers, approvers, and organization seal participants share one order space. CC recipients are stored separately and never take a position in it. * `PATCH /templates/{id}` and `PATCH /signing-requests/{id}` move a recipient or seal participant by shift-insert: every participant between the old and new positions is renumbered. A target position above `n` returns `400 INVALID_SIGNING_ORDER`. An `order` below 1 returns `400 INVALID_SIGNING_ORDER` for a recipient and `400 VALIDATION_ERROR` for a seal participant. * A new Signer or Approver on those routes is inserted at `order` within `[1..n+1]`, or appended when `order` is omitted. `order: 0` or a position outside that range returns `400 INVALID_SIGNING_ORDER`. * A new seal participant requires `order` as a positive integer. A missing or zero `order` returns `400 VALIDATION_ERROR`; a position outside `[1..n+1]` returns `400 INVALID_SIGNING_ORDER`. * On `PATCH /templates/{id}` and `PATCH /signing-requests/{id}`, the seal must belong to your company: another company's seal returns `404 NOT_FOUND`. A revoked, deleted, or superseded seal returns `400 SEAL_UNAVAILABLE`. A seal scoped to another workspace returns `400 SEAL_UNAVAILABLE` on `PATCH /signing-requests/{id}`. On `PATCH /templates/{id}` it returns `404 NOT_FOUND` when you add a seal participant or move one (`order` set); changing only the seal of an existing participant (`id` and `seal_id`, no `order`) does not check the seal's workspace. * On a sent signing request, `PATCH /signing-requests/{id}` returns `409 ALREADY_SENT`, except a protected caller's swap of a paused seal participant. Removing a seal participant with `DELETE /signing-requests/{id}/seal-participants/{participant_id}` returns `409 SEAL_MUTATION_NOT_ALLOWED`. * An order change that conflicts with a seal participant that has already been applied returns `409 SIGNING_ORDER_LOCKED_APPLIED`. * `POST /signing-requests` and `POST /signing-requests/create-and-send` without seals number recipients in array order when `use_signing_order` is false or any recipient omits `order`. With seals, every recipient needs an `order`, and recipients and seals together must form `1..n`: a missing order or a gap returns `400 INVALID_SIGNING_ORDER`, and two participants (recipients or seals) on the same order return `400 SEAL_ORDER_COLLISION`. * `PUT /templates/{id}` applies each entry of `users` and `deleted_users` as its own participant write. If one entry fails, the entries applied before it are kept. * `POST /templates/{id}/copy` keeps the source template's order, drops seal participants whose seal is not in scope for the target workspace (along with their fields), and compacts the remaining participants to `[1..n]`. ### Organization seal authority name Seals returned by `POST /seals`, `GET /seals`, `GET /seals/{id}`, and `PATCH /seals/{id}` include `authority_name`, the name the seal was attested for: the company for company scope or the protected workspace, otherwise the workspace. It is `null` for seals whose lineage was created before the name was pinned; those seals certify in the company's current name. ### Other changes * `POST /signing-requests/{id}/resend` returns `409 SEND_IN_PROGRESS` while a send of that signing request is in progress. * Fixed: for companies enabled for multi-document signing, creating a signing request (`POST /signing-requests`) and creating and sending one (`POST /signing-requests/create-and-send`) now return each organization seal participant with its seal (id, lineage, version, name and display name) in `seal_participants`. Previously this list was empty, even though the seal itself was saved and applied correctly on the document. Signed documents, certificates and webhooks are not affected. * Documentation only: the `POST /signing-requests/create-and-send` response now documents `seal_participants`, which it already returned. The resend response now documents `recipients_suppressed`, the number of recipients not reminded because their address is suppressed. ### Unchanged * `PUT /signing-requests/{id}` with a field id that does not belong to the signing request still returns `400`, on multi-document requests too. * `reminders` on `POST /signing-requests/create-and-send` work as before, including on requests created with `document_ids`. ### Migration notes * The change is additive. Requests that never use `document_ids`, `document_index`, or the `/documents` routes behave as before, and companies that are not enabled see no new response keys. * If you generate a client from the OpenAPI spec: `document` is optional on `POST /templates`, and the seal participant schema in signing-request responses is now named `SealParticipantApi` (previously `SealParticipant`). * If you parse error codes strictly, accept the new codes above. * On an enabled company, send `documents_layout_version` with field writes on templates and signing requests that have more than one document. *** ## v1.38.0 **Release Date:** September 17, 2026 **Release Type:** Minor Release **OpenAPI Spec:** [Download v1.38.0 spec](/api-reference/v01.38.00/openapi-v01.38.00.json) ### Catalan support The supported language set grows from 14 to 15 with the addition of Catalan (`ca`), joining `en`, `es`, `it`, `pt`, `fr`, `de`, `el`, `ru`, `pl`, `cs`, `sv`, `nl`, `ro`, and `nb`. The new code is accepted at every language-bearing surface of the API: * **Workspace settings**: `language` on `PUT /workspace/{workspace_id}/settings` (and returned by the corresponding GET) * **Company settings**: `language` on the company settings endpoints * **Per-signing-request email language**: the `language` field on `POST /signing-requests`, `POST /signing-requests/create-and-send`, and `PATCH` / `PUT /signing-requests/{id}` * **Signer terms**: the `{language}` path parameter on the company and workspace signer-terms endpoints (`PUT` / `DELETE`) * **Email template defaults**: `GET /email-templates/defaults/{language}` * **Organization seal statement**: `statement.language` on seal creation and update, returned as `statement_language` on the seal Setting Catalan localizes the full signer experience: signer-facing emails, the signing ceremony UI, the consent statement recorded at terms acceptance, and date formatting. The completion certificate follows the workspace language, so set Catalan on the workspace for a Catalan certificate. Validation errors for unsupported codes now list all 15 languages. #### Migration notes * The API change is additive. The existing 14 language codes are accepted and returned exactly as before, and requests that never send `ca` validate as before. * Unless the signing URL carries a supported `lang` code, the signing page selects the first language in the signer's browser preference list that Firma.dev supports. A signer whose browser lists `ca` or a `ca-` regional tag ahead of every other supported language now sees the signing page in Catalan. Before v1.38.0, the signer saw the next supported language in the list or English. Signing links sent by email do not include a `lang` parameter, so this behavior also applies to existing signers. A supported `lang` code you add to an embedded signing URL still sets the language; any other value is ignored. * The consent statement resolves in this order: workspace terms in the signer's language, company terms in the signer's language, workspace terms in the workspace's configured language, company terms in the company's configured language, then Firma.dev's default terms in the signer's language. If no Catalan terms or terms in the applicable configured language exist, a Catalan-first signer now accepts Firma.dev's default Catalan statement instead of custom terms in another browser language, such as Spanish. Terms in the workspace's or company's configured language still apply when available. * Add Catalan signer terms at the company or workspace level to keep custom terms for these signers. Use `PUT /company/signer-terms/ca`, `PUT /workspace/{workspace_id}/signer-terms/ca`, or the dashboard terms settings. *** ## v1.37.1 **Release Date:** September 15, 2026 **Release Type:** Patch Release **OpenAPI Spec:** [Download v1.37.0 spec](/api-reference/v01.37.00/openapi-v01.37.00.json) (unchanged in this release) ### CC recipients The update endpoints now store recipients with `"designation": "CC"`, and the read endpoints now return them. Before this release they did neither. #### Adding and updating CC recipients * `PUT /templates/{id}` (`users[]`) and `PATCH /templates/{id}` (`user`) store CC recipients. Previously these returned `400` and `500`. * `PUT /signing-requests/{id}` (`recipients[]`) and `PATCH /signing-requests/{id}` (`recipient`) store CC recipients. Previously `PUT` returned `200` and counted the CC in `summary.recipients_created` without storing it, and `PATCH` returned `500`. * On `PUT /signing-requests/{id}`, a recipient that fails to save is now listed in `details.recipients` of the `400` response instead of being counted in `summary.recipients_created`. * Existing CC recipients can be updated by `id` and removed through `deleted_users` / `deleted_recipients`, the same way as signers and approvers. * `first_name` and `email` are required for a new CC recipient. * `designation` is matched case-insensitively (`"CC"`, `"cc"`). #### CC recipients in responses CC recipients are returned with `"designation": "CC"`, after signers and approvers, by: * `GET /signing-requests/{id}/users` * `GET /templates/{id}/users` * `GET /templates/{id}` and `GET /templates` (`recipients`) * the `recipients` array of the `POST /signing-requests`, `POST /signing-requests/create-and-send`, and `PUT /signing-requests/{id}` responses CC entries carry the same keys as the other entries on that endpoint. They never get a signing link, and keys that only apply to signing hold neutral values (for example `required_fields: []` and `ready_to_send: true`). #### Template-based creation * `POST /signing-requests` and `POST /signing-requests/create-and-send` with `template_id` now copy the template's CC recipients to the new signing request. A template CC is skipped when a CC with the same email (case-insensitive) is already on the request. * A request-body recipient with `"designation": "CC"` is always added as a CC recipient. It is never matched to a template user by `template_user_id` or `order`. #### Validation * Changing an existing recipient between CC and Signer or Approver returns `400`. `PATCH /templates/{id}` and `PATCH /signing-requests/{id}` return the message `Cannot change designation between CC and Signer/Approver; delete the recipient and create it again`, `PUT /signing-requests/{id}` lists it in `details.recipients`, and `PUT /templates/{id}` returns the generic `Validation errors occurred`. Delete the recipient and create it again with the new designation. * A field cannot be assigned to a CC recipient. On `PUT /signing-requests/{id}`, including for a CC created in the same request through `_temp_id`, the field error reads `Cannot assign fields to CC recipient.`; other field paths reject a CC id as an invalid recipient. * Updating or deleting a recipient by `id` now only matches recipients that have not been deleted. An already-deleted recipient is treated as not found instead of being written to. #### Migration notes * No new endpoints, fields, or enum values, so the v1.37.0 spec still applies. * Recipient lists can now include CC entries. If your code treats every entry as a signer, check `designation`. * Signing requests created from a template that has CC recipients now include those CC recipients, and they receive the completed document. Remove the CC recipients from the template if you do not want this. * If you relied on `PUT /signing-requests/{id}` returning `200` when a recipient failed to save, handle the `400` response. *** ## v1.37.0 **Release Date:** September 14, 2026 **Release Type:** Minor Release **OpenAPI Spec:** [Download v1.37.0 spec](/api-reference/v01.37.00/openapi-v01.37.00.json) ### Organization Seals Electronic seals applied automatically on behalf of a company, as participants in the signing order. Setup, scope rules, and certificate output are described in the [Organization Seals guide](/guides/organization-seals). #### New endpoints | Method | Path | Purpose | | - | - | - | | `POST` | `/seals` | Create a seal (`kind`: `uploaded`, `typed`, or `drawn`; `scope`: `company` or `workspace` with `workspace_id`). Requires a protected API key for company scope | | `GET` | `/seals` | List seals visible in the key's scope | | `GET` | `/seals/{id}` | Get one seal | | `PATCH` | `/seals/{id}` | Replace the image (new version), rename, update `display_name` / `signatory_title`, or set `is_default` | | `DELETE` | `/seals/{id}` | Revoke; add `?stop_pending=true` to pause in-flight participants | | `GET` | `/seals/{id}/image` | Canonical PNG; every read is logged | | `GET` | `/seals/{id}/applications` | Signing requests the seal was applied to | | `GET` | `/seals/{id}/access-log` | Lifecycle and image-read events | | `DELETE` | `/seals/{id}/erase` | Erase a revoked lineage that no request or template references, 90 days after revocation | | `DELETE` | `/signing-requests/{id}/seal-participants/{participant_id}` | Remove a seal participant and its fields from an unsent request | #### Seal participants Signing requests and templates accept a `seal_participants[]` array alongside `recipients[]` / `users[]` on `POST /signing-requests`, `POST /signing-requests/create-and-send`, `PATCH /signing-requests/{id}`, `POST /templates`, and `PATCH /templates/{id}`. Each entry takes `temp_id` (client id used to assign fields), `seal_id`, and `order`. Recipients and seals share one contiguous order space; order 1 applies at send. Fields owned by a seal set `seal_participant_temp_id` instead of a recipient. Seal fields are server-authored (`read_only: 1`, no recipient) and client-provided values on them are ignored. Signing-request responses now include `seal_participants[]`. #### Seal swap `PATCH /signing-requests/{id}` accepts `seal_participant: { "id": "", "swap_to_seal_id": "" }` to move a paused participant on a sent request to a different in-scope seal. Requires a protected API key. #### Webhook events | Event | Trigger | | - | - | | `organization_seal.created` | Seal created | | `organization_seal.updated` | Image replaced (new version), renamed, or default flag changed | | `organization_seal.deleted` | Seal revoked | | `organization_seal.erased` | Revoked lineage permanently erased | | `signing_request.seal.applied` | Seal applied to a document | | `signing_request.seal.paused` | Seal participant paused (revoked with stop pending, or integrity mismatch) | | `signing_request.seal.swapped` | Paused participant swapped to a different seal | #### Error codes `SEALS_DISABLED`, `SEAL_NOT_FOUND`, `SEAL_SCOPE_FORBIDDEN`, `SEAL_IMAGE_INVALID`, `SEAL_ALREADY_REVOKED`, `SEAL_ERASE_NOT_ELIGIBLE`, `SEAL_MUTATION_NOT_ALLOWED`, `SEAL_ORDER_COLLISION`, `SEAL_PAUSED`, `SEAL_UNAVAILABLE`, and `SEAL_CREATION_DISABLED_EDGE` (reserved for the edge fallback host; the primary API host performs seal creation). #### Migration notes * Purely additive. `recipients[]`, `users[]`, and all existing endpoints are unchanged. * No breaking changes. *** ## v1.36.0 **Release Date:** August 31, 2026 **Release Type:** Minor Release **OpenAPI Spec:** [Download v1.36.0 spec](/api-reference/v01.36.00/openapi-v01.36.00.json) ### Anchor tags * `add_white_background` on anchor tag definitions draws a white background across the full resolved field rectangle, independently of anchor-text removal. ### Error responses * Error objects carry a machine-readable `code`. * Validation failures also return `errors[]` with every error when multiple failures are reported together; the top-level error repeats the first item for backward compatibility. ### Create and send * `POST /signing-requests/create-and-send` responses include `warnings[]`: optional non-blocking warnings, including unknown anchor-tag properties during the compatibility window and anchor-processing warnings. #### Migration notes * Purely additive. No breaking changes. *** ## v1.35.0 **Release Date:** August 13, 2026 **Release Type:** Minor Release **OpenAPI Spec:** [Download v1.35.0 spec](/api-reference/v01.35.00/openapi-v01.35.00.json) ### Cross-workspace template copy New endpoint `POST /templates/{id}/copy` deep-copies a template to another workspace within the same company. All child data is copied with correct ID remapping: * Template settings (expiration, signing order, email flags, completion page, etc.) * Recipients (signers, approvers) with designation preserved * Fields with `required_conditions` and `visibility_conditions` field references remapped * CC recipients * Custom field definitions * Reminders with recipient references remapped * PDF document (independently stored copy) **Request body:** * `workspace_id` (required, uuid) - target workspace to copy the template into * `name` (optional, string, max 255) - name for the copy (defaults to original name with " (copy)" suffix) **Authorization:** requires a company-level (protected) API key. Workspace-scoped keys receive `403 PROTECTED_KEY_REQUIRED`. **New endpoint:** * `POST /templates/{id}/copy` - copy template to another workspace *** ## v1.34.0 **Release Date:** August 7, 2026 **Release Type:** Minor Release **OpenAPI Spec:** [Download v1.34.0 spec](/api-reference/v01.34.00/openapi-v01.34.00.json) ### Completion page customization Four new nullable fields on signing requests, templates, workspace settings, and company settings control what signers see after completing their signature: * `completion_title` (string, max 200 chars) - custom heading on the completion page * `completion_message` (string, max 1000 chars) - custom body text * `completion_redirect_url` (string, max 2000 chars, https only) - URL to redirect signers to after signing * `completion_redirect_delay` (integer, 0-30) - seconds before redirect (defaults to 5 when a URL is set) Settings cascade from company to workspace to template to signing request. Each level can override the parent, and each field resolves independently. Setting a field to `null` (or omitting it) inherits from the parent level; the built-in translated completion page is the final fallback. **Affected endpoints:** * `POST /signing-requests` - accepts completion fields * `POST /signing-requests/create-and-send` - accepts completion fields * `PUT /signing-requests/{id}` - accepts completion fields * `PATCH /signing-requests/{id}` - accepts completion fields * `GET /signing-requests/{id}` - returns completion fields * `POST /templates` - accepts completion fields * `PUT /templates/{id}` - accepts completion fields * `PATCH /templates/{id}` - accepts completion fields * `GET /templates/{id}` - returns completion fields * `PUT /workspace/{id}/settings` - accepts completion fields * `GET /workspace/{id}/settings` - returns completion fields * `PUT /company/settings` - accepts completion fields * `GET /company/settings` - returns completion fields **Validation:** * `completion_redirect_url` must use `https://`. For test-mode signing requests (`is_test: true`), `http://localhost` and `http://127.0.0.1` are also accepted. * Redirect is time-limited: the auto-redirect only fires within 10 minutes of signing completion. After that, the custom title and message still display but no redirect occurs. ### Migration guide No breaking changes. All new fields are nullable and default to `null`. Existing signing requests, templates, and settings are unaffected - the completion page renders its built-in translated content when no custom settings are configured. *** ## v1.33.0 **Release type:** Minor Release **Released:** 2026-08-03 [Download OpenAPI Spec](/api-reference/v01.33.00/openapi-v01.33.00.json) ### Five new languages The supported language set grows from 9 to 14: Czech (`cs`), Swedish (`sv`), Dutch (`nl`), Romanian (`ro`), and Norwegian Bokmål (`nb`) join `en`, `es`, `it`, `pt`, `fr`, `de`, `el`, `ru`, `pl`. The new codes are accepted at every language-bearing surface of the API: * **Workspace settings** — `language` on `PUT /workspace/{workspace_id}/settings` (and returned by the corresponding GET) * **Company settings** — `language` on the company settings endpoints * **Per-signing-request email language** — the `language` field on `POST /signing-requests`, `POST /signing-requests/create-and-send`, and `PATCH` / `PUT /signing-requests/{id}` * **Signer terms** — the `{language}` path parameter on the company and workspace signer-terms endpoints (`GET` / `PUT` / `DELETE`) * **Email template defaults** — `GET /email-templates/defaults/{language}` Setting one of the new languages localizes the full signer experience: signer-facing emails, the signing ceremony UI, the consent statement recorded at terms acceptance, date formatting, and the completion certificate (including correct rendering of Czech and Romanian diacritics). Validation errors for unsupported codes now list all 14 languages. ### Migration Guide No action required. The change is purely additive — the existing 9 language codes behave exactly as before, and workspaces or signing requests that never set a language are unaffected. *** ## v1.32.0 **Release type:** Minor Release **Released:** 2026-07-22 [Download OpenAPI Spec](/api-reference/v01.32.00/openapi-v01.32.00.json) ### Explicit field font size Text-bearing fields (`text`, `textarea`, `email`, `name`, `phone`, `company`, `title`, `number`, `date`, `signing_date`, `approval_date`, `dropdown`, `url`) now accept an optional `fontSize` inside `format_rules` — an integer between 8 and 48, in pixels. See the new `TextFormatRules` schema; `DateFormatRules` gains the same optional property, so a date field can carry both `dateFormat` and `fontSize`. The value is a **starting/maximum** size, not a guarantee: the signing view and the sealed PDF both start rendering the field value at the explicit size and still auto-shrink it to fit the field box when the content overflows. Omit the property for automatic sizing (the previous behavior, unchanged). Out-of-range values are clamped to 8-48; non-numeric values are ignored. Signature, initials, checkbox, radio, file, and stamp fields do not accept a font size. Set it wherever `format_rules` is accepted — template field create/update and signing-request field payloads — for example: ```json theme={null} { "format_rules": { "fontSize": 12 } } ``` ### Anchor tags: `font_size` Anchor tags gain an optional `font_size` property (integer, 8-48). When an anchor resolves to a field, the value is stored on the created field as `format_rules.fontSize` and behaves exactly as above. ### Migration Guide No action required. The properties are optional and additive — fields without `fontSize` render exactly as before, and existing templates and signing requests are unaffected. *** ## v1.31.0 **Release type:** Minor Release **Released:** 2026-07-14 [Download OpenAPI Spec](/api-reference/v01.31.00/openapi-v01.31.00.json) ### Opt-in signing-request email language New optional `language` field on the signing-request create and update endpoints — `POST /signing-requests`, `POST /signing-requests/create-and-send`, and `PATCH` / `PUT /signing-requests/{id}`. Set it to one of `en`, `es`, `it`, `pt`, `fr`, `de`, `el`, `ru`, `pl`, and every signer-facing email for that signing request — invitation, reminders, OTP verification, completion and CC, expiry, cancellation, and decline — along with its date formatting, uses that language. The field is nullable and additive: `language` controls the signer-facing **emails** for the whole request. ### Migration Guide No action required. The field is optional — omit it or send `null` and behavior is unchanged: emails fall back to the workspace language, then the company language, then `en`. Existing signing requests are unaffected. *** ## v1.30.0 **Release type:** Minor Release **Released:** 2026-07-08 [Download OpenAPI Spec](/api-reference/v01.30.00/openapi-v01.30.00.json) ### Retrieve signer marks as images Four new read-only endpoints return a signer's captured marks individually, separate from the signed PDF: * `GET /signing-requests/{id}/signers/{signer_id}/signature` — the signer's adopted signature * `GET /signing-requests/{id}/signers/{signer_id}/initials` — the signer's adopted initials * `GET /signing-requests/{id}/signers/{signer_id}/stamps/{field_id}` — a stamp for a specific stamp field * `GET /signing-requests/{id}/signers/{signer_id}/files/{field_id}` — a file the signer uploaded to a file field The signature, initials, and stamp endpoints return the mark as a base64 PNG data URI (`data:image/png;base64,...`), usable directly in an ``. The file endpoint returns a short-lived (300 second) pre-signed download URL. A signer adopts one signature and one initials, applied to all their fields of that type, so those endpoints take no `field_id`; stamps and files are per-field and require `field_id` (obtain field IDs from `GET /signing-requests/{id}/fields`). By default a signer-ID frame is overlaid on returned images; pass `include_frame=false` for the bare mark. These endpoints are rate limited to 60 requests per minute. The legal validity of a signature rests on the sealed PDF and its completion certificate, not on a retrieved image. ### Migration Guide No breaking changes. These are new additive endpoints; existing integrations require no changes. *** ## v1.29.0 **Release type:** Minor Release **Released:** 2026-07-06 [Download OpenAPI Spec](/api-reference/v01.29.00/openapi-v01.29.00.json) ### Two-step document upload A new `POST /documents` endpoint enables uploading documents larger than the inline request size limit. Instead of sending a base64-encoded document in the request body, you can now: 1. Call `POST /documents` with the file metadata to receive a presigned upload URL 2. `PUT` the raw file bytes directly to the upload URL (supports up to 50MB) 3. Pass the returned `document_id` in your `POST /signing-requests` or `POST /signing-requests/create-and-send` request This is required for documents larger than approximately 5MB. Smaller documents can continue using the inline `document` field. ### New request field: `document_id` The `POST /signing-requests/create-and-send` endpoint now accepts `document_id` as an alternative to `document` and `template_id`. All three fields are mutually exclusive - provide exactly one. ### Document size limit increase The maximum document size has been increased from 20MB to 50MB when using the two-step upload flow. ### Migration Guide No breaking changes. Existing integrations using inline `document` or `template_id` continue to work unchanged. To upload larger documents, adopt the two-step flow via `POST /documents`. *** ## v1.28.0 **Release type:** Minor Release **Released:** 2026-07-05 [Download OpenAPI Spec](/api-reference/v01.28.00/openapi-v01.28.00.json) ### TypeScript SDK code samples Every endpoint in the API reference now includes a copy-paste code sample for the official [`@firma-dev/sdk`](/guides/typescript-sdk) TypeScript client, showing the exact typed call for that operation. See the new [TypeScript SDK guide](/guides/typescript-sdk) to install the client and get started. ### Reference surface changes The following endpoints were removed from the API reference because they are not part of the documented partner API surface: | Endpoint | Notes | | - | - | | `POST /save-embedded-signing-request` | Embedded editor endpoint (JWT-authenticated). Embedded signing is documented under the [Embeddables](/guides/embeddable-signing) guides. | | `POST /send-embedded-signing-request` | Embedded editor endpoint (JWT-authenticated). | | `GET /get-embedded-signing-request` | Embedded editor endpoint (JWT-authenticated). | | `POST /documents` | Legacy document endpoint, superseded by the template and signing request endpoints. | ### Migration Guide No changes are required for integrations using the partner API endpoints documented here. If you were referencing the embedded signing-request endpoints, use the [Embeddables](/guides/embeddable-signing) guides instead; the embedded editor continues to rely on those endpoints. If you were using the legacy `/documents` endpoints, migrate to the template (`POST /templates`) and signing request (`POST /signing-requests`) endpoints. *** ## v1.27.0 **Release type:** Patch Release **Released:** 2026-06-29 [Download OpenAPI Spec](/api-reference/v01.27.00/openapi-v01.27.00.json) ### Settings fields now documented in responses This release documents settings fields that the API already returns. All changes are additive and backward-compatible — no API behavior changed, only the OpenAPI specification was updated to match what the endpoints already send. #### Signing request settings The settings object returned by the signing request endpoints (`POST /signing-requests`, `POST /signing-requests/create-and-send`, `GET /signing-requests`, `GET /signing-requests/{id}`, `PUT /signing-requests/{id}`, `PATCH /signing-requests/{id}`) now documents four additional fields: | Field | Type | Description | | - | - | - | | `show_qr_code` | boolean or null | Show a QR code on the signing page that lets signers continue on their phone. Inherits from the workspace or company setting when `null`. | | `allow_presigning_download` | boolean or null | Allow signers to download the original document before signing. Inherits from the workspace or company setting when `null`. | | `identity_editable_fields` | string\[] or null | Identity fields signers may edit before signing (e.g. `["name", "company"]`). `null` disables identity editing. | | `notify_identity_change_email` | boolean | Send an email notification when a signer changes their identity. | #### Template settings The settings object returned by the template endpoints (`POST /templates`, `PUT /templates/{id}`, `PATCH /templates/{id}`, `GET /templates`, `GET /templates/{id}`, `POST /templates/{id}/replace-document`) now documents the `show_qr_code` field. Template settings cover the same options as signing request settings except the signer-identity fields (`identity_editable_fields`, `notify_identity_change_email`), which apply only to signing requests. #### Cancel and resend responses | Endpoint | New response field | Type | Description | | - | - | - | - | | `POST /signing-requests/{id}/cancel` | `emails_sent` | integer | Number of cancellation notification emails sent to signers | | `POST /signing-requests/{id}/resend` | `recipients_failed` | integer | Number of recipients whose resend notification failed to send | ### New schemas Two new schemas split the settings shapes that differ across endpoints, so each response documents exactly the fields it returns: | Schema | Description | | - | - | | `TemplateSettings` | Settings returned by the template endpoints. Matches `SigningRequestSettings` minus the signer-identity fields (`identity_editable_fields`, `notify_identity_change_email`), which apply only to signing requests. | | `LegacyDocumentSettings` | Reduced settings set returned by the deprecated `POST /documents` endpoint. New integrations should use the signing request endpoints, which return `SigningRequestSettings`. | ### Migration Guide No breaking changes. These are documentation-only additions describing fields the API already returns. Existing integrations are unaffected. If you parse settings objects, you can now rely on the documented `show_qr_code`, `allow_presigning_download`, `identity_editable_fields`, and `notify_identity_change_email` fields, and on `emails_sent` / `recipients_failed` in cancel and resend responses. *** ## v1.26.0 **Release type:** Minor Release **Released:** 2026-06-23 [Download OpenAPI Spec](/api-reference/v01.26.00/openapi-v01.26.00.json) ### Workspace settings: four new fields Four fields are now available on `GET /workspace/{workspace_id}/settings` and `PUT /workspace/{workspace_id}/settings`: | Field | Type | Description | | - | - | - | | `signing_button_label_overrides` | object or null | Per-language custom labels for signing view buttons. Keys are language codes, values are objects mapping button keys to custom text. Set to `null` to use defaults. | | `show_qr_code` | boolean or null | Show a QR code on the signing page so signers can continue on their phone. Inherits from company settings when `null`. | | `allow_presigning_download` | boolean or null | Allow signers to download the original document before signing. Inherits from company settings when `null`. | | `show_partial_watermark` | boolean or null | Show an IN PROGRESS watermark on partial PDF downloads. Inherits from company settings when `null`. | #### Button label overrides structure ```json theme={null} { "en": { "common.finish": "Confirm", "signing.decline.confirm": "I Refuse" }, "de": { "common.finish": "Bestätigen" } } ``` Customizable button keys: `common.finish`, `signing.approveAndFinish`, `signing.nextRequiredField`, `signing.saveAndFinishLater`, `signing.decline.confirm`, `signing.decline.title`, `signing.decline.cancel`, `signing.finishAnyway`, `signing.goBack`. Values are sanitized server-side (HTML stripped, max 200 characters). Empty or whitespace-only values are ignored. *** ## v1.25.2 **Release type:** Patch Release **Released:** 2026-06-18 [Download OpenAPI Spec](/api-reference/v01.25.02/openapi-v01.25.02.json) ### Stricter validation for template field overrides When creating a signing request from a template, any provided field that does not match a field in that template is now rejected with **`400` `VALIDATION_ERROR`** instead of being silently ignored. Previously such a field was dropped and the request still succeeded, so a mistyped identifier meant the field simply never appeared on the document with no error. A provided field matches a template field by `template_field_id`, `variable_name`, or `variable_defined_name`. The error names the offending field(s): ```json theme={null} { "error": "The following provided fields do not match any field in the template and cannot be applied: full_name_x. Each provided field must reference an existing template field by 'template_field_id' or 'variable_name'.", "code": "VALIDATION_ERROR" } ``` Applies to `POST /signing-requests` and `POST /signing-requests/create-and-send`. **Migration:** ensure every field you send on a template-based create references an existing template field. If you were relying on extra/unmatched fields being ignored, remove them. ### Case-insensitive field types Field `type` values are now normalized case-insensitively. `"Initials"`, `"Signature"`, `"TextArea"`, etc. resolve to their canonical type (`initial`, `signature`, `text_area`) instead of being rejected as invalid. Lowercase values are unchanged. ## v1.25.1 **Release type:** Patch Release **Released:** 2026-06-14 [Download OpenAPI Spec](/api-reference/v01.25.01/openapi-v01.25.01.json) ### Accurate error responses for create-and-send `POST /signing-requests/create-and-send` previously returned a generic **500** whenever the send step rejected a request, hiding the real reason. It now returns the **client-actionable status** (`400`, `402`, or `422`) with a machine-readable `code` and the relevant detail: * **`400` `VALIDATION_ERROR`** — request or send validation failed (e.g. a signer missing required information); the response includes `validation_errors`, `field_errors`, or `details` identifying what to fix. * **`402` `INSUFFICIENT_CREDITS`** — not enough credits to send; `current_credits` reports the balance. * **`422` `RECIPIENT_EMAIL_SUPPRESSED`** *(new)* — a recipient's email address is suppressed (previously bounced or marked as spam) and cannot be sent to. Genuine server-side failures still return `500`. When the send step fails, the partially-created signing request is rolled back, so no orphaned draft is left behind. **Migration guide:** No action required for clients that already treat any non-2xx response as a failure. If you previously special-cased the opaque `500` from this endpoint, switch to branching on the HTTP status / `code`: `400` and `422` mean "fix the request, then retry," `402` means "add credits." *** ## v1.25.0 **Release type:** Minor Release **Released:** 2026-06-14 [Download OpenAPI Spec](/api-reference/v01.25.00/openapi-v01.25.00.json) ### Editor Brand Colors The appearance settings now include five additional brand colors that theme the **embedded editors** (the template and signing-request editors you embed on your own pages): * **`color_accent`** — accent color for highlights, active tabs, switches, and primary controls in the editor chrome. * **`color_accent_fg`** — foreground (text/icon) color shown on accent surfaces. * **`color_canvas`** — the document-canvas surround color behind the page. * **`color_muted`** — muted surface color (separators, hover states, switch tracks). * **`color_muted_fg`** — muted text color. These join the existing six color fields, so settings endpoints now carry eleven `color_*` fields: * **`GET`/`PUT /company/settings`** and **`GET`/`PUT /workspace/{workspace_id}/settings`** accept and return all five new fields. Each is a nullable `#rrggbb` hex string; `null` means inherit (workspace → company → Firma default). * The resolved **`color_palette`** object returned in embedded-editor payloads now contains eleven keys (the six existing plus `accent`, `accent_fg`, `canvas`, `muted`, `muted_fg`). When unset, `muted` defaults to the resolved `card` color and `muted_fg` is derived for readability, so the palette is always fully populated. Workspace values override company values; unset values fall back to the Firma defaults, so existing integrations render unchanged. **Migration guide:** No action required. All five fields are additive and nullable. To brand the embedded editors, send any of the new `color_*` fields (valid `#rrggbb` hex, or `null` to clear) to the company- or workspace-settings endpoint. *** ## v1.24.0 **Release type:** Minor Release **Released:** 2026-06-09 [Download OpenAPI Spec](/api-reference/v01.24.00/openapi-v01.24.00.json) ### Workspace Test API Keys Workspaces now expose a **test-mode API key** alongside the existing live key. Requests authenticated with a test key do not consume credits and produce test-flagged, watermarked signing requests. * **`POST /workspaces`** now provisions a live + test key pair and returns both `api_key` (live) and the new `test_api_key` field. * **`GET /workspaces/{id}`** and **`GET /workspaces`** now return both `api_key` and `test_api_key`. * **`POST /workspaces/{id}/api-key/regenerate`** and **`/api-key/expire`** accept an optional `key_type` (`"live"` or `"test"`, default `"live"`). Regenerating one key type no longer affects the other. * Existing workspaces created via the API are backfilled with a test key. All changes are additive and backward-compatible: consumers that read `api_key` continue to work unchanged. **Migration guide:** No action required. To use test mode, read `test_api_key` from any workspace create/get response and send it as the `Authorization` key for requests you want to run in test mode. *** ## v1.23.0 **Release type:** Minor Release **Released:** 2026-06-04 [Download OpenAPI Spec](/api-reference/v01.23.00/openapi-v01.23.00.json) ### Signer Terms New Signer Terms endpoints let you manage custom signer consent statements at the company and workspace levels: * `GET` endpoints list company and workspace signer terms * `PUT` endpoints create or update per-language company and workspace signer terms * `DELETE` endpoints remove company and workspace signer terms ### Company Settings New `GET /company/settings` and `PUT /company/settings` endpoints expose company settings, including the terms acceptance gate. ### Schema Changes * **Company settings** schema: added `require_terms_acceptance` (boolean) * **Workspace settings** schema: added `require_terms_acceptance` (boolean, nullable). Use `null` to inherit the company setting. ### Migration Guide No breaking changes. This release is additive, and no action is required for existing integrations. *** ## v1.22.1 **Release type:** Patch Release **Released:** 2026-06-02 [Download OpenAPI Spec](/api-reference/v01.22.01/openapi-v01.22.01.json) ### Email Domain Deletion `DELETE /company/domains/{id}` and `DELETE /workspace/{workspace_id}/domains/{id}` now succeed when the target is the workspace's primary domain or its only domain. Previously these returned `400 "Cannot delete primary domain"`. After deletion, outbound email falls back to the company default sender, or the platform default if none is set. To use a custom sending domain again, add and verify a new domain, then set it as primary. ### Migration Guide No breaking changes. The `400 "Cannot delete primary domain"` and "cannot delete the only domain" responses are removed from both `DELETE` domain endpoints; they now return `200` in those cases. Integrations that special-cased that `400` can drop the handling. *** ## v1.22.0 **Release type:** Minor Release **Released:** 2026-05-30 [Download OpenAPI Spec](/api-reference/v01.22.00/openapi-v01.22.00.json) ### Delete Signing Request Endpoint New `DELETE /signing-requests/{id}` endpoint for removing unsent (draft) signing requests. This endpoint only works on signing requests that have not been sent yet. For sent signing requests, use the existing cancel endpoint instead. **Response:** * `200` with `signing_request_id` and `deleted_on` on success * `409 ALREADY_SENT` if the signing request has been sent * `404` if not found or already deleted ### New Webhook Event * `signing_request.deleted` fires when a signing request is deleted via the API ### Migration Guide No breaking changes. The new `DELETE` method on `/signing-requests/{id}` is purely additive. Existing integrations are unaffected. *** ## v1.21.1 **Release type:** Patch Release **Released:** 2026-05-19 [Download OpenAPI Spec](/api-reference/v01.21.01/openapi-v01.21.01.json) ### Workspace Webhook Management The `PATCH /workspaces/{id}` endpoint now supports two new fields for managing workspace-level webhooks via the API: * `webhook_enabled` (boolean) - Enable or disable workspace-level webhooks. When set to `true` for the first time, a webhook secret is automatically generated. * `ignore_company_webhooks` (boolean) - If `true`, company-level webhooks will not fire for events in this workspace. Previously, these settings could only be configured through the dashboard UI. ### Schema Changes * **PATCH /workspaces/** request body: added `webhook_enabled` (boolean, optional) and `ignore_company_webhooks` (boolean, optional) * **PATCH /workspaces/** response: now includes `webhook_enabled`, `webhook_secret`, `webhook_secret_rotated_at`, `webhook_secret_created_at`, and `ignore_company_webhooks` ### Migration Guide No breaking changes. Existing workspace webhook configurations are unaffected. Use `PATCH /workspaces/{id}` with `webhook_enabled: true` to enable workspace webhooks via API instead of the dashboard. *** ## v1.21.0 **Release type:** Minor Release **Released:** 2026-05-14 [Download OpenAPI Spec](/api-reference/v01.21.00/openapi-v01.21.00.json) ### New Settings * `email_local_part` (string, nullable) - Customize the local-part (before the @) of outgoing signing emails when using a verified custom domain. Available at two levels: * **Company settings** (`GET/PATCH /company`) - Sets the default for all workspaces. String with default `"support"`. * **Workspace settings** (`GET/PUT /workspace/{id}/settings`) - Overrides company default. Nullable string (null inherits from company). Emails are sent from `{email_local_part}@{your-custom-domain}`. For example, setting `email_local_part` to `"noreply"` sends emails from `noreply@yourdomain.com` instead of `support@yourdomain.com`. Validation: lowercase alphanumeric, dots, hyphens, and underscores. Must start and end with an alphanumeric character. 1-64 characters. Pattern: `^[a-z0-9]([a-z0-9._-]*[a-z0-9])?$` ### Schema Changes * **Company** schema: added `email_local_part` (string, default `"support"`) * **Workspace settings** schema: added `email_local_part` (string, nullable) ### Migration Guide No breaking changes. Existing configurations continue to send emails from `support@{domain}` by default. Set `email_local_part` at the company or workspace level to customize the sender address. *** ## v1.20.0 **Release type:** Minor Release **Released:** 2026-05-12 ### New Settings * `disable_guided_navigation` (boolean, nullable) - Disable automatic scrolling to the next required field during signing. Available at four levels: * **Company settings** (`GET/PUT /company/settings`) - Sets the default for all workspaces. Boolean (not nullable). * **Workspace settings** (`GET/PUT /workspace/{id}/settings`) - Overrides company default. Boolean or null (null inherits from company). * **Template** (`POST/PATCH /templates`) - Copied to signing requests created from this template. Boolean or null. * **Signing request** (`POST/PATCH /signing-requests`) - Highest priority override. Boolean or null (null inherits from workspace/company). The setting follows the same cascading pattern as `require_otp_verification`: signing request level takes priority, then workspace, then company settings. ### Schema Changes * **Company settings** schema: added `disable_guided_navigation` (boolean) * **Workspace settings** schema: added `disable_guided_navigation` (boolean, nullable) * **Template settings** schema: added `disable_guided_navigation` (boolean, nullable) * **Signing request settings** schema: added `disable_guided_navigation` (boolean, nullable) ### Migration Guide No breaking changes. Existing signing requests and templates continue to work as before (auto-scroll enabled by default). To disable auto-scroll, set `disable_guided_navigation: true` at the desired level. [Download OpenAPI Spec](/api-reference/v01.20.00/openapi-v01.20.00.json) *** ## v1.19.0 **Release type:** Minor Release **Released:** 2026-05-12 ### New Endpoints * `POST /company/logo` - Upload a company logo image (PNG or JPEG, max 2MB). The logo appears on signing certificates and emails. * `DELETE /company/logo` - Remove the company logo. Certificates fall back to the Firma default. * `POST /workspaces/{id}/logo` - Upload a workspace-specific logo that overrides the company logo for that workspace. * `DELETE /workspaces/{id}/logo` - Remove the workspace logo. Falls back to the company logo. ### Schema Changes * **Workspace** schema now includes `icon_url` (string, nullable) - a publicly accessible URL to the workspace logo image * **Company** `icon_url` is now read-only in PATCH/PUT requests. Use the new `POST /company/logo` endpoint to manage logos. ### Migration Guide * If you were setting `icon_url` directly via `PATCH /company` or `PATCH /workspaces/{id}`, switch to the new `POST .../logo` endpoints instead. The `icon_url` field is no longer accepted in update request bodies. * `icon_url` in GET responses now returns a publicly accessible URL instead of an internal reference. [Download OpenAPI Spec](/api-reference/v01.19.00/openapi-v01.19.00.json) *** ## v1.18.0 **Release date:** May 5, 2026 ### New Features #### Identity Editable Fields Signing requests now support per-field, per-signer identity editing. When configured, signers see a confirmation dialog before signing where they can review and edit specified identity fields (name, company, title, phone, address). New settings property: | Property | Type | Default | Description | | - | - | - | - | | `identity_editable_fields` | `string[]` or `null` | `null` | Array of identity field keys signers can edit. `null` = disabled. | | `notify_identity_change_email` | `boolean` | `false` | Send an email when a signer changes their identity | Valid field keys: `name`, `company`, `title`, `phone`, `address` Per-recipient override via `identity_editable_fields` on the recipient object: * `null` - use request default * `[]` - disabled for this signer * `["name", "company"]` - custom fields for this signer Example: ```json theme={null} { "settings": { "identity_editable_fields": ["name", "company"] }, "recipients": [ { "name": "John Doe", "email": "john@example.com", "designation": "signer", "order": 1, "identity_editable_fields": null }, { "name": "Jane Smith", "email": "jane@example.com", "designation": "signer", "order": 2, "identity_editable_fields": [] } ] } ``` In this example, John inherits the request default (name + company editable), while Jane has identity editing explicitly disabled. New webhook event: `signing_request.recipient.identity_changed` is now available as a standard webhook event. Subscribe to it in your webhook configuration to receive notifications when a signer modifies their identity during signing. #### Editable Prefilled Data Fields Prefilled data fields can now be optionally editable by signers. Previously, all fields with `prefilled_data` set were automatically locked as read-only. A new `prefilled_editable` property lets you control this behavior. When `prefilled_editable` is `true`, the field value is still auto-populated from recipient data, but the signer can modify it during the signing process. | Property | Type | Default | Description | | - | - | - | - | | `prefilled_editable` | `boolean` | `false` | When true, signers can edit prefilled values | Available on: * Template fields (`PATCH /templates/{id}`, `PUT /templates/{id}`) * Signing request fields (`POST /signing-requests`, `POST /signing-requests/create-and-send`) * Field response objects (`GET /signing-requests/{id}`) Example - create a signing request with an editable prefilled field: ```json theme={null} { "fields": [ { "type": "text", "recipient_id": "temp_1", "page_number": 1, "x": 10, "y": 20, "width": 30, "height": 5, "format_rules": { "prefilledData": "full_name", "prefilledEditable": true } } ] } ``` #### Field Value Source Validation The API now validates that fields use only one value source at a time. Requests that combine `read_only_value`, `prefilled_data`, and explicit `value` on the same field will return a `400 VALIDATION_ERROR`. This enforcement applies to: * `POST /signing-requests` * `POST /signing-requests/create-and-send` * `PATCH /templates/{id}` (field creation and update) * `PUT /templates/{id}` (field upsert) #### Value Precedence When `prefilled_editable` is `true` and both an explicit `value` and `prefilled_data` are set on a field, the explicit value takes precedence over the auto-populated recipient data. ### Migration Guide No breaking changes. New features are opt-in: * Identity editing is disabled by default (`identity_editable_fields: null`). Set it to an array of field keys to enable. * Existing prefilled data fields remain locked (read-only) by default. Set `prefilled_editable: true` in `format_rules` to make them editable. *** ## v1.17.0 **Release date:** April 30, 2026 **[Download OpenAPI Spec](/api-reference/v01.17.00/openapi-v01.17.00.json)** ### New Features #### Color Customization for Signing Experience Six new nullable color fields allow you to customize the signing experience appearance at both company and workspace levels. Colors follow a 3-tier cascade: Firma defaults are used unless overridden at the company level, and company colors are used unless overridden at the workspace level. **New fields on Company (`GET /company`, `PUT /company`, `PATCH /company`):** | Field | Type | Description | | - | - | - | | `color_primary` | `string \| null` | Primary action color for buttons, links, and accents (#rrggbb hex) | | `color_primary_fg` | `string \| null` | Text color on primary-colored elements | | `color_background` | `string \| null` | Page/canvas background color | | `color_foreground` | `string \| null` | Main text color | | `color_card` | `string \| null` | Card/panel background color | | `color_border` | `string \| null` | Border and divider color | **New fields on Workspace Settings (`GET /workspace/{workspace_id}/settings`, `PUT /workspace/{workspace_id}/settings`):** The same six fields are available on workspace settings. Set a value to override the company default, or set `null` to inherit from the company. | Field | Type | Description | | - | - | - | | `color_primary` | `string \| null` | Primary color override. Null to inherit from company. | | `color_primary_fg` | `string \| null` | Primary foreground color override. Null to inherit. | | `color_background` | `string \| null` | Background color override. Null to inherit. | | `color_foreground` | `string \| null` | Foreground/text color override. Null to inherit. | | `color_card` | `string \| null` | Card background color override. Null to inherit. | | `color_border` | `string \| null` | Border color override. Null to inherit. | #### ColorPalette Schema A new `ColorPalette` schema represents the resolved color palette applied to the signing experience. All values are hex color strings (#rrggbb). | Property | Description | | - | - | | `primary` | Primary action color (buttons, links, accents) | | `primary_fg` | Text color on primary-colored elements | | `background` | Page/canvas background color | | `foreground` | Main text color | | `card` | Card/panel background color | | `border` | Border and divider color | ### Migration Guide **From v1.16.0 to v1.17.0:** This is a non-breaking additive change. Existing integrations continue to work without modification. * All six color fields default to `null`, which preserves the existing Firma default appearance. * To customize colors, set hex values on company settings (company-wide defaults) or workspace settings (per-workspace overrides). * The color cascade is: Firma defaults -> Company overrides -> Workspace overrides. Each level only overrides fields that are explicitly set (non-null). *** ## v1.16.0 **Release date:** April 27, 2026 **[Download OpenAPI Spec](/api-reference/v01.16.00/openapi-v01.16.00.json)** ### New Features #### Download Signing Request Document A new endpoint retrieves a download URL for a signing request's document. For completed signing requests, the URL points to the final signed PDF. For in-progress, cancelled, declined, or expired requests, the URL points to a partial snapshot PDF capturing the document state at the time of the last signer action. **Endpoint:** `GET /signing-requests/{id}/download` **Response fields:** | Field | Type | Description | | - | - | - | | `status` | `string` | Signing request status: `finished`, `in_progress`, `cancelled`, `declined`, or `expired` | | `is_partial` | `boolean` | `true` when the document is a partial snapshot (not all signers completed) | | `download_url` | `string` | Pre-signed URL to the PDF document | | `generated_at` | `date-time \| null` | When the document was last generated | | `expires_at` | `date-time` | When the `download_url` expires | **Behavior by status:** | Signing Request State | Response | | - | - | | Not yet sent | `409 Conflict` — download unavailable | | Sent, generation in progress | `503 Service Unavailable` with `Retry-After` header | | Sent, at least one signer completed | `200` with `is_partial: true` | | Cancelled / Declined / Expired | `200` with `is_partial: true` (snapshot of last known state) | | Completed (all signers finished) | `200` with `is_partial: false` | Partial snapshots are generated on demand and cached. The first request may return `503` with a `Retry-After` header while the PDF is being generated — retry after the indicated interval. **URL expiry:** The `download_url` is a pre-signed URL. Fetch the endpoint again after `expires_at` to get a fresh URL. #### Partial Download Watermark Setting A new workspace setting `show_partial_watermark` controls whether partial PDF downloads include an "IN PROGRESS — NOT YET EXECUTED" diagonal watermark. Defaults to disabled. Can be set at the company level (default for all workspaces) or overridden per workspace. **Endpoint:** `PUT /workspace/{workspace_id}/settings` | Field | Type | Description | | - | - | - | | `show_partial_watermark` | `boolean \| null` | `true` to enable watermark, `false` to disable, `null` to inherit company default | ### Migration Guide **From v1.15.3 to v1.16.0:** This is a non-breaking additive change. Existing integrations continue to work without modification. * The download endpoint is purely opt-in. No existing behavior changes. * Call `GET /signing-requests/{id}/download` — handle `503` with `Retry-After` until the document is ready. *** ## v1.15.3 **Release date:** April 27, 2026 **[Download OpenAPI Spec](/api-reference/v01.15.03/openapi-v01.15.03.json)** ### Clean field aliases for GET /signing-requests//fields The fields endpoint now returns clean, consistently-named fields alongside the existing database column names: | New Field | Replaces (Deprecated) | Notes | | - | - | - | | `type` | `field_type` | Field type string | | `recipient_id` | `companies_workspaces_signing_requests_users_id` | Recipient UUID | | `value` | `final_value` | Signed field value | | `position.x` | `x_postion` | Fixes typo in original name | | `position.y` | `y_position` | Nested in position object | | `position.width` | `width` | Nested in position object | | `position.height` | `heigh` | Fixes typo in original name | Additionally, `required`, `read_only`, and `date_signing_default` now return booleans (`true`/`false`) instead of integers (`0`/`1`). All deprecated fields remain in the response for backward compatibility and will be removed in a future major version. ### Migration guide Update your field parsing to use the new names. The deprecated names still work: ```json theme={null} // Before → After field.field_type → field.type field.x_postion → field.position.x field.heigh → field.position.height field.final_value → field.value field.required === 1 → field.required === true ``` *** ## v01.15.02 **Release Date:** April 23, 2026\ [Download OpenAPI Spec](/api-reference/v01.15.02/openapi-v01.15.02.json) ### Bug Fixes #### Create-and-Send Field Types Expanded The `create-and-send` endpoint's `fields` schema was missing several field types that were already supported by the backend and available via `anchor_tags`. The following types have been added to the `fields.type` enum: * `dropdown` (with `dropdown_options` property) * `radio_buttons` * `text_area` * `url` * `file` These types were already accepted by the API and fully functional. This update aligns the OpenAPI specification with the actual backend behavior. **Affected endpoint:** * `POST /signing-requests/create-and-send` -- `fields[].type` enum *** ## v01.15.01 **Release Date:** April 17, 2026\ [Download OpenAPI Spec](/api-reference/v01.15.01/openapi-v01.15.01.json) ### Bug Fixes #### Language Enum Updated Added missing `ru` (Russian) and `pl` (Polish) language codes to all language enum fields across the API. These languages were already supported by the platform but were missing from the OpenAPI specification, preventing API users from setting them via documented endpoints. **Affected endpoints:** * `PUT /workspace/{workspace_id}/settings` — `language` field * `PUT /company` and `PATCH /company` — `language` field * `GET /email-templates/defaults` — `language` query parameter *** ## v01.15.00 **Release Date:** April 16, 2026\ [Download OpenAPI Spec](/api-reference/v01.15.00/openapi-v01.15.00.json) ### New Features #### Workspace-Scoped Webhooks Webhooks can now be scoped to individual workspaces instead of applying company-wide. This allows different workspaces to have independent webhook configurations and signing secrets. * **`workspace_id` parameter on webhook creation** — Pass an optional `workspace_id` when creating a webhook to scope it to a specific workspace. Omitting it creates a company-level webhook (existing behavior). * **`workspace_id` filter on webhook listing** — Filter the webhook list by workspace. When omitted, only company-level webhooks are returned. * **`ignore_company_webhooks` workspace field** — Workspaces can opt out of receiving company-level webhook events by setting this flag to `true`. #### Workspace Webhook Secret Management Two new endpoints for managing workspace-level webhook signing secrets: * **`POST /workspaces/{id}/webhooks/rotate-secret`** — Generates a new webhook signing secret for the workspace. The old secret remains valid for a 7-day grace period. * **`GET /workspaces/{id}/webhooks/secret-status`** — Returns the status of the workspace webhook secret including creation date, rotation date, and grace period information. #### Workspace Response Webhook Fields The workspace GET response now includes webhook-related fields: * `webhook_enabled` — Whether workspace-level webhooks are enabled * `webhook_secret` — The current webhook signing secret * `webhook_secret_created_at` — When the current secret was created * `webhook_secret_rotated_at` — When the secret was last rotated * `ignore_company_webhooks` — Whether the workspace ignores company-level webhook events ### Changes * Added optional `workspace_id` parameter to `POST /webhooks` and `GET /webhooks` * Added 5 new fields to the `Workspace` schema: `webhook_enabled`, `webhook_secret`, `webhook_secret_created_at`, `webhook_secret_rotated_at`, `ignore_company_webhooks` * Added 2 new endpoints: `POST /workspaces/{id}/webhooks/rotate-secret`, `GET /workspaces/{id}/webhooks/secret-status` * SSRF protection applied to webhook URL validation ### Migration Guide **From v01.14.00 to v01.15.00:** This is a non-breaking additive change. Existing integrations continue to work without modification. * Company-level webhooks remain the default. The `workspace_id` parameter is optional on both create and list endpoints. * Existing webhooks are unaffected. The new workspace fields on the workspace response default to `webhook_enabled: false` and `ignore_company_webhooks: false`. * To adopt workspace webhooks, create a webhook with a `workspace_id`, then use the workspace secret rotation endpoint to generate a signing secret for that workspace. *** ## v01.14.00 **Release Date:** April 12, 2026\ [Download OpenAPI Spec](/api-reference/v01.14.00/openapi-v01.14.00.json) ### New Features #### Recipient Designations The `designation` field on signing request and template recipients now accepts three values: `"Signer"`, `"Approver"`, and `"CC"`. * **Signer** — Signs the document (existing behavior, remains the default) * **Approver** — Approves the document using approval-specific fields. Approvers do not sign but must complete all assigned approval fields before the request can finish. * **CC** — Receives a completed copy of the signed document. CC recipients cannot sign or have fields assigned to them. At least one Signer is still required per signing request. #### Approval Field Types Three new field types for Approver recipients: * `approval_signature` — An approval stamp placed by the approver * `approval_checkmark` — An approval checkbox * `approval_date` — Auto-filled date when the approver approves These field types can only be assigned to recipients with `"Approver"` designation. #### CC Recipients CC recipients are now supported across all create and update endpoints for both templates and signing requests. CC recipients appear in the recipient list but have no fields and do not participate in the signing or approval flow. ### Changes * Expanded `designation` enum from `["Signer"]` to `["Signer", "Approver", "CC"]` across 7 schemas * Added `approval_signature`, `approval_checkmark`, and `approval_date` to field type enums across 10 schemas ### Migration Guide **From v01.13.00 to v01.14.00:** This is a non-breaking additive change. Existing integrations continue to work without modification. * New `designation` values are opt-in. Omitting the `designation` field defaults to `"Signer"`, preserving existing behavior. * New field types are additive. All existing field type values remain valid and unchanged. * If you integrate approval workflows, assign `"Approver"` designation to the relevant recipients and use the `approval_*` field types for their fields. *** ## v01.13.00 **Release Date:** April 12, 2026\ [Download OpenAPI Spec](/api-reference/v01.13.00/openapi-v01.13.00.json) ### New Features #### Stamp Field Type New `stamp` field type for placing pre-configured image stamps or company seals on documents. Stamps are read-only PNG images set by the template or signing request creator, rendered at signing time with no signer interaction, and embedded in the final PDF. ### Changes * Added `stamp` to field type enums across all field-related schemas (templates, signing requests, create-and-send) * Updated field type descriptions to document stamp behavior ### Migration Guide **From v01.12.01 to v01.13.00:** This is a non-breaking additive change. Existing integrations are not affected. * If you create fields via the API, you can now use `"stamp"` as a field type. Stamp fields should be set as `read_only: true` with the stamp image as a PNG data URL in `read_only_value`. * Stamp fields are also supported in anchor tags. When using anchor tags, the stamp image should be provided via `read_only_value` as a PNG data URL. *** ## v01.12.01 **Release Date:** April 2, 2026\ [Download OpenAPI Spec](/api-reference/v01.12.01/openapi-v01.12.01.json) ### New Features #### `variable_defined_name` on Field Responses All API endpoints that return template or signing request fields now include a `variable_defined_name` property. This is the human-readable `field_name` from the custom field definition that the field is linked to. Previously, fields linked to custom field definitions only exposed `variable_name`, which contains the internal identifier (e.g. `custom_template_a1b2c3d4-...`). The new `variable_defined_name` returns the name you originally defined (e.g. `artist_name`), making it straightforward to map fields in your integration without cross-referencing the custom fields endpoint. **Affected endpoints:** * `GET /templates` (list and single) * `GET /templates/{id}/fields` * `POST /templates/{id}/replace-document` * `GET /signing-requests` (list) * `GET /signing-requests/{id}/fields` * `GET /documents` (legacy) **Example response:** ```json theme={null} { "variable_name": "custom_template_a1b2c3d4-...", "variable_defined_name": "artist_name" } ``` For fields not linked to a custom field definition, `variable_defined_name` is `null`. #### `variable_defined_name` as Input for Field Matching When creating a signing request from a template via `POST /signing-requests/create-and-send`, you can now use `variable_defined_name` to target fields for overrides instead of the internal `variable_name`: ```json theme={null} { "fields": [ { "variable_defined_name": "artist_name", "read_only_value": "John Smith" } ] } ``` Both `variable_name` and `variable_defined_name` are accepted. `variable_name` takes precedence when both are provided. ### No Breaking Changes This is a non-breaking patch release. All existing API behavior is unchanged. *** ## v01.12.00 **Release Date:** March 26, 2026\ [Download OpenAPI Spec](/api-reference/v01.12.00/openapi-v01.12.00.json) ### New Features #### MCP Server Integration Firma now supports the [Model Context Protocol](https://modelcontextprotocol.io) (MCP), allowing AI assistants like Claude to interact with the Firma API through natural language. The MCP server exposes Firma's core API operations as tools that AI assistants can discover and call directly. **Server URL:** `https://mcp.firma.dev/mcp` **Authentication:** * API key authentication via the `Authorization` header * OAuth 2.1 with PKCE for browser-based clients (Claude.ai, Claude Desktop) **Available tool categories:** * Workspaces — list, create, get, update * Templates — full CRUD, duplicate, view fields/users/reminders * Signing Requests — full CRUD, send, cancel, resend, audit trails * Webhooks — full CRUD, test delivery * Company — view and update company information **Transport:** [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) — compatible with Claude Code, Claude Desktop, Claude.ai, and any MCP client supporting the standard. See the [MCP Integration guide](/guides/mcp) for setup instructions and usage examples. ### No Breaking Changes This release adds no new API endpoints or schema changes. The OpenAPI specification is unchanged from v1.11.0. *** ## v01.11.00 **Release Date:** March 19, 2026\ [Download OpenAPI Spec](/api-reference/v01.11.00/openapi-v01.11.00.json) ### New Features #### File Upload Field Type A new `file` field type allows signers to upload file attachments. Supported across signing requests and templates. * Signers can upload images (JPG, PNG) and/or PDF files depending on configuration * Files are validated by magic bytes, not just file extension * Maximum file size: 10MB **Configuration via `format_rules`:** ```json theme={null} { "type": "file", "format_rules": { "acceptedFileTypes": "image_and_pdf" } } ``` | `acceptedFileTypes` Value | Accepted Formats | | - | - | | `image_and_pdf` (default) | JPG, PNG, PDF | | `image` | JPG, PNG only | | `pdf` | PDF only | #### Anchor Tags for Automatic Field Placement A new `anchor_tags` array on signing request creation enables automatic field placement using text markers embedded in PDF documents (e.g., `{{SIGN_HERE}}`). The anchor text is removed from the PDF after processing by default. * Up to 100 anchor tags per request * Only available for document-based creation (not template-based) * Fields created from anchor tags are added alongside any manually specified fields **Key properties:** | Property | Type | Default | Description | | - | - | - | - | | `anchor_string` | `string` (required) | — | Text to search for in the PDF | | `type` | `string` (required) | — | Field type to place at anchor location | | `recipient_id` | `string\|integer` (required) | — | Recipient assigned to the field | | `x_offset` / `y_offset` | `number` | `0` | Offset from anchor position | | `offset_units` | `percent` \| `pixels` | `percent` | Unit type for offsets | | `width` / `height` | `number` | varies by type | Field dimensions as percentage of page | | `occurrence` | `integer` | `0` (all) | Which occurrence to target (0 = all) | | `ignore_if_not_present` | `boolean` | `false` | Skip without error if anchor not found | | `remove_anchor_text` | `boolean` | `true` | Remove anchor text from PDF | | `case_sensitive` | `boolean` | `false` | Case-sensitive matching | | `match_whole_word` | `boolean` | `true` | Match whole words only | **Example:** ```json theme={null} { "anchor_tags": [ { "anchor_string": "{{SIGN_HERE}}", "type": "signature", "recipient_id": "temp_1", "width": 25, "height": 5 } ] } ``` #### Field Background Color All field types now support a `background_color` property — a hex color string (e.g., `#FFFDE7`, `#fff`) useful for highlighting fields that need attention. Available on both standard fields and anchor tag definitions. ### Schema Changes #### New Schemas | Schema | Description | | - | - | | `FileFormatRules` | Formatting rules for file upload fields (`acceptedFileTypes` enum) | | `AnchorTag` | Anchor tag definition for automatic field placement (\~25 properties) | #### Field / TemplateField / SigningRequestField | Added Field | Type | Description | | - | - | - | | `background_color` | `string \| null` | Hex color for field background (e.g., `#FFFDE7`) | #### Field Type Enum * Added `file` to the list of accepted field types #### `format_rules` * Now accepts `FileFormatRules` (with `acceptedFileTypes`) in addition to `DateFormatRules` and URL display text #### Signing Request Creation | Added Field | Type | Description | | - | - | - | | `anchor_tags` | `AnchorTag[]` | Anchor tags for automatic field placement (max 100) | ### Breaking Changes #### `use_signing_order` Default Changed The `use_signing_order` setting default has changed from `false` to `true`. This means new signing requests will enforce signing order by default. When `true`, signers receive the document in sequence based on their `order` field. Set `use_signing_order: false` explicitly to preserve the previous behavior where all signers receive the document simultaneously. ### Migration Guide If you rely on the previous behavior where all signers receive documents simultaneously, explicitly set `use_signing_order: false` when creating signing requests. Review any integration code that creates signing requests without specifying this field. *** ## v01.10.00 **Release Date:** March 10, 2026\ [Download OpenAPI Spec](/api-reference/v01.10.00/openapi-v01.10.00.json) ### New Features #### Conditional Field Logic Fields now support conditional rules for both **required** status and **visibility**. Two new nullable properties are available on field objects: * `required_conditions` — When set, overrides the static `required` flag. The field is required only when the conditions evaluate to true based on other field values. * `visibility_conditions` — When set, the field is hidden unless the conditions evaluate to true. Hidden fields are not validated on submission. Conditions use a nested group structure with logical operators: | Schema | Description | | - | - | | `ConditionSet` | Top-level container with `logic` (`and`/`or`) operator and an array of `groups` | | `ConditionGroup` | Contains an array of `conditions`, combined using the opposite of the parent's logic operator | | `Condition` | Evaluates a single field by `field_id`, `operator`, and optional `value` | **Supported operators:** `is_filled`, `is_empty`, `equals`, `not_equals`, `contains`, `not_contains`, `greater_than`, `less_than`, `greater_than_or_equal`, `less_than_or_equal` **Example — Make a field required only when another field is filled:** ```json theme={null} { "required_conditions": { "logic": "and", "groups": [ { "conditions": [ { "field_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "operator": "is_filled" } ] } ] } } ``` #### DOCX Document Support All document upload endpoints now accept **DOCX files** in addition to PDF. DOCX files are automatically converted to PDF on upload. This applies to: * Template creation (`POST /templates`) * Template document replacement (`POST /templates/{id}/replace-document`) * Signing request creation (`POST /signing-requests`) * Template/signing request updates (PUT and PATCH) #### Audit Trail Endpoint A new endpoint retrieves the complete audit trail for a signing request, combining admin actions (created, edited, sent, cancelled) and signer actions (viewed, signed, declined, downloaded). Events are sorted chronologically. **Endpoint:** `GET /signing-requests/{id}/audit` **Response fields:** | Field | Type | Description | | - | - | - | | `id` | `uuid` | Audit event ID | | `timestamp` | `date-time` | When the event occurred | | `source` | `string` | `admin` or `signer` | | `event` | `string` | Event type identifier | | `description` | `string` | Human-readable event description | | `actor` | `object \| null` | Who performed the action | | `ip_address` | `string \| null` | IP address (signer events only) | | `details` | `object \| null` | Additional event-specific metadata | #### Signature Frame Control A new `show_signature_frame` setting controls whether a visual frame with Signature ID is rendered around signatures in completed PDFs. Available at company, workspace, and signing request levels. | Level | Field | Behavior | | - | - | - | | Company | `show_signature_frame` | Sets the default (enabled by default) | | Workspace Settings | `show_signature_frame` | Overrides company default; `null` inherits | | Signing Request Settings | `show_signature_frame` | Overrides workspace default; `null` inherits | **Values:** * `true` — Show signature frame with Signature ID * `false` — Hide signature frame * `null` — Inherit from parent level #### Force Remove Conditions on User Deletion When deleting recipients whose fields are referenced by conditions in other fields, a new `force_remove_conditions` boolean parameter controls behavior: * `false` (default) — The request is rejected with an error listing the dependent fields * `true` — Automatically removes the condition references and proceeds with deletion This applies to both template and signing request user deletion operations. ### Schema Changes #### Field | Added Field | Type | Description | | - | - | - | | `required_conditions` | `ConditionSet \| null` | Conditional rules for when this field is required | | `visibility_conditions` | `ConditionSet \| null` | Conditional rules for when this field is visible | #### New Schemas | Schema | Description | | - | - | | `ConditionSet` | A set of condition groups with nested logic (contains `logic` and `groups`) | | `ConditionGroup` | A group of conditions (contains `conditions` array) | | `Condition` | A single condition evaluating a field's value (contains `field_id`, `operator`, `value`) | #### SigningRequestSettings / WorkspaceSettings / Company | Added Field | Type | Description | | - | - | - | | `show_signature_frame` | `boolean \| null` | Whether to render signature frames in completed PDFs | #### Template & Signing Request User Deletion | Added Parameter | Type | Description | | - | - | - | | `force_remove_conditions` | `boolean` | Force-remove condition references when deleting users whose fields are referenced (default: `false`) | ### New Endpoints | Endpoint | Method | Description | | - | - | - | | `/signing-requests/{id}/audit` | GET | Get signing request audit trail | ### Migration Guide No breaking changes. All new features are additive: * Conditional field properties default to `null` (no conditions) * `show_signature_frame` defaults to `null` (inherit, which is enabled by default) * `force_remove_conditions` defaults to `false` (preserving existing rejection behavior) * DOCX support is transparent — existing PDF uploads continue to work unchanged * The audit trail endpoint is purely additive *** ## v01.09.00 **Release Date:** March 5, 2026\ [Download OpenAPI Spec](https://github.com/Firma-esign/firma-docs/raw/4b2cbdb9cd36b46fcbd1cf06f8e079533487c45d/api-reference/v01.09.00/openapi-v01.09.00.json) ### New Features #### OTP Email Verification A new `require_otp_verification` setting allows you to require signers to verify their email address with a one-time code before accessing signing documents. This adds an extra layer of identity verification. **Where it's available:** | Level | Field | Behavior | | - | - | - | | Company | `require_otp_verification` | Sets the default for all workspaces | | Workspace Settings | `require_otp_verification` | Overrides company default; `null` inherits from company | | Signing Request Settings | `require_otp_verification` | Overrides workspace default; `null` inherits from workspace | **Values:** * `true` — Require OTP verification before document access * `false` — Do not require OTP verification * `null` — Inherit from parent level (workspace → company, signing request → workspace) **Example — Enable OTP at workspace level:** ```json theme={null} PATCH /workspace-settings/{workspace_id} { "settings": { "require_otp_verification": true } } ``` This route was part of the historical release note. The current endpoint is `PUT /workspace/{workspace_id}/settings`. **Example — Override for a specific signing request:** ```json theme={null} PATCH /signing-requests/{id} { "settings": { "require_otp_verification": false } } ``` #### Replace Template Document A new endpoint allows replacing a template's PDF document while preserving all field placements. This is useful when you need to update a document (e.g., fixing a typo) without recreating all the field configurations. **Endpoint:** `POST /templates/{id}/replace-document` **Requirements:** * The replacement document must have the **same page count** as the original * Page dimensions must match within **1pt tolerance** * Document must be provided as a **base64-encoded PDF** **Example:** ```json theme={null} POST /templates/{template_id}/replace-document { "document": "JVBERi0xLjQKJeLjz9..." } ``` **Error cases:** | Error | Cause | | - | - | | 400 | Page count mismatch | | 400 | Page dimension mismatch (exceeds 1pt tolerance) | | 400 | Invalid or corrupt PDF document | ### Schema Changes #### SigningRequestSettings | Added Field | Type | Description | | - | - | - | | `require_otp_verification` | `boolean \| null` | Require OTP email verification; `null` inherits from workspace | #### WorkspaceSettings | Added Field | Type | Description | | - | - | - | | `require_otp_verification` | `boolean \| null` | Require OTP email verification; `null` inherits from company | ### New Endpoints | Endpoint | Method | Description | | - | - | - | | `/templates/{id}/replace-document` | POST | Replace template PDF while preserving fields | ### Migration Guide No breaking changes. The `require_otp_verification` field defaults to `null` (inherit), so existing behavior is unchanged. The replace document endpoint is purely additive. *** ## v01.08.00 **Release Date:** March 3, 2026\ [Download OpenAPI Spec](https://github.com/Firma-esign/firma-docs/raw/4b2cbdb9cd36b46fcbd1cf06f8e079533487c45d/api-reference/v01.08.00/openapi-v01.08.00.json) ### New Features #### Email Templates API A new **Email Templates** endpoint group allows you to customize the notification emails sent during the signing process. Templates can be configured at both company and workspace levels, with workspace-level templates overriding company-level defaults. **Supported email types:** | Email Type | When Sent | | - | - | | `signing_invite` | When a signing request is sent to a recipient | | `next_signer` | When it's the next signer's turn in a signing order | | `signing_expired` | When a signing request expires | | `signing_cancelled` | When a signing request is cancelled | | `signing_declined` | When a signer declines | **New endpoints:** | Endpoint | Description | | - | - | | `GET /workspace/{workspace_id}/email-templates` | List workspace email templates | | `GET /workspace/{workspace_id}/email-templates/{email_type}` | Get a specific workspace email template | | `PUT /workspace/{workspace_id}/email-templates/{email_type}` | Create or update a workspace email template | | `DELETE /workspace/{workspace_id}/email-templates/{email_type}` | Delete a workspace email template | | `GET /company/email-templates` | List company email templates | | `PUT /company/email-templates/{email_type}` | Create or update a company email template | | `DELETE /company/email-templates/{email_type}` | Delete a company email template | | `GET /email-templates/defaults/{language}` | Get built-in default templates for a language | | `GET /email-templates/placeholders` | Get available template placeholders | **Template hierarchy:** Workspace templates → Company templates → Built-in defaults. Templates support HTML bodies with placeholders like `{{signing_link}}`, `{{signer_name}}`, etc. A warning is returned if the body does not contain `{{signing_link}}`. **Example:** ```json theme={null} PUT /workspace/{workspace_id}/email-templates/signing_request { "subject": "Please sign: {{document_name}}", "body": "

Hi {{signer_name}},

Please sign using this link: {{signing_link}}

" } ``` #### Language Support New `language` field added to both the `Company` and `WorkspaceSettings` schemas to support multi-language email templates. | Field | Type | Values | Default | | - | - | - | - | | `language` | `string` | `en`, `es`, `it`, `pt`, `fr`, `de`, `el` | `en` | ### Schema Changes #### Timestamp Corrections Timestamp field names have been corrected in the spec to match actual API responses: | Schema | Previous Spec | Corrected | | - | - | - | | `Company` | `date_created`, `date_changed` | `created_date`, `updated_date` | | `Workspace` | `date_created`, `date_changed` | `created_date`, `updated_date` | | `Template` | `date_created`, `date_changed` | `created_date`, `updated_date` | | `Reminder` | `date_created`, `date_changed` | `created_at`, `updated_at` | | `Webhook` | `date_created`, `date_changed` | `created_at`, `updated_at` | > [!NOTE] > > These are spec corrections only — the API responses have not changed. If your integration already handles the actual API response fields, no code changes are needed. #### Company Schema | Change | Details | | - | - | | Corrected | `company_name` → `name` (matches actual API response) | | Added | `language` (enum, default `"en"`) | | Corrected | `date_created`/`date_changed` → `created_date`/`updated_date` | #### Template Schema (Expanded) The `Template` schema now includes inline recipients and fields when fetching a single template (`GET /templates/{id}`), reducing the need for separate API calls. | Added Field | Type | Description | | - | - | - | | `page_count` | `integer` | Number of pages in the document | | `expiration_hours` | `integer` | Hours until signing requests expire (default: 168) | | `settings` | `SigningRequestSettings` | Template-level settings | | `recipients` | `array` | Template recipients (inline) | | `fields` | `array` | Template fields (inline) | #### Signing Request Response Shapes The monolithic `SigningRequest` schema has been split into endpoint-specific response shapes: | Schema | Used By | | - | - | | `SigningRequestListItem` | `GET /signing-requests` (list) | | `SigningRequestCreateResponse` | `POST /signing-requests`, `POST /documents` | | `SigningRequestDetail` | `GET /signing-requests/{id}` (unchanged from v1.7.0) | The list and create responses now include inline `recipients` and `fields` arrays, and use standardized timestamp fields (`created_date`, `sent_date`, `finished_date`, etc.). #### SigningRequestSettings `use_signing_order` (boolean, default `false`) is now included in the settings object. Previously this was only available as a deprecated top-level integer field. #### Reminder Schema | Added Field | Description | | - | - | | `recipient_id` | Specific recipient to send reminder to (signing request context) | | `sent_on` | Timestamp when the reminder was actually sent | #### Webhook Schema | Added Field | Type | Description | | - | - | - | | `description` | `string` | Webhook description | | `consecutive_failures` | `integer` | Number of consecutive delivery failures | | `auto_disabled_at` | `datetime` | When the webhook was auto-disabled due to failures | #### SendSigningRequestResponse (Corrected) The spec now accurately reflects the actual response shape: | Previous Spec | Corrected | | - | - | | `success` (boolean) | `message` (string) | | `sentTo` (email) | `signing_request_id` (uuid) | | `sentAt` (datetime) | `recipients_notified` (integer), `sent_date`, `expires_at` | #### WorkspaceSettings | Added Field | Description | | - | - | | `name` | Workspace name | | `language` | Workspace language for email templates | *** ## v01.07.00 **Release Date:** February 25, 2026\ [Download OpenAPI Spec](https://github.com/Firma-esign/firma-docs/raw/4b2cbdb9cd36b46fcbd1cf06f8e079533487c45d/api-reference/v01.07.00/openapi-v01.07.00.json) ### New Features #### Hand-Drawn Only Signatures A new `hand_drawn_only` setting allows you to require signers to hand-draw their signatures instead of using typed or font-based options. **Added to `SigningRequestSettings` schema:** | Field | Type | Default | Description | | - | - | - | - | | `hand_drawn_only` | `boolean` | `false` | When enabled, signers can only hand-draw their signatures | This setting is available in all places where signing request settings are configured: * Creating signing requests (`POST /signing-requests`) * Creating and sending signing requests (`POST /signing-requests/create-and-send`) * Comprehensive updates (`PUT /signing-requests/{id}`) * Partial updates (`PATCH /signing-requests/{id}`) * Template creation and updates **Example:** ```json theme={null} { "settings": { "hand_drawn_only": true, "use_signing_order": false, "allow_download": true } } ``` ### Schema Changes #### Deprecated Legacy Settings Fields The `SigningRequest` schema now includes deprecated top-level integer fields that mirror the boolean `settings` object. These exist for backward compatibility and should not be used in new integrations: | Deprecated Field | Type | Use Instead | | - | - | - | | `use_signing_order` | `integer` (0/1) | `settings.use_signing_order` | | `allow_download` | `integer` (0/1) | `settings.allow_download` | | `allow_editing_before_sending` | `integer` (0/1) | `settings.allow_editing_before_sending` | | `hand_drawn_only` | `integer` (0/1) | `settings.hand_drawn_only` | | `attach_pdf_on_finish` | `integer` (0/1) | `settings.attach_pdf_on_finish` | > [!WARNING] > > These deprecated fields use `0`/`1` integers instead of booleans. Always use the `settings` object for new integrations. ### Migration Guide No breaking changes. The `hand_drawn_only` setting defaults to `false`, preserving existing behavior. Deprecated top-level integer fields are purely additive. *** ## v01.06.00 **Release Date:** February 12, 2026\ [Download OpenAPI Spec](https://github.com/Firma-esign/firma-docs/raw/4b2cbdb9cd36b46fcbd1cf06f8e079533487c45d/api-reference/v01.06.00/openapi-v01.06.00.json) ### New Features #### Split PDF Downloads Completed signing requests now support downloading the signed document and audit trail certificate as separate files, in addition to the existing combined download. **New fields on `SigningRequest` schema:** | Field | Description | | - | - | | `document_only_download_url` | Signed URL to download just the signed document (without certificate/audit trail pages) | | `document_only_download_error` | Error indicator if the document-only file is inaccessible | | `certificate_only_download_url` | Signed URL to download just the certificate/audit trail pages | | `certificate_only_download_error` | Error indicator if the certificate-only file is inaccessible | **Notes:** * Only available when the split PDF feature has been applied to the signing request * URLs expire after 1 hour — fetch the signing request again for a fresh URL * Error fields return `"file_not_accessible"` if the file path exists but the file is missing **Example Response (partial):** ```json theme={null} { "id": "sr-uuid", "status": "finished", "final_document_download_url": "https://storage.example.com/combined.pdf", "document_only_download_url": "https://storage.example.com/document.pdf", "document_only_download_error": null, "certificate_only_download_url": "https://storage.example.com/certificate.pdf", "certificate_only_download_error": null } ``` ### Schema Changes #### Field Type Normalization Field type enums now accept both original and normalized forms across all schemas. The API normalizes inputs automatically: | Input | Normalized To | | - | - | | `initials` | `initial` | | `textarea` | `text_area` | **Affected schemas:** | Schema | Change | | - | - | | `TemplateField.type` | `initials` → `initial`; enum now includes both `initial` and `initials` variants | | `Field.type` (signing requests) | Now accepts `initial`/`initials` and `textarea`/`text_area` | | `SigningRequestField.field_type` | `initials` → `initial`, `textarea` → `text_area` | | Template PATCH `field.type` | Now accepts both forms with normalization | | Signing Request PATCH `field.type` | Now accepts both forms with normalization | ### Migration Guide No breaking changes. Both old and new field type names are accepted. The split PDF download fields are purely additive. *** ## v01.05.00 **Release Date:** February 5, 2026\ [Download OpenAPI Spec](https://github.com/Firma-esign/firma-docs/raw/4b2cbdb9cd36b46fcbd1cf06f8e079533487c45d/api-reference/v01.05.00/openapi-v01.05.00.json) ### New Features #### Email Validation Warnings Signing request endpoints now return non-blocking warnings when recipient email addresses have potentially invalid formats (e.g., unusual TLDs, missing dots). These warnings help identify potential delivery issues without failing the request. **Affected Endpoints:** | Endpoint | Response Field | | - | - | | `POST /signing-requests` (create) | `warnings` array | | `PATCH /signing-requests/{id}` (partial update) | `warning` field | | `PUT /signing-requests/{id}` (comprehensive update) | `warnings` array | **Example Response:** ```json theme={null} { "signing_request": { ... }, "warnings": [ "Recipient email 'user@compny' may have an invalid format" ] } ``` **Notes:** * Warnings are non-blocking — the request still succeeds * Useful for flagging potential typos or invalid email domains * Check the `warnings` or `warning` field in the response to surface these to users ### Migration Guide No breaking changes. Email validation warnings are purely additive and do not affect existing integrations. *** ## v01.04.00 **Release Date:** January 31, 2026\ [Download OpenAPI Spec](https://github.com/Firma-esign/firma-docs/raw/4b2cbdb9cd36b46fcbd1cf06f8e079533487c45d/api-reference/v01.04.00/openapi-v01.04.00.json) ### New Features #### New Field Types Three new field types have been added: | Field Type | Description | | - | - | | `textarea` | Multi-line text input field | | `url` | Clickable link field (automatically read-only) | | `radio_buttons` | Radio button group (renamed from `radio`) | **URL Field Details:** * Automatically set to `read_only: true` * Use `read_only_value` to set the target URL * Use `format_rules.urlDisplayText` to customize the display text Example: ```json theme={null} { "field": { "type": "url", "position": { "x": 100, "y": 200, "width": 150, "height": 30 }, "page_number": 1, "read_only_value": "https://example.com/terms", "format_rules": { "urlDisplayText": "View Terms & Conditions" } } } ``` #### PATCH Field Operations Both Template and Signing Request PATCH endpoints now support single-field operations: **Template PATCH** (`PATCH /templates/{id}`): * Can now update properties, a single user, **OR a single field** * Include `field` object in request body * Include `field.id` to update existing field, omit to create new **Signing Request PATCH** (`PATCH /signing-requests/{id}`): * Can now update properties, a single recipient, **OR a single field** * Same field object structure as templates Example - Create new field: ```json theme={null} { "field": { "type": "text", "x": 100, "y": 200, "width": 200, "height": 30, "page": 1, "required": true, "assigned_to_user_id": "user-uuid" } } ``` Example - Update existing field: ```json theme={null} { "field": { "id": "field-uuid", "x": 120, "y": 220 } } ``` #### Template User Matching for Recipients When creating signing requests from templates, you can now use `template_user_id` to explicitly match recipients to template users: ```json theme={null} { "template_id": "template-uuid", "recipients": [ { "template_user_id": "template-user-uuid", "first_name": "Jane", "last_name": "Smith", "email": "jane@example.com" } ] } ``` * `template_user_id` is the preferred method for matching * `order` remains available as a fallback ### Schema Changes #### Field Type Enum Updated from: ``` ["text", "signature", "date", "checkbox", "initials", "dropdown", "radio"] ``` To: ``` ["text", "signature", "date", "checkbox", "initials", "dropdown", "radio_buttons", "textarea", "url"] ``` #### Recipient Schema * Signing request creation now uses shared `Recipient` schema reference * Added `template_user_id` property for explicit template user matching *** ## v01.03.00 [Download OpenAPI Spec](https://github.com/Firma-esign/firma-docs/raw/4b2cbdb9cd36b46fcbd1cf06f8e079533487c45d/api-reference/v01.03.00/openapi-v01.03.00.json) ### New Features #### Email Domains API A complete new API category for configuring custom email domains for sending signing request emails: **New Endpoints:** * `GET /company/domains` - List all company email domains * `POST /company/domains` - Add a new email domain * `GET /company/domains/{id}` - Get domain details * `DELETE /company/domains/{id}` - Delete a domain * `POST /company/domains/{id}/verify-ownership` - Verify domain ownership via TXT record * `POST /company/domains/{id}/finalize` - Complete domain setup with email provider * `POST /company/domains/{id}/verify-dns` - Verify SPF, DKIM, DMARC records * `POST /company/domains/{id}/set-primary` - Set primary sending domain **New Schemas:** * `Domain` - Email domain configuration with verification status * `DomainDnsRecord` - DNS record details for domain verification #### Credit Cost Tracking * Added `credit_cost` field to `Template` schema - Number of credits consumed when sending * Added `credit_cost` field to `SigningRequest` schema - Credits consumed when sent #### Signing Request Declined Status * Added `declined` to `SigningRequest.status` enum values * Added `date_declined` field to `SigningRequest` schema ### Schema Improvements #### Template Fields * Added `date_default` field for setting default date values (ISO 8601 format) * Enhanced `multi_group_id` description: explains mutually exclusive field grouping for checkboxes/radio buttons * Added clarification that `page_number` is 1-indexed and must not exceed document page count #### Signing Request Fields * Enhanced `multi_group_id` description with same improvements as template fields *** ## v01.02.00 [Download OpenAPI Spec](https://github.com/Firma-esign/firma-docs/raw/4b2cbdb9cd36b46fcbd1cf06f8e079533487c45d/api-reference/v01.02.00/openapi-v01.02.00.json) ### Enhanced User Information #### TemplateUser Schema Enhancements Added comprehensive recipient information fields: * `first_name` - Recipient first name * `last_name` - Recipient last name * `phone_number` - Contact phone number * `street_address` - Street address * `city` - City * `state_province` - State or province * `postal_code` - Postal/ZIP code * `country` - Country * `title` - Job title * `company` - Company name #### Ready-to-Send Indicators New fields to help determine recipient readiness: * `required_fields` - List of required recipient data fields based on template configuration * `missing_fields` - List of required fields that are currently empty * `required_read_only_fields` - Read-only fields needing pre-filled values * `ready_to_send` - Boolean indicating if recipient has all required data #### SigningRequestUser Schema Enhancements Same enhancements as TemplateUser, plus: * `declined_on` - Timestamp when recipient declined to sign * `decline_reason` - Reason provided by recipient for declining * `custom_fields` - Custom field values for the recipient * `has_value` property in `required_read_only_fields` - Indicates if read-only field has a value ### Response Format Changes Template users endpoint (`GET /templates/{id}/users`) now returns: ```json theme={null} { "results": [ { "id": "uuid", "name": "John Doe", "first_name": "John", "last_name": "Doe", "email": "john@example.com", "designation": "Signer", "order": 1, "required_fields": ["email", "first_name"], "missing_fields": [], "ready_to_send": true } ] } ``` *** ## v01.01.00 [Download OpenAPI Spec](https://github.com/Firma-esign/firma-docs/raw/4b2cbdb9cd36b46fcbd1cf06f8e079533487c45d/api-reference/v01.01.00/openapi-v01.01.00.json) ### New Endpoints #### Template Management Full CRUD support for templates: * `POST /templates` - Create a new template with base64-encoded PDF * `PATCH /templates/{id}` - Partial update (properties OR single user) * `PUT /templates/{id}` - Comprehensive update (properties, users, fields, reminders) * `DELETE /templates/{id}` - Soft delete a template #### Template Sub-Resources * `GET /templates/{id}/users` - Get all template recipients * `GET /templates/{id}/fields` - Get all template fields * `GET /templates/{id}/reminders` - Get all template reminders #### API Key Management New endpoints for workspace API key lifecycle: * `POST /workspaces/{id}/api-key/regenerate` - Generate new API key with 24-hour grace period * `POST /workspaces/{id}/api-key/expire` - Immediately expire pending keys ### Enhanced Operations #### Comprehensive Updates The PUT endpoint for templates supports: * `template_properties` - Update metadata and settings * `users` - Upsert users (include id to update, omit to create) * `deleted_users` - Delete users with field handling strategy (`delete` or `reassign`) * `fields` - Upsert fields * `reminders` - Upsert reminders Example request: ```json theme={null} { "template_properties": { "name": "Updated Template", "settings": { "allow_editing_before_sending": true } }, "users": [ { "id": "existing-uuid", "email": "updated@example.com" }, { "first_name": "New", "last_name": "Signer", "email": "new@example.com", "designation": "Signer", "order": 2 } ], "deleted_users": [ { "user_id": "user-to-delete", "field_action": "reassign", "reassign_to_user_id": "existing-uuid" } ] } ``` *** ## v01.00.02 [Download OpenAPI Spec](https://github.com/Firma-esign/firma-docs/raw/4b2cbdb9cd36b46fcbd1cf06f8e079533487c45d/api-reference/v01.00.02/openapi-v01.00.02.json) ### New Features #### Custom Fields API Added custom field definition management for workspaces, templates, and signing requests. **New Tag:** * `Custom Fields` - Custom field definition management **New Schemas:** ##### WorkspaceCustomField ```json theme={null} { "id": "uuid", "field_name": "string", "field_label": "string", "is_preset": true, "preset_value": "string", "created_at": "datetime", "updated_at": "datetime" } ``` ##### TemplateCustomField ```json theme={null} { "id": "uuid", "field_name": "string", "field_label": "string", "created_at": "datetime", "updated_at": "datetime" } ``` ##### SigningRequestCustomField ```json theme={null} { "id": "uuid", "field_name": "string", "field_label": "string", "copied_from_template": true, "created_at": "datetime", "updated_at": "datetime" } ``` ### Schema Improvements #### TemplateField Schema Enhanced with clearer descriptions and additional properties: * Explicit `type` property with enum values * `position` object with x, y, width, height * `read_only` and `read_only_value` for pre-filled fields *** ## v01.00.01 [Download OpenAPI Spec](https://github.com/Firma-esign/firma-docs/raw/4b2cbdb9cd36b46fcbd1cf06f8e079533487c45d/api-reference/v01.00.01/openapi-v01.00.01.json) ### Initial Release The initial release of the Firma Partner API with the following core features: #### API Tags / Categories * **Company** - Company information and settings * **Workspaces** - Workspace management operations * **Templates** - Template management operations * **Signing Requests** - Document signing request operations * **Webhooks** - Webhook configuration and management * **JWT Management** - JWT token generation and revocation * **Legacy** - Deprecated endpoints for backward compatibility #### Authentication * API key authentication via `Authorization` header * Optional Bearer prefix support #### Rate Limiting Tiered rate limits based on operation type: * Read operations (GET): 200 requests/minute * Write operations (POST/PUT/PATCH/DELETE): 120 requests/minute * Webhook CRUD: 60 requests/minute * Webhook test: 10 requests/minute * API key operations: 1 request/minute * Secret rotation: 1 request/minute #### Core Schemas ##### Company * `id`, `company_name`, `account_owner`, `account_owner_email` * `website`, `icon_url`, `credits` (read-only) * `date_created`, `date_changed` ##### Workspace * `id`, `name`, `protected`, `api_key` * `date_created`, `date_changed` ##### Template * `id`, `name`, `description` * `document_url` (pre-signed, expires) * `document_url_expires_at` * `date_created`, `date_changed` ##### SigningRequest * `id`, `name`, `description` * `document_url`, `document_url_expires_at` * `document_page_count`, `status`, `expiration_hours` * `settings`, `template_id` * Date fields: `date_created`, `date_sent`, `date_finished`, `date_cancelled`, `expires_at` * `certificate` object with generation status * `final_document_download_url`, `final_document_download_error` ##### Recipient * Core: `id`, `first_name`, `last_name`, `email`, `designation`, `order` * Address: `street_address`, `city`, `state_province`, `postal_code`, `country` * Professional: `phone_number`, `title`, `company` * `custom_fields` for additional data * Temporary ID support for document-based creation ##### Field * `id`, `type`, `position`, `page_number`, `required` * `recipient_id`, `variable_name` * Type-specific: `dropdown_options`, `date_default`, `date_signing_default` * Read-only: `read_only`, `read_only_value`, `prefilled_data` * Formatting: `format_rules`, `validation_rules` ##### Webhook * `id`, `url`, `events`, `enabled` * `date_created`, `date_changed` #### JWT Management * Generate JWT for template embedding * Generate JWT for signing request embedding * Revoke JWT tokens *** ## Migration Guide ### Migrating from v01.00.01 to v01.00.02 * No breaking changes * Custom Fields API available for use ### Migrating from v01.00.02 to v01.01.00 * No breaking changes * New template management endpoints available * API key regeneration endpoints available ### Migrating from v01.01.00 to v01.02.00 * No breaking changes * Template and signing request users now include additional fields * `ready_to_send` boolean available to check recipient readiness ### Migrating from v01.02.00 to v01.03.00 * No breaking changes * Email domain configuration available for custom sending domains * `declined` status added to signing request status enum * Credit cost tracking available on templates and signing requests ### Migrating from v01.03.00 to v01.04.00 * No breaking changes * `radio` field type renamed to `radio_buttons` (both still accepted) * New field types available: `textarea`, `url` * PATCH endpoints now support single-field operations * Use `template_user_id` for explicit template user matching in signing requests ### Migrating from v01.04.00 to v01.05.00 * No breaking changes * Email validation warnings are purely additive ### Migrating from v01.05.00 to v01.06.00 * No breaking changes * Split PDF download URLs available on completed signing requests * Field type inputs `initials`/`initial` and `textarea`/`text_area` both accepted with automatic normalization ### Migrating from v01.06.00 to v01.07.00 * No breaking changes * New `hand_drawn_only` setting available in signing request settings (defaults to `false`) * Deprecated top-level integer settings fields now visible in `SigningRequest` schema — use `settings` object instead ### Migrating from v01.07.00 to v01.08.00 * No breaking changes — schema field name corrections reflect actual API responses * New Email Templates API for customizing signing notification emails * `language` field added to `Company` and `WorkspaceSettings` * Template schema expanded with inline `recipients`, `fields`, `settings`, `page_count`, `expiration_hours` * `use_signing_order` added to `SigningRequestSettings` * Webhook schema includes `description`, `consecutive_failures`, `auto_disabled_at` * Signing request responses split into endpoint-specific shapes with inline data ### Migrating from v01.08.00 to v01.09.00 * No breaking changes * New `require_otp_verification` setting available at company, workspace, and signing request levels * New `POST /templates/{id}/replace-document` endpoint for replacing template PDFs while preserving fields ### Migrating from v01.09.00 to v01.10.00 * No breaking changes * Conditional field logic available via `required_conditions` and `visibility_conditions` on fields * DOCX files now accepted alongside PDF in all document upload endpoints * New `GET /signing-requests/{id}/audit` endpoint for retrieving audit trails * New `show_signature_frame` setting at company, workspace, and signing request levels * New `force_remove_conditions` parameter on user deletion operations ### Migrating from v01.10.00 to v01.11.00 * **Breaking change:** `use_signing_order` default changed from `false` to `true` — explicitly set `use_signing_order: false` if you rely on simultaneous delivery * New `file` field type for signer file uploads * Anchor tags for automatic field placement from text markers in PDFs * `background_color` property available on all field types ### Migrating from v01.11.00 to v01.12.00 * No breaking changes * MCP Server integration for AI assistant access to the Firma API * No new API endpoints or schema changes — OpenAPI specification unchanged from v1.11.0 ### Migrating from v01.12.00 to v01.12.01 * No breaking changes * New `variable_defined_name` field on all field responses — returns the human-readable custom field definition name alongside the internal `variable_name` * `variable_defined_name` accepted as input for field matching on `POST /signing-requests/create-and-send` ### Migrating from v01.15.03 to v01.16.00 * No breaking changes * New `GET /signing-requests/{id}/download` endpoint for retrieving a download URL for the signed document * Partial downloads available when `allow_partial_download` is enabled in signing request settings ### Migrating from v01.16.00 to v01.17.00 * No breaking changes * Six new nullable color fields (`color_primary`, `color_primary_fg`, `color_background`, `color_foreground`, `color_card`, `color_border`) on Company and Workspace Settings * New `ColorPalette` schema for resolved signing experience colors * Colors follow a 3-tier cascade: Firma defaults -> Company -> Workspace ### Migrating from v1.19.0 to v1.20.0 * No breaking changes * New `disable_guided_navigation` setting available at company, workspace, template, and signing request levels * Follows the same cascading pattern as `require_otp_verification`: signing request -> workspace -> company (first non-null wins) * Defaults to `false` (auto-scroll enabled), preserving existing behavior ### Migrating from v1.20.0 to v1.21.0 * No breaking changes * New `email_local_part` setting on Company (`GET/PATCH /company`) and Workspace Settings (`GET/PUT /workspace/{id}/settings`) * Workspace value takes priority over company value; default is `"support"` * Only applies when a verified custom domain is configured ### Migrating from v1.38.0 to v1.39.0 * No breaking changes * At launch, before October 13, 2026, multi-document features required enablement for your company; companies that were not enabled saw no change * New `document_ids` on `POST /templates`, `POST /signing-requests`, and `POST /signing-requests/create-and-send`, and new `/documents` routes on templates and signing requests * On enabled companies, `anchor_tags[].document_index` selects the document an anchor tag is searched in on multi-document requests * Send `documents_layout_version` with field writes on templates and signing requests that have more than one document ### Migrating from v1.40.0 to v1.40.1 * No change for valid PDFs * A PDF with a page that has no valid content returns `400 MALFORMED_PDF` with `malformed_pages`; nothing is written * An unreadable PDF, including an encrypted one, returns `400 VALIDATION_ERROR` instead of `500` ### Migrating from v1.40.1 to v1.40.2 * No API change * The `events` list of `POST /webhooks` and `PUT /webhooks/{id}` now names the 26 events Firma.dev sends; regenerate clients built from the spec # API Versioning Source: https://docs.firma.dev/guides/api-versioning Learn how the Firma API handles versioning, deprecation, and breaking changes # API Versioning Guide The Firma API uses header-based versioning via the `X-API-Version` header. This allows you to specify which API version you want to use, with automatic deprecation warnings and a structured sunset process. ## How to Specify a Version Include the `X-API-Version` header in your request: ```bash cURL theme={null} curl https://api.firma.dev/functions/v1/signing-request-api/signing-requests \ -H "X-API-Version: 1" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const response = await fetch( 'https://api.firma.dev/functions/v1/signing-request-api/signing-requests', { headers: { 'X-API-Version': '1', 'Authorization': `Bearer ${process.env.FIRMA_API_KEY}` } } ); ``` ```python Python theme={null} import requests headers = { 'X-API-Version': '1', 'Authorization': f'Bearer {os.environ["FIRMA_API_KEY"]}' } response = requests.get( 'https://api.firma.dev/functions/v1/signing-request-api/signing-requests', headers=headers ) ``` > [!NOTE] > > If the `X-API-Version` header is omitted, the API defaults to the current stable version (currently **1**). ## Version Lifecycle Each API version goes through three stages: | Status | Description | Behavior | | - | - | - | | **active** | Fully supported version | Normal responses with `X-API-Version` header | | **deprecated** | Still functional but scheduled for removal | Responses include deprecation warning headers | | **sunset** | Removed from service | Returns `410 Gone` error | ## Response Headers ### For Active Versions ``` X-API-Version: 1 ``` ### For Deprecated Versions When a version is deprecated, the following headers are included in every response: ``` X-API-Version: 1 Deprecation: 2025-06-01 Sunset: 2025-12-01 X-Deprecated-Message: This API version is deprecated and will be removed on 2025-12-01. Please migrate to version 2. ``` | Header | Description | | - | - | | `Deprecation` | RFC 8594 compliant - Date when the version was deprecated | | `Sunset` | RFC 8594 compliant - Date when the version will be removed | | `X-Deprecated-Message` | Human-readable warning message | ## Error Responses ### Invalid Version Header ```json theme={null} // 400 Bad Request { "error": "Invalid X-API-Version header. Must be a positive integer.", "code": "INVALID_VERSION" } ``` ### Unsupported Version ```json theme={null} // 400 Bad Request { "error": "API version 99 is not supported. Supported versions: 1", "code": "UNSUPPORTED_VERSION" } ``` ### Sunset Version (Removed) ```json theme={null} // 410 Gone { "error": "API version 1 has been removed as of 2025-12-01. Please migrate to version 2.", "code": "VERSION_SUNSET" } ``` ## Breaking Changes Policy > [!NOTE] > > Breaking changes are only introduced in major version increments (e.g., v1 → v2). Non-breaking changes such as new optional fields or new endpoints do not require a new version. When a new major version is released: 1. **The previous version is marked as deprecated** 2. **You'll receive warning headers** for a recommended 6-month deprecation period 3. **After the sunset date**, the old version returns `410 Gone` ### What Constitutes a Breaking Change? Breaking changes include: * Removing or renaming existing fields * Changing field types or validation rules * Removing endpoints * Changing authentication requirements * Modifying rate limits significantly Non-breaking changes include: * Adding new optional fields * Adding new endpoints * Adding new enum values (if your client handles unknown values gracefully) * Improving error messages ## Best Practices > **Always check for deprecation headers** > > Implement automated checks in your API client code to detect `Deprecation` and `Sunset` headers. Log warnings when these headers are present. > > ```javascript theme={null} > const response = await fetch(url, options); > > if (response.headers.get('Deprecation')) { > console.warn('API Deprecation Warning:', { > deprecation: response.headers.get('Deprecation'), > sunset: response.headers.get('Sunset'), > message: response.headers.get('X-Deprecated-Message') > }); > } > ``` > **Explicitly specify the version** > > Rather than relying on the default version, always specify the `X-API-Version` header explicitly. This prevents unexpected breaking changes when the default version changes. > > ```javascript theme={null} > // Good - explicit version > headers: { 'X-API-Version': '1' } > > // Avoid - relies on default > headers: { } > ``` > **Subscribe to changelog updates** > > Stay informed about upcoming changes by subscribing to the changelog or API announcements. This gives you advance notice of: > > * New version releases > * Deprecation schedules > * Breaking changes > * Migration guides > **Test against new versions early** > > When a new version is announced, test your integration against it well before the sunset date of the old version. This gives you time to: > > * Identify breaking changes in your implementation > * Update your code and dependencies > * Test thoroughly in staging > * Deploy with confidence before the deadline ## Current Version Status The current active version is **v1** with no deprecation scheduled at this time. > [!NOTE] > > When new versions are released or deprecation schedules are announced, this guide will be updated with specific dates and migration information. ## Related Resources > **[Authentication](/guides/authentication)** > > Learn about API key authentication and JWT tokens > **[Complete Setup Guide](/guides/complete-setup-guide)** > > Get started with the Firma API > **[Webhooks](/guides/webhooks)** > > Set up webhook notifications for events > **[API Reference](/api-reference/v01.15.00/company/get-company-information)** > > Browse all API endpoints # Audit Trail Source: https://docs.firma.dev/guides/audit-trail Track every action on a signing request for compliance and reporting. Every signing request in Firma has a complete audit trail. Every document view, field interaction, signature, decline, and admin action is recorded with timestamps, actor identity, and IP addresses. The finalized PDF and its completion certificate include `Signing Request ID: ` near the top of every page. This identifier cannot be removed with a workspace or API setting. Common use cases: * **Compliance and legal evidence** — prove who signed what, when, and from where * **Debugging signer issues** — see exactly what a recipient did during their signing session * **Internal reporting** — track admin actions like creation, edits, and cancellations The audit trail captures two categories of events: * **Signer events** — document views, field interactions, signing, declining, certificate downloads * **Admin events** — creation, edits, sending, cancellation, resending (via dashboard or API) Each event includes a timestamp, a human-readable description, actor identity, and (for signer events) the signer's IP address. ## Viewing the audit trail in the dashboard Open any signing request in the Firma dashboard and click the **Audit Trail** tab. You'll see a chronological timeline of all events, with signer actions and admin actions visually distinguished. Consecutive repetitive events (like scrolling or repeated field interactions) are automatically condensed with a count indicator. ### Authentication on the completion certificate Every signer row on the completion certificate carries an `Authentication:` line that reads `Email Link` or `Email Link + One-Time Code`. For a code signer, the line ends with `(code verified