waafir-esign reference
The waafir-esign server exposes Waafir's first-party e-signature service
to a connected AI client: create an envelope over a file in your organisation,
have its signatories sign (or decline), track status, and download the signed
PDF. Signatures are eIDAS AES (PAdES), produced by Waafir's own signing
engine; every signature captures signer consent (IP address, user agent,
timestamp) on a tamper-evident audit trail, and the signed PDF is stored as
the newest version of the source file.
Connection: https://app.waafir.io/api/mcp/waafir-esign — see
Setup. Read tools require a read-scope
PAT; creating, sending,
signing, declining, revising signers and voiding require write scope. A token restricted to specific data rooms
can only work with envelopes over files in those data rooms — the same
boundary the data room tools enforce.
A monthly envelope quota
E-signing is metered by the envelope — one document / signing job counts once, no matter how many signatories it has. This MCP server and the in-app signing surface both draw from one shared monthly pool:
- Access requires the E-Signatures add-on or an active Waafir
subscription. Without either, every request is refused at the door with
HTTP 402, before any tool runs — a
Layer 1
rejection.
ADDON_NOT_PURCHASEDappears inside that response'smessagetext; it is not a separate machine-readable field, so branch on the 402 status, not on a code. - Your plan includes a monthly envelope bundle: Starter 10, Premium 50, Enterprise 50 per active seat (calendar month).
- Each purchased unit of the E-Signatures add-on adds 100 envelopes per month on top of your plan bundle; units stack on any plan.
- The quota is a hard ceiling: when you send an envelope at the
limit, the send is refused with the wire code
QUOTA_EXCEEDED, prompting you to buy more units or upgrade — nothing is ever charged per-envelope automatically. The message text also names an internalESIGN_QUOTA_EXCEEDEDtoken; that is prose, so branch onerror.code, which isQUOTA_EXCEEDED. Asend: truecreate that trips the ceiling still leaves the draft behind — send it once you have headroom rather than creating it again.
Quota is consumed when an envelope is sent (opened for signing), not per
signature — so signing and declining never consume quota. Reads and downloads
don't either. Check headroom any time with esign_quota_status.
Your own NDA, signed electronically
When your organization uploads its own NDA (Investors → NDA → Upload your own NDA, see Customisability), each investor signs it in Waafir's signer: a signed-in investor chooses Sign the NDA and the document opens in a new tab; a share-link visitor receives the signing link by email at the address they entered. These NDA envelopes do not count towards the monthly quota, and the NDA template itself cannot be sent like an ordinary envelope.
Tools
Argument and result keys on this server are
snake_case— the same convention every other Waafir MCP server uses.file_id,envelope_id,transaction_id,signing_mode,field_layout,consent_affirmed,confirm_send,download_url,has_more.
Breaking change —
waafir-esignpreviously tookcamelCasearguments.fileId,envelopeId,transactionId,signingMode,fieldLayoutandconsentAffirmedare no longer accepted; each now has asnake_casespelling, listed in the tables below.confirm_sendis unchanged — it was already snake_case.Where the retired spelling was a required argument it is rejected, not silently honoured: a call still sending
fileIdtoesign_create_envelopefails argument validation namingfile_idas the missing key. Where it was optional it is dropped silently, exactly as any other unrecognised key is — see the two failure modes below. Update your calls to the spellings on this page.
A misspelled key fails in one of two ways, and one of them is silent. An unrecognised argument is dropped, never rejected, so what happens next depends only on whether the key you meant was required:
-
Required key → the call is refused. The argument is simply missing. This is the loud, safe case, and it covers
esign_create_envelope(file_id),esign_get_envelope,esign_sign(envelope_idandconsent_affirmed),esign_decline,esign_void_envelope,esign_revise_signers(envelope_idandsignatories) andesign_download_signed— including every retired camelCase spelling of those keys.It carries no
error.code. Arguments are validated against the tool's published schema before the tool runs, so a missing required key — or one of the wrong type — comes back as an error result the server generates itself: the response is flagged as an error and its text names the offending argument, but no tool ran, so nothing populates the Layer 2error.codefield. Don't branch onINVALID_ARGUMENTfor an argument-shape failure — read the message.INVALID_ARGUMENTis still what you get for a refusal a tool raises after it starts, such assend: truewithoutconfirm_send: true. -
Optional key → the call succeeds and does something else. You get a
200and a well-formed result that answers a different question:esign_list_envelopes' filters are optional, so a straytransactionId/fileIdis discarded and you receive the unfiltered list of every envelope the token can see — a superset returned as though it were the filtered answer.- On
esign_create_envelope, a discardedsigningMode: "sequential"leaves the envelope on its defaultparallelmode. Sending it then emails a legally-binding signature request to every signatory at once, which is precisely what asking forsequentialwas meant to prevent.transactionIdandfieldLayoutare dropped the same way.
Because a dropped optional key cannot announce itself, check that the
result echoes what you asked for — the created envelope reports its own
signing_mode, and esign_list_envelopes is only filtered if you spelled the
filter correctly. transaction_id is the exception, and the trap: it is not
in esign_create_envelope's result, so a dropped transactionId cannot be
caught there. Read the envelope back with
esign_get_envelope, which does return it.
esign_create_envelope (write)
Create an envelope over a file in your organisation, naming its signatories. Any email address is accepted — a signatory does not need a Waafir account. How each one is reached is decided when the envelope is sent, by whether that address already has a Waafir account: an account holder signs in-session (an in-app notification plus an email linking to the signing screen), and everyone else is emailed a guest signing link. The test is account existence, not membership of your organisation, so a signatory who belongs to a different organisation also signs in-session. Every pending signatory gets exactly one of the two — never both, never neither.
| Field | Type | Required | Description |
|---|---|---|---|
file_id | string (UUID) | yes | The file to be signed. Must belong to your organisation. |
signatories | array | yes | 1–50 of { "email": string, "name"?: string }. Any email is accepted — signatories do not need to be Waafir users; external addresses get a guest signing link. |
transaction_id | string (UUID) | no | Attach the envelope to a transaction. |
send | boolean | no | When true, open the envelope for signing immediately. Requires confirm_send: true. |
confirm_send | boolean | no | Explicit acknowledgement that send: true immediately emails a legally-binding signature request to every signatory. Must be true whenever send is true; ignored otherwise. |
signing_mode | "parallel" | "sequential" | no | Signing order. "parallel" is the default: signatories may sign in any order, and sending emails all of them at once. "sequential" emails only the first, and releases each signatory once every earlier one has signed; signing out of turn is refused with CONFLICT. Omitted — or misspelled signingMode — means "parallel". |
field_layout | array | no | Field-placement layout override; omit for the default layout. |
observers | array | no | Up to 20 observers (cc) of { "email": string, "name"?: string }. An observer is emailed a copy of the signature request and of each reminder, with a read-only link to follow its status. Observers never sign and are not sent the signed copy. |
send: true without confirm_send: true is refused with INVALID_ARGUMENT —
the acknowledgement exists because sending dispatches real, legally-binding
signature-request emails to every listed address the moment the call succeeds.
Returns the envelope. Its fields are exactly id, file_id, status,
signing_mode, created_by, signed_output_name,
signed_output_version_count, field_layout, packet_page_count and
expires_at — the result is a hand-mapped allow-list, so nothing else is on
the wire. Timestamps are ISO-8601 strings.
A draft envelope must be sent before anyone can sign. Sending consumes one
envelope from your monthly quota — at the ceiling, a send returns
QUOTA_EXCEEDED and the envelope stays a draft you can send later. A mis-sent
envelope can be recalled with esign_void_envelope.
esign_list_envelopes (read)
List your organisation's envelopes, newest first, with each envelope's
signatories and statuses. Optional filters: transaction_id, file_id.
Because they are optional, a retired camelCase spelling is dropped silently
and the call returns the unfiltered list rather than an error; see the key
note above.
Paginated — limit (optional, 1–100, default 25) and offset (optional,
default 0) must be numbers; a value of another type fails argument
validation like any other type mismatch. An out-of-range number is
clamped, not rejected — limit: 10000 becomes 100, offset: -2 becomes
0. Returns:
{ envelopes: [...],
pagination: { limit, offset, count, has_more, next_offset, total },
filtered_by_pat_scope: boolean }Use has_more to tell a clipped result from a complete one, and follow
next_offset until it is null; never infer more-pages from
count === limit. total is the number of envelopes this token can see.
filtered_by_pat_scope is true when the token is restricted to specific data
rooms and that allow-list constrained the result — the window is applied after
the filter, so pages are always exact. It is deliberately a boolean and not a
count of hidden envelopes: reporting how many were withheld would leak the
existence and volume of envelopes the token was restricted from seeing.
esign_get_envelope (read)
Get one envelope with its signatories and their statuses
(pending / signed / declined). Input: envelope_id. Alongside the
create-tool fields above, the envelope carries transaction_id, created_at,
updated_at, and signatories (each
{ id, email, name, status, signed_at }).
esign_sign (write)
Sign your own pending signatory slot — the slot whose email matches the user who owns the token. Signing does not consume quota (the envelope was already metered when it was sent).
| Field | Type | Required | Description |
|---|---|---|---|
envelope_id | string (UUID) | yes | The envelope to sign. |
consent_affirmed | boolean | yes | Must be literally true — an explicit affirmation of the consent statement. There is no default; omitting it, or passing false, fails the call at the protocol boundary. |
fields | object | no | Fill values keyed by field label; name and date default when omitted. |
Returns { envelope }, the envelope in the same allow-listed shape
esign_create_envelope returns.
Errors you may see: FORBIDDEN if you are not a pending signatory on the
envelope, CONFLICT if the envelope is not open for signing — or, on a
sequential envelope, if an earlier signatory has not signed yet. These are
in-payload error.code values, not HTTP statuses — see
Setup.
If the envelope's layout has required fields and fields does not supply
them, the call is refused with INVALID_ARGUMENT and a message containing
(code: MISSING_REQUIRED_FIELDS). The message does not name the missing
labels; the in-app signing screen does. Fill them and call again.
esign_decline (write)
Decline your own pending slot (terminal — the envelope becomes declined).
Input: envelope_id. Returns { id, status: "declined" }. Consumes no quota.
esign_void_envelope (write)
Recall a mis-sent envelope. This is the remedy when a signature request went out that should not have: the envelope stops accepting signatures, every outstanding guest signing link is invalidated immediately, and pending signatories can no longer act.
Input: envelope_id. Returns the voided envelope, in the same allow-listed
shape esign_create_envelope returns.
- Terminal. A voided envelope cannot be re-sent — create a new one instead.
- Signatures already recorded and the full audit trail are preserved; voiding does not erase what was already signed.
- Legal only while the envelope is
draft,sent, orpartially_signed. Any other status returnsCONFLICT, and an envelope that has expired reports that rather than a stale status. - Who may void: only the person who created the signature request, or an
organization owner or admin. Anyone else gets
FORBIDDEN; a caller who is not a party to the envelope at all getsNOT_FOUND, so the tool never confirms an envelope exists to someone who should not know. - Voiding consumes no quota — and refunds none. Quota is charged when an envelope is sent.
esign_revise_signers (write, destructive)
Fix who is being asked to sign, without voiding the envelope and spending a second envelope from your quota: a mistyped email, a missed counterparty, the wrong signing order. The envelope keeps its id, its audit trail and its quota unit. Consumes no quota.
| Field | Type | Required | Description |
|---|---|---|---|
envelope_id | string (UUID) | yes | The envelope to revise. |
signatories | array | yes | The complete signer list the envelope should end up with, in signing order: 1–50 of { "email": string, "name"?: string }. It replaces the current list. |
signing_mode | "parallel" | "sequential" | no | Change the signing order. Omitted means the current mode is kept; it does not reset to "parallel". |
observers | array | no | The complete observer (cc) list, up to 20. Omitted means the current observers are kept; an observer you leave out is removed and their read-only link stops working. |
It is destructive. A signatory you leave out of signatories is removed:
their invitation is withdrawn and their signing link stops working at once,
and they are not told. Any fields placed for them are deleted. A signatory who
is already on the envelope keeps their link and is not emailed again. A new
signatory is invited straight away if the envelope has already been sent. The
tool is marked destructiveHint: true, so a client that confirms destructive
calls will ask before running it.
- Only until the first signature. Once anyone has signed, the signer list
and the signing mode are frozen: the call is refused with
CONFLICT(code: ESIGN_SIGNERS_FROZEN), and the remedy is to void and create a new envelope. An expired envelope is refused withCONFLICTas well. - Changing only the observers. Pass the unchanged signer list (and mode)
with a new
observerslist. Removing an observer is allowed at any time, even after signing or completion. Adding one is allowed only while the envelope is a draft or out for signing. - Who may revise a sent envelope: only the person who created the signature
request, or an organization owner or admin; anyone else gets
FORBIDDEN. A draft can be revised by anyone who can see it. - Other refusals carry their reason in the message: a duplicate address
(
ESIGN_DUPLICATE_SIGNATORY) or an empty list (ESIGN_EMPTY_ROSTER) isINVALID_ARGUMENT; a new signatory or observer on a sent envelope when you cannot read every document it covers isFORBIDDEN.
Returns the envelope in the same shape as
esign_get_envelope, plus a revision summary of
what changed (and observer_revision when observers were passed).
esign_list_my_pending_signatures (read)
List the envelopes awaiting your own signature in this organisation: every
open envelope with a pending signatory slot matching the email of the token's
owner. This is the most useful read to make before esign_sign.
No inputs. Returns
{ envelopes: [{ id, file_id, document_name, status, created_at }] }.
Scoped to this token's organisation and, if the token is restricted, to its data-room allow-list — pending signatures in other organisations are never shown, even though you may be a signatory there.
esign_download_signed (read)
Get a short-lived presigned download URL for the signed PDF. Input:
envelope_id. Returns { download_url, expires_in_seconds }. The envelope
must have at least one recorded signature; the signed PDF is also available in
the product as the newest version of the source file.
esign_quota_status (read)
No inputs. Returns { quota, used, remaining } for the current calendar
month — total envelope quota (plan bundle + purchased units), envelopes sent,
and headroom before the ceiling.
Legal validity
Signatures are eIDAS Advanced Electronic Signatures (PAdES), valid for NDAs and contracts under US ESIGN/UETA and EU eIDAS. Signer consent (IP, user agent, timestamp) is recorded on Waafir's append-only, hash-chained audit trail at signing time. Envelopes created or sent through this MCP server are recorded on that trail as token-initiated actions, so an agent-initiated send is always distinguishable from one a person performed in the app. Qualified (QES) signatures are not part of this release.
waafir-dataroom reference
Reference for the waafir-dataroom MCP server — every tool for reading and managing data rooms over a Personal Access Token, grouped into read, folder, file, document-text, permission, and Q&A tools, with their inputs, outputs, and required scopes.
Platform API (/v1)
The Waafir /v1 Platform API. What it covers, how to authenticate, your first call, and where to find every endpoint and the known error codes.