DevelopersAPI · v1

Changelog

Every change to the BuiltSign v1 API, newest first.

Versioning policy

  • A change that can break a correct integration is announced at least 90 days ahead in the changelog.
  • Not breaking, and added without notice: new endpoints, new optional parameters, new fields in responses and webhook payloads, new webhook events, new error codes and new headers. Ignore what you do not know, and act on error.code rather than on the message.
  • An endpoint that is being phased out answers with a Deprecation header and a Link to the changelog, and with Sunset (RFC 8594) once the end date is set.
  • Security fixes can ship without notice. They are in the changelog too, with their date.

What a deprecated endpoint returns

http
HTTP/1.1 200 OK
Deprecation: @1793577600
Sunset: Mon, 01 Feb 2027 00:00:00 GMT
Link: <https://builtsign.com/developers/changelog>; rel="deprecation"; type="text/html",
      <https://builtsign.com/developers/changelog>; rel="sunset"; type="text/html"

No endpoint is deprecated at the moment.

23 de octubre de 2026

2026-10-23

Security

  • Behavior change POST /v1/signing-requests (also with auto_send: true), POST /v1/signing-requests/{request_id}/send, POST /v1/templates/{template_id}/send and POST /v1/bulk-send now refuse a document that the malware scan rejected, the same check the dashboard runs when a request is created: 422 with the new error code document_blocked, and nothing is created or sent. A document whose scan has not finished yet can still be used. When the scan rejects the document of a queued bulk job later, its remaining recipients fail with the same message.

22 de octubre de 2026

2026-10-22

Added

  • GET /v1/signing-requests/{request_id}/audit-certificate returns the audit certificate (PDF) of a completed, cancelled or expired request. Earlier: 409 with signing_request_wrong_status. Right after completion, while the evidence is still being sealed: 409 with document_not_ready and Retry-After. Scope read.
  • GET /v1/documents/{document_id} returns one document with its signing request ids and removal: what a delete would do now (delete, archive or blocked). Like the list, a key only reaches documents of its own mode (sandbox or live); any other id returns 404 with document_not_found.
  • DELETE /v1/documents/{document_id} deletes a document without evidence, together with its draft requests that were never signed. A document in an open request returns 409 with document_has_open_request; one with signatures or other evidence (kept for 7 years) returns 409 with document_has_evidence. Scope write.
  • Cursor paging on GET /v1/signing-requests, GET /v1/documents and GET /v1/events: pass the next_cursor of a page as starting_after until has_more is false. The responses gain has_more and next_cursor. Offset paging works as before; offset together with starting_after returns 422. On /v1/events, after_id keeps working.
  • Daily limit headers on every API response: X-RateLimit-Daily-Limit, X-RateLimit-Daily-Remaining and X-RateLimit-Daily-Reset (next midnight UTC, Unix time).
  • Error codes idempotency_key_reused and idempotency_key_in_use.
  • Large documents, up to 1 GB, in parts straight to storage: POST /v1/uploads starts an upload and returns an upload id, a part_size and a presigned PUT URL per part. POST /v1/uploads/{upload_id}/parts gives fresh URLs (they are valid for 15 minutes, and the first response holds at most 100). POST /v1/uploads/{upload_id}/complete takes the part numbers with their ETags and returns the same document as POST /v1/documents/upload, including the document.uploaded webhook. POST /v1/uploads/{upload_id}/abort stops an upload. Start and complete accept an Idempotency-Key.
  • Complete checks the real size against your plan and that the file starts with %PDF; a file that fails is deleted. Sandbox keys can upload up to 100 MB per file (413 payload_too_large above that, with details.sandbox: true) and nothing counts toward the quota.
  • Node SDK 0.4.0 (@builtsign/node): documents.uploadLarge(pathOrStream, { partSize, concurrency }) does the whole upload flow, with parallel parts and a retry per part. Also new: webhooks.verify() with the timestamp tolerance, webhooks.test(), webhooks.redeliver(), webhooks.regenerateSecret({ overlap_hours }), documents.get(), documents.delete(), signingRequests.auditCertificate(), me(), cursor paging with iterate(), the return_url type on embeddedUrl() and details on every BuiltSignError.
  • Node SDK 0.4.0: maxRetryAfter (seconds, default 60). A 429 with a longer Retry-After comes back as BuiltSignRateLimitError at once instead of being slept on; daily_limit_exceeded is never retried.
  • Error code daily_limit_exceeded (429) for the daily API call limit. The minute limit keeps rate_limit_exceeded.
  • POST /v1/signing-requests/{id}/embedded-url accepts an optional return_url: after the signer signs or declines, the iframe navigates there with builtsign_event (signed or declined) and builtsign_request_id added. Its origin must be on the embed allowlist; otherwise the call returns 400 with validation_error. The response echoes return_url.
  • The embedded signing page now also posts builtsign:ready (with requestId, signerId, signerStatus, requestStatus) and builtsign:error (with code, message, recoverable) to the parent window. Every event carries requestId and signerId.
  • @builtsign/embed 0.2.0: container (element or selector), onReady on builtsign:ready, onError with code and recoverable, a ready_timeout error, and a script-tag build. It checks event.source as well as event.origin.
  • Webhook payloads now carry an event_id (evt_...). It stays the same on every retry and redelivery, so deduplicate on it. Every request also carries X-Webhook-Delivery-ID: the id of the delivery, kept on automatic retries, new on a redelivery.
  • New signature header X-BuiltSign-Webhook-Signature: t=<unix seconds>,v1=<hex>: HMAC-SHA256 with the webhook secret over "{t}.{raw body}". Check it in constant time and reject a t more than 5 minutes from your clock; that stops a captured request from being replayed. @builtsign/node 0.4 does this in webhooks.verify(payload, header, secret, { tolerance }).
  • POST /v1/webhooks/{webhook_id}/test sends a signed test event ("test": true, header X-Webhook-Test: true). Optional body {"event_type": "..."}. It is not stored as a delivery and does not count as a failure.
  • POST /v1/webhooks/deliveries/{delivery_id}/redeliver sends a stored delivery again with the same body and event_id, as a new delivery. Only for deliveries of your own organization, of a webhook in the key's mode (sandbox or live); without the Enterprise plan only sandbox deliveries. A successful redelivery ends the retries still planned for the original.
  • Delivery responses (GET /v1/webhooks/{webhook_id}/deliveries, redeliver) now include webhook_id, event_id, attempt_number and next_retry_at.
  • Versioning policy, published with this changelog at https://builtsign.com/developers/changelog: /v1 stays stable, breaking changes are announced at least 90 days ahead, and new fields, new webhook events and new error codes are not breaking.
  • An endpoint that is being phased out now answers with Deprecation (RFC 9745), a Link to this changelog (rel="deprecation") and, once its date is set, Sunset (RFC 8594). The spec marks it deprecated: true with the dates in x-deprecation. No endpoint is deprecated yet. The headers are exposed to browsers (CORS).
  • A Postman collection (v2.1) generated from the spec: https://builtsign.com/builtsign-api.postman_collection.json. Set the variables baseUrl and apiKey; create and send calls get a fresh Idempotency-Key.

Changed

  • Breaking Node SDK 0.4.0: webhooks.verify() and verifyWebhook() only accept X-BuiltSign-Webhook-Signature (t=...,v1=...). The plain hex value of X-Webhook-Signature that 0.3 checked is refused with invalid_signature. Pass the new header when you upgrade.
  • Breaking The Zapier hooks POST /v1/zapier/subscribe and DELETE /v1/zapier/subscribe/{subscription_id} now need scope write; a key with only read gets 403 with auth_scope_missing. They change your account, so read was too little. GET /v1/zapier/samples still takes read.
  • Breaking Sandbox and live are now separated for every resource, not only for signing requests. A sandbox key only sees and changes sandbox data; a live key never sees it:
    • GET /v1/documents lists only documents uploaded with a key of the same mode. POST /v1/signing-requests, POST /v1/templates, PATCH /v1/templates/{template_id} and POST /v1/templates/{template_id}/send return 404 with document_not_found for a document of the other mode. A sandbox request on a live document is no longer possible.
    • Templates and webhooks take the mode of the key that creates them. GET, PATCH and DELETE on a template or webhook of the other mode, POST /v1/templates/{template_id}/send, POST /v1/bulk-send, POST /v1/webhooks/{webhook_id}/regenerate-secret and GET /v1/webhooks/{webhook_id}/deliveries return 404. Templates and webhooks made in the dashboard are live.
    • GET /v1/bulk-jobs/{bulk_job_id} returns 404 for a job of the other mode.
    • A webhook receives only events of its own mode: a webhook created with a sandbox key only sandbox events, a live webhook only live events. Until now an Enterprise webhook received both.
    • GET, POST and DELETE on /v1/contacts with a sandbox key return 403 with the new error code sandbox_not_supported. Saved contacts are the organization's real address book.
    • Existing templates on a sandbox upload, and the templates and webhooks of partners with sandbox access only, are marked sandbox, so those keys keep seeing them.
  • Behavior change an Idempotency-Key is now tied to the request. The same key with a different body or on another endpoint returns 422 with idempotency_key_reused instead of the first response. A retry with the same key while the first call is still running waits a few seconds for its answer; if it still runs after that, 409 with idempotency_key_in_use and Retry-After. Before, both calls ran. If the key store is unreachable, a call with an Idempotency-Key returns 503 with Retry-After and does nothing.
  • Behavior change with auto_send: true on POST /v1/signing-requests, the send limits (trial, transaction limit, SMS bundle, ID check credits) are checked before anything is created. A 402 no longer leaves a draft behind (it did since 2026-10-12), so a retry does not create a second one.
  • Behavior change POST /v1/signing-requests now limits expires_in_days to 1 to 365, signers to at most 50 and message to at most 2000 characters. Outside these: 422 with validation_error. A negative expires_in_days used to create a request that had already expired.
  • Behavior change an API key's own rate limit (requests per minute) can no longer exceed its organisation's limit (60 by default). It used to be the higher of the two.
  • When the daily limit is reached, Retry-After now gives the seconds until midnight UTC instead of a fixed 3600.
  • When the rate limiter is briefly unavailable the API returns 503 with Retry-After instead of 500. Nothing was done; retry the call.
  • GET /v1/signing-requests and GET /v1/documents now order items with the same timestamp by id, so offset pages no longer shift between calls.
  • The OpenAPI spec documents 401, 403, 404, 409, 422, 429 and 503 per operation, with examples.
  • Behavior change POST /v1/signing-requests, POST /v1/templates and PATCH /v1/templates/{template_id} with a document_id, POST /v1/templates/{template_id}/send and POST /v1/bulk-send now return 409 with document_not_ready for a document whose upload is not completed (details.status: "uploading"), or that is still being processed (with Retry-After). A document whose file is not in storage returns 409 with document_file_removed. POST /v1/signing-requests/{request_id}/send refuses a draft on an unfinished upload the same way.
  • Behavior change POST /v1/uploads/{upload_id}/complete needs every part from 1 up to the last one, and the joined file must be exactly the file_size given at the start. A missing part returns 400 with upload_incomplete and changes nothing; a joined file of another size returns 400 with upload_incomplete and is deleted.
  • Behavior change when a signing request or template already points at the document of an upload, POST /v1/uploads/{upload_id}/abort and the rejection on complete no longer delete the document: 409 with document_in_use and details.references. On complete the rejected file itself is still deleted.
  • Behavior change the daily API call limit now returns 429 with daily_limit_exceeded instead of rate_limit_exceeded. Its Retry-After runs to midnight UTC.
  • Sandbox uploads no longer count toward the storage of the live plan.
  • Breaking POST /v1/documents/upload with a file over 50 MB now returns 413 with error code payload_too_large (it was 400 with validation_error). details.limit holds the limit and details.use points to /v1/uploads. A request whose Content-Length is over the limit is refused before the body is read.
  • Breaking POST /v1/documents/upload now applies the plan limits of the dashboard: the file size of the plan (413 payload_too_large), the storage and the monthly document volume (403 quota_exceeded, with details.resource storage or document_volume). The upload counts toward the monthly volume when your plan has one. Sandbox keys are not affected.
  • Behavior change failed deliveries are retried 9 times with exponential backoff and jitter (about 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, then every 12 hours), over roughly 2.5 days. It was 3 retries within 21 minutes.
  • Behavior change a webhook is disabled automatically when 10 events in a row fail over at least 3 days. It used to be after 10 failed events, however quickly they came. Retries stop when a webhook is disabled. A paused webhook keeps its planned retries; they go out once it is active again.
  • Behavior change POST /v1/webhooks/{webhook_id}/regenerate-secret keeps the previous secret signing for 24 hours: X-BuiltSign-Webhook-Signature then carries a v1 for each secret. Optional body {"overlap_hours": 0-168}; 0 revokes the old secret at once. The response and GET /v1/webhooks show previous_secret_expires_at while the overlap runs. The overlap is only in X-BuiltSign-Webhook-Signature: the deprecated X-Webhook-Signature uses the new secret at once.
  • The dashboard's test button no longer sends the secret in X-Webhook-Secret; the test event is signed like a real delivery.

Deprecated

  • X-Webhook-Signature (HMAC-SHA256 over the body only, with the current secret) is still sent so existing receivers keep working, but it has no timestamp. Switch to X-BuiltSign-Webhook-Signature.

    Proposed removal: not before 2027-01-20, 90 days after this notice. That date is not final yet; we confirm it here first, and the header keeps being sent until then.

Fixed

  • PATCH /v1/templates/{template_id} with a document_id now checks that the document belongs to your organization (and, with a sandbox key, was uploaded with a sandbox key). Otherwise it returns 404 with document_not_found and nothing is saved. It used to accept any document id. The same check now applies to PATCH /api/templates/{template_id} in the dashboard, and bulk jobs only use a template and document of the job's own organization.
  • PATCH /v1/templates/{template_id} now checks signing_level against your plan, like the dashboard: aes or qes without qualified signatures in the plan returns 403 with feature_not_in_plan.
  • A webhook's trigger_count and failure_count could miss counts when deliveries ran at the same time. They are now raised in one database statement.
  • builtsign:signed sent the request status in requestId. It now carries the signing request ID.
  • Retries could stop for every endpoint: the retry sweep took 100 due deliveries without an order, and deliveries of a paused or disabled webhook kept their retry time and filled that batch. The sweep now takes only deliveries of active webhooks, oldest due first, and clears the retries of disabled ones.

13 de octubre de 2026

2026-10-13

Changed

  • Behavior change template names are now unique per owner (the user who created the API key), ignoring case and leading or trailing spaces. POST /v1/templates with a name the owner already uses for an active template, and PATCH /v1/templates/{template_id} that renames a template (or sets is_active: true) to such a name, return 409 with error code template_name_exists and the existing template's id in details.template_id. Nothing is saved.

    Templates that already share a name stay as they are and can still be updated as long as the name does not change.

12 de octubre de 2026

2026-10-12

Changed

  • Behavior change every signer now confirms with an SMS code before signing. Outside the sandbox POST /v1/signing-requests, POST /v1/templates/{template_id}/send and POST /v1/bulk-send always create the request with phone_verification_required: true, whatever the body or the template says. The field stays in the schemas for compatibility. Requests created before this change keep their setting.
  • Sandbox keys still send no SMS: phone_verification_required: true on POST /v1/signing-requests still returns 400 with validation_error. A template's phone_verification_required is now ignored, so POST /v1/templates/{template_id}/send and POST /v1/bulk-send with such a template no longer return 400 with a sandbox key; the sandbox request gets no SMS code.
  • A signer phone locks the SMS code to that number. Without a phone the signer enters their own number when signing.
  • The signing link goes by SMS only for a signer with sms_invite: true and a phone (paid plans), since 2026-10-11. A phone alone no longer sends the link by SMS.
  • Behavior change sending (auto_send: true, or POST /v1/signing-requests/{id}/send) now checks the SMS bundle. When the bundle plus purchased SMS credits cannot cover the codes and SMS invites, the call returns 402 with quota_exceeded and details.resource: "sms" (with used, limit and needed). With auto_send: true the request stays a draft.

    POST /v1/bulk-send with auto_send refuses the whole job this way before anything is queued. A recipient that no longer fits while the job runs fails with that message in error_message.

  • Signing request responses now return the real phone_verification_required; it was always false.

Fixed

  • POST /v1/bulk-send now keeps each signer's phone and sms_invite. They were dropped before the job ran.

5 de octubre de 2026

2026-10-05

Changed

  • Behavior change POST /v1/contacts now checks the format of email and returns 422 with error code validation_error for an invalid address such as jan@gmailcom (no dot in the domain). It used to only check for an @, so such a contact was saved and failed later when it was used as a signer.

    A name of only spaces is now also rejected with 422; leading and trailing spaces are removed before the length check.

    The call is still an upsert: an email address that already exists updates that contact.

  • Behavior change a signer name that is empty or only spaces now returns 422 with error code validation_error on POST /v1/signing-requests, POST /v1/templates/{template_id}/send and POST /v1/bulk-send. Leading and trailing spaces are removed.

    On POST /v1/signing-requests a signer name longer than 255 characters is now also rejected with 422 before anything is saved.

4 de octubre de 2026

2026-10-04

Changed

  • Behavior change a feature that is not in the organization's plan now always returns 403 with error code feature_not_in_plan and details (feature, required_plan, upgrade_plan, plan). This affects:
    • POST /v1/bulk-send without bulk automation: was 403 with quota_exceeded.
    • signing_level aes or qes while qualified signatures are enabled but not in the plan: was 422. When qualified signatures are not enabled at all the call still returns 422.
    • POST /v1/signing-requests with identity_verification_required and auto_send on a plan without identity checks: was accepted without a check, now 403 (feature: identityVerification). Not enough credits is still 402 with insufficient_credits.
  • Breaking GET /v1/usage: storage.limit is now in bytes, the same unit as storage.used. It used to be in GB, so used and limit could not be compared.

2 de octubre de 2026

2026-10-02

Added

  • GET /v1/me returns the organization and the name of the API key used for the call (organization_id, organization_name, api_key_name, sandbox, scopes). The key itself is never returned.

Changed

  • Behavior change GET /v1/signing-requests/{id}/download no longer returns the unsigned original while the signed PDF is still being prepared.

    Right after a request turns completed the call can return 409 with error code document_not_ready and a Retry-After header (in seconds). Retry after that delay.

    An error without Retry-After is final: 409 with conflict when the signed PDF could not be generated (contact support), 410 with document_file_removed when it was removed after the retention period.

    Wait for the document.signed webhook or event before downloading; signing_request.completed can arrive before the signed PDF exists.

Fixed

  • POST /v1/documents/upload now checks the file content instead of only the .pdf extension.

    A file that does not start with a PDF header is rejected with 400 and error code validation_error.

14 de junio de 2026

2026-06-14

Added

  • sandbox field on GET /v1/signing-requests, GET /v1/signing-requests/{id}, and POST /v1/signing-requests responses.

    Indicates whether the request was created with a sandbox API key. Sandbox requests skip real email delivery, SMS costs, and identity checks.

  • New webhook events:
    • signing_request.reminded: fired when POST /v1/signing-requests/{id}/remind is called. Payload: signing_request_id, signers_reminded, sent_count.
    • document.uploaded: fired when POST /v1/documents/upload succeeds. Payload: document_id, name, file_size, is_sandbox.
    • member.invited: fired when a team member is invited via the dashboard. Payload: email, name, role, invited_by.
  • MCP (Model Context Protocol) server published as @builtsign/mcp in packages/mcp-server/.

    Enables AI assistants (Claude, Cursor, etc.) to upload documents and send signing requests directly.

10 de junio de 2026

2026-06-10

Added

  • Machine-readable error codes on all v1 endpoints. Errors now return {"error": {"code": "ERROR_CODE", "message": "...", "request_id": "..."}} instead of plain strings.
  • Cursor-based pagination on GET /v1/events: new after_id parameter, response includes last_id and has_more.
  • GET /v1/signing-requests filters: created_after, created_before, signer_email.
  • Sort parameters on GET /v1/signing-requests: sort (created_at | updated_at | expires_at) and order (asc | desc). Default: created_at desc.
  • signing_request.cancelled webhook now fires correctly when POST /v1/signing-requests/{id}/cancel is called.
  • document.signed event added to SigningEventType and included in Events API responses.
  • Webhook events signer.reviewed, identity.approved, identity.rejected registered.

Fixed

  • signing_request.cancelled webhook was not fired when cancelling via API v1.
  • document.signed was missing from the events pull API.

19 de mayo de 2026

2026-05-19

Added

  • POST /v1/bulk-send: send one template to many recipients in a single call. Progress through GET /v1/bulk-jobs/{bulk_job_id}.
  • GET /v1/contacts: list contacts for your organisation.
  • GET /v1/usage: monthly usage stats (requests used, limit, resets at).
  • Sandbox API keys: set is_sandbox: true when creating an API key. Sandbox keys bypass email/SMS/identity, use a sandbox/ S3 prefix, and do not count against usage quotas.
  • Idempotency-Key support on POST /v1/documents/upload and POST /v1/signing-requests.
  • Enterprise API governance: per-organisation rate limits, daily caps (default 2000 calls/day), and Didit identity quota via enterprise_api_config.

10 de mayo de 2026

2026-05-10

Initial v1 release.

Added

  • POST /v1/documents/upload
  • GET /v1/documents
  • POST /v1/signing-requests with auto_send flag
  • GET /v1/signing-requests
  • GET /v1/signing-requests/{id}
  • POST /v1/signing-requests/{id}/send
  • POST /v1/signing-requests/{id}/cancel
  • POST /v1/signing-requests/{id}/remind
  • POST /v1/signing-requests/{id}/reactivate
  • GET /v1/templates
  • POST /v1/templates/{id}/send
  • GET /v1/events
  • GET/POST /v1/webhooks with HMAC-SHA256 signatures
  • API key authentication via X-API-Key header (bs_ prefix)