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 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.

Tools

Argument keys on this server are camelCase. Unlike the other Waafir MCP servers, waafir-esign takes fileId, envelopeId, transactionId, signingMode, fieldLayout and consentAffirmednot their snake_case spellings. The one exception is confirm_send, which really is snake_case. Result keys are camelCase too, except esign_download_signed and the pagination block, which are snake_case. This inconsistency is known and tracked; until it is resolved, use exactly 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, and you get INVALID_ARGUMENT. This is the loud, safe case, and it covers esign_create_envelope (fileId), esign_get_envelope, esign_sign (envelopeId and consentAffirmed), esign_decline, esign_void_envelope and esign_download_signed.
  • 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 transaction_id / file_id are 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 signing_mode: "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. transaction_id and field_layout 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 signingMode and transactionId, and esign_list_envelopes is only filtered if you spelled the filter correctly.

The one place this failure mode works in your favour: confirmSend (camelCase) is not an accepted spelling. It is dropped, so send: true then arrives unacknowledged and is refused — the send gate fails safe rather than firing off emails.

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
fileIdstring (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.
transactionIdstring (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.
signingMode"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 signing_mode — means "parallel".
fieldLayoutarraynoField-placement layout override; omit for the default layout.

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, whose documented fields are id, fileId, status, signingMode, createdBy, signedOutputName, signedOutputVersionCount, fieldLayout, packetPageCount and expiresAt. The response currently also carries internal storage identifiers that are not part of the contract — do not read or persist them; they may disappear without notice.

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: transactionId, fileId — both camelCase. Because they are optional, a snake_case spelling is dropped silently and the call returns the unfiltered list rather than an error; see the key note above.

Paginatedlimit (optional, 1–100, default 25) and offset (optional, default 0) are clamped rather than rejected. 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: envelopeId. The envelope additionally carries transactionId, createdAt, updatedAt, and signatories (each { id, email, name, status, signedAt }).

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
envelopeIdstring (UUID)yesThe envelope to sign.
consentAffirmedbooleanyesMust be literally true — an explicit affirmation of the consent statement. There is no default; omitting it, or passing false, fails the call.
fieldsobjectnoFill values keyed by field label; name and date default when omitted.

Returns { envelope }.

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.

One caller-fixable failure currently arrives as INTERNAL. If the envelope's layout has required fields and fields does not supply them, the refusal is a 422, and 422 has no entry in this surface's status-to-code map — so it surfaces as INTERNAL, the one code the table tells you not to retry, with a generic message rather than the missing labels. It is retryable once you fill the fields. Until that mapping is fixed, do not treat an INTERNAL from esign_sign as unfixable without first checking the layout's required fields; the in-app signing screen names them.

esign_decline (write)

Decline your own pending slot (terminal — the envelope becomes declined). Input: envelopeId. 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: envelopeId. Returns the voided envelope.

  • 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_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, fileId, documentName, status, createdAt }] }.

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: envelopeId. Returns { download_url, expires_in_seconds } — note both keys are snake_case here, unlike the rest of this server. 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.