Skip to content

DASP — DocTransit Agent Signing Protocol

Version 0.1. This is the settlement layer DocTransit implements today, not an IETF draft and not legal advice.

An AI agent is not a legal person. DASP never lets a key stamp a PDF by itself. Every signature remains the registered principal’s ESIGN/UETA mark, applied under a scoped mandate. Agents are channels.

DocTransit is the verifier and notary: it checks the mandate, hashes the PDF, stamps the stored appearance, writes the audit trail, and issues the completion certificate. Adjacent protocols (A2A transport, A2CN negotiation, Concordia commitments) can carry these messages later. They do not replace this layer.

Map to AP2 and delegated e-sign

AP2 settles payments. DASP reuses the same words for PDF contracts:

AP2DASP
Open mandateSigning Mandate grant in the app (clickwrap + email OTP). Standing authority, not a signature.
Closed mandateAn offer bound to document_id + pre_sign_hash. Exact record, expiry, optional human_signature_required.
Human-present / not-presentL1 confirm vs L2 in-policy sign_as_principal + notify — see Signing with an agent
ReceiptCompletion certificate + document.signed / offer.accepted webhooks. Dual attribution: principal + authorized agent + mandate id.

This matches production delegated e-sign UX (DocuSign Shared Access Sign, Adobe sign-on-behalf): the certificate says who signed and that it was applied via a named agent. It does not name the agent as a party.

Reserved, not implemented: counter, revoke_mandate (future authority only — completed documents stay signed), A2A DataPart urn:doctransit:dasp:v1.

Parties

  • Principal — registered DocTransit user (human or entity). The only legal signer.
  • Agent — API key acting for that principal. Never a party.
  • DocTransit — settlement: mandate check, hash, stamp, audit, certificate.
  • Agent Contact — mutual recognition (invitedactiverevoked). Not a signature.

Both principals need an active contact and an active Signing Mandate before accept can apply a mark.

Messages

Events are JSON. Tenant webhooks use the existing HMAC envelope (X-Webhook-Signature). An optional per-contact inbox_url receives the same event + data.

Contact

json
{
  "event": "contact.invited",
  "data": {
    "contact_id": "…",
    "pair_id": "uuid",
    "counterparty_email": "beau@example.com",
    "accept_url": "https://app.doctransit.com/agent-contacts?accept=…"
  }
}

Also: contact.accepted, contact.revoked.

Offer (closed mandate)

POST /v1/documents/:id/offers creates this. Document must already be sent. Counterparty must be a signer.

json
{
  "event": "offer.received",
  "data": {
    "offer_id": "…",
    "document_id": "…",
    "document_name": "NDA — Acme",
    "from_email": "ada@example.com",
    "to_email": "beau@example.com",
    "terms": "Standard NDA",
    "pre_sign_hash": "sha256 hex of the current PDF",
    "human_signature_required": false,
    "expires_at": "2026-09-18T00:00:00.000Z"
  }
}

Also: offer.sent (to the offeror), offer.acked, offer.declined, offer.escalated (includes confirm_url), offer.accepted.

Accept / escalate / sign

POST /v1/offers/:id/accept (scope documents:sign):

  • L2 and in policy, l2_signing enabled, and not human_signature_requiredsign_as_principal (stored appearance only). Status accepted. Receipt fields: mandate_id, confirmation_level, document_hash.
  • L1, l2_signing disabled, out of policy, or human_signature_required → create request_sign. Status escalated. Principal confirms in the app; then status becomes accepted.

POST /v1/offers/:id/escalate always takes the L1 path.

The sign attestation is the existing audit row plus webhook document.signed:

json
{
  "event": "document.signed",
  "data": {
    "document_id": "…",
    "principal": { "email": "beau@example.com", "name": "Beau" },
    "agent": { "api_key_id": "…", "label": "ops-bot" },
    "mandate_id": "…",
    "confirmation_level": "l1",
    "pre_sign_hash": "…"
  }
}

document.completed is the dual-attestation receipt when every signer has signed. The PDF certificate discloses Signer (via authorized agent) and Mandate : {id} (L1|L2).

HTTP mapping

DASPAgents API
Invite / accept / revoke contactPOST /contacts, /contacts/accept, /contacts/:id/accept, /contacts/:id/revoke
OfferPOST /documents/:id/offers
Ack / declinePOST /offers/:id/ack, POST /offers/:id/decline
Accept (L2 or escalate)POST /offers/:id/accept
Force L1POST /offers/:id/escalate
Open mandateApp grant — GET /mandates
Closed-mandate applyPOST /documents/:id/sign (L2) or /sign-requests (L1)

MCP: invite_contact, propose_contract, accept_offer, decline_offer. There is no sign_document tool.

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