Waafir
Connect AI Tools

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_PURCHASED appears inside that response's message text; 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 internal ESIGN_QUOTA_EXCEEDED token; that is prose, so branch on error.code, which is QUOTA_EXCEEDED. A send: true create 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-esign previously took camelCase arguments. fileId, envelopeId, transactionId, signingMode, fieldLayout and consentAffirmed are no longer accepted; each now has a snake_case spelling, listed in the tables below. confirm_send is unchanged — it was already snake_case.

Where the retired spelling was a required argument it is rejected, not silently honoured: a call still sending fileId to esign_create_envelope fails argument validation naming file_id as 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_id and consent_affirmed), esign_decline, esign_void_envelope, esign_revise_signers (envelope_id and signatories) and esign_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 2 error.code field. Don't branch on INVALID_ARGUMENT for an argument-shape failure — read the message. INVALID_ARGUMENT is still what you get for a refusal a tool raises after it starts, such as send: true without confirm_send: true.

  • Optional key → the call succeeds and does something else. You get a 200 and a well-formed result that answers a different question:

    • esign_list_envelopes' filters are optional, so a stray transactionId / fileId is 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 discarded signingMode: "sequential" leaves the envelope on its default parallel mode. Sending it then emails a legally-binding signature request to every signatory at once, which is precisely what asking for sequential was meant to prevent. transactionId and fieldLayout are 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.

FieldTypeRequiredDescription
file_idstring (UUID)yesThe file to be signed. Must belong to your organisation.
signatoriesarrayyes1–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_idstring (UUID)noAttach the envelope to a transaction.
sendbooleannoWhen true, open the envelope for signing immediately. Requires confirm_send: true.
confirm_sendbooleannoExplicit 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"noSigning 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_layoutarraynoField-placement layout override; omit for the default layout.
observersarraynoUp 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).

FieldTypeRequiredDescription
envelope_idstring (UUID)yesThe envelope to sign.
consent_affirmedbooleanyesMust 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.
fieldsobjectnoFill 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, or partially_signed. Any other status returns CONFLICT, 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 gets NOT_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.

FieldTypeRequiredDescription
envelope_idstring (UUID)yesThe envelope to revise.
signatoriesarrayyesThe 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"noChange the signing order. Omitted means the current mode is kept; it does not reset to "parallel".
observersarraynoThe 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 with CONFLICT as well.
  • Changing only the observers. Pass the unchanged signer list (and mode) with a new observers list. 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) is INVALID_ARGUMENT; a new signatory or observer on a sent envelope when you cannot read every document it covers is FORBIDDEN.

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.

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.