Skip to content

Signing with an agent

An AI agent is not a legal person. It cannot form intent, cannot be a party, and cannot hold an ESIGN/UETA signature of its own. Every mark DocTransit applies is the registered principal’s stored appearance, under a scoped Signing Mandate. The agent is a channel. There is no silent sign_document tool.

This is product documentation, not legal advice. Mandate consent copy has not been reviewed by counsel.

The wire protocol for two agents closing a deal is DASP. This page is how signing works for a single principal and their key.

What a mandate is

A Signing Mandate is an open authorization, not a signature. The principal consents once (clickwrap + email OTP) that a specific API key may later apply their stored mark, inside a scope they set.

FieldMeaning
Confirmation levelL1 — principal confirms each apply. L2 — in-policy auto-sign + email notify.
Modeoutbound (docs the principal sent), inbound (docs sent to them), or both.
ExpiryOptional. Expired mandates cannot apply a mark.
Daily L2 cap25 applies per mandate per UTC day.

Revoke anytime in Developer → Signing Mandates. Revocation does not unwind completed documents.

documents:sign is not on a key by default. Grant attaches it. Revoking the last active mandate for that key removes it.

Prerequisites

  1. Business+ plan (API access).
  2. An API key (Developer → API Keys).
  3. A stored signature under Settings → My Signature. L1/L2 apply that image only — the model cannot upload a new scribble.
  4. A Signing Mandate for that key.
  5. A sent document where the principal is a signer, with at least one signature widget.

Required widgets the stored appearance cannot fill (text input, checkbox, dropdown, and similar) block apply (unfilled_fields). Auto-filled types: signature, initials (stored image, or letters from the principal’s name), date, name, email.

Grant (human, in the app)

  1. Open Developer → Signing Mandates.
  2. Choose the API key, a name, L1 or L2, inbound/outbound/both, and optional expiry.
  3. Accept the clickwrap (mandate-consent-v2).
  4. Confirm the email OTP.

The grant is the intent event. It does not stamp any PDF.

GET /v1/me returns signing_mandate when this key has an active mandate, plus flags and allowed_confirmation_levels. GET /v1/mandates lists mandates granted to this key.

L1 — confirm each signature

Use L1 when a human should see the document before the mark is applied.

create / send document

POST /documents/:id/sign-requests     (MCP request_sign)

principal opens confirm_url

Confirm and apply my signature

stored appearance stamped → PDF signed → document.signed
bash
curl -s https://api.doctransit.com/v1/documents/$DOC_ID/sign-requests \
  -H "Authorization: Bearer $DOCTRANSIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"reason":"NDA matches standing policy"}'

The response includes confirm_url (https://app.doctransit.com/sign-request/{token}). DocTransit also emails that link. The request expires in 24 hours. Decline leaves the document unsigned.

A pending request already exists → 409 pending_exists. Call GET /documents/:id/sign-requests instead of creating another.

L1 works with an L1 or L2 mandate. An L2 key can still escalate to confirm.

L2 — in-policy auto-sign

Use L2 when the principal has already authorized applies inside the mandate scope and does not want a per-document click.

create / send document

POST /documents/:id/sign              (MCP sign_as_principal)

mandate + flag + widgets checked

stored appearance stamped → PDF signed → email notify → document.signed
bash
curl -s https://api.doctransit.com/v1/documents/$DOC_ID/sign \
  -H "Authorization: Bearer $DOCTRANSIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"reason":"in-policy NDA"}'

An L1 mandate calling this endpoint gets 403 confirmation_required — use request_sign. If l2_signing is off, the endpoint returns 403 feature_disabled.

Production currently has l2_signing on (DOCTRANSIT_FF_L2_SIGNING=true). The code default is still off when NODE_ENV=production unless that env (or DOCTRANSIT_ALLOW_L2) is set. See feature flags.

L2 is not silent: mandate grant + disclosed certificate + email after every apply + 25/day cap.

What happens on apply

Both L1 confirm and L2 sign run the same settlement path:

  1. Mandate must be active and cover the document (principal is a signer; mode matches inbound/outbound).
  2. Document must be sent and not already signed by the principal.
  3. Strict signing order, if enabled, must not be waiting on a prior signer.
  4. Widgets must be stampable; stored signature bytes must be a readable PNG/JPEG.
  5. SHA-256 of the current PDF is recorded as pre_sign_hash.
  6. The stored appearance is drawn onto the principal’s widgets (pdf-lib).
  7. The existing signPdf path digitally signs the file and writes the audit trail.
  8. Webhooks: document.signed (and document.completed if every signer is done).
  9. L2 also emails the principal. L1 already confirmed in the app.

The completion certificate discloses the principal, Signer (via authorized agent), and Mandate : {id} (L1|L2). The agent is never named as a party.

GET /documents/:id works for documents the principal owns and inbound documents where they are a signer.

Feature flags

Deploy-time switches, not plan entitlements. Resolution per flag:

  1. DOCTRANSIT_FF_<NAME> (e.g. DOCTRANSIT_FF_L2_SIGNING=true)
  2. Legacy alias (DOCTRANSIT_ALLOW_L2 for l2_signing)
  3. DOCTRANSIT_FF JSON blob ({"l2_signing":true})
  4. Registry default (l2_signing is off in production unless overridden)

GET /v1/flags and GET /v1/me return the snapshot. The app calls Parse getfeatureflags. Off flags hide the corresponding UI and return 403 feature_disabled.

FlagEnvDefault
l1_signingDOCTRANSIT_FF_L1_SIGNINGon
l2_signingDOCTRANSIT_FF_L2_SIGNINGon in non-production; off in production unless env is set
agent_contactsDOCTRANSIT_FF_AGENT_CONTACTSon
agent_offersDOCTRANSIT_FF_AGENT_OFFERSon (requires contacts)
agent_editorDOCTRANSIT_FF_AGENT_EDITORon

Surfaces

SurfaceL1L2
AppDeveloper → Signing Mandates; /sign-request/:tokenSame grant UI when L2 is allowed
RESTPOST /documents/:id/sign-requestsPOST /documents/:id/sign
MCP 0.3+request_sign / list_sign_requestssign_as_principal
CLI 0.3+doctransit docs request-signdoctransit docs sign
SDK 0.3+client.requestSign(id, reason)client.signAsPrincipal(id, reason)

Published npm 0.2.x clients can create and send documents but do not include the mandate tools. Use the Agents API, or MCP/CLI/SDK 0.3+.

Errors

CodeWhen
feature_disabledFlag off for this deployment
mandate_required / mandate_inactiveNo active mandate for this key
confirmation_requiredL1 key called POST /sign
principal_not_signerPrincipal is not a signer on the document
mandate_scopeMode is outbound-only or inbound-only and does not cover this doc
missing_appearanceNo readable signature under Settings → My Signature
missing_signature_fieldsNo signature widget for the principal
unfilled_fieldsRequired widgets the appearance cannot fill
already_signedPrincipal already signed
pending_existsAn open L1 request already exists
strict_orderPrior signer has not signed
rate_limitedL2 daily cap (25) reached
sign_failedDigital signature step did not succeed

What this will not do

  • Treat API key possession as intent to sign.
  • Let a model upload a new scribble as the principal’s signature.
  • Hide the agent on the certificate.
  • Let tenant A’s agent sign as tenant B.
  • Unwind completed documents when a mandate is revoked.
  • Enable L3 silent corporate auto-sign (mandate-consent-v2 does not grant it).