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:
| AP2 | DASP |
|---|---|
| Open mandate | Signing Mandate grant in the app (clickwrap + email OTP). Standing authority, not a signature. |
| Closed mandate | An offer bound to document_id + pre_sign_hash. Exact record, expiry, optional human_signature_required. |
| Human-present / not-present | L1 confirm vs L2 in-policy sign_as_principal + notify — see Signing with an agent |
| Receipt | Completion 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 (
invited→active→revoked). 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
{
"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.
{
"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_signingenabled, and nothuman_signature_required→sign_as_principal(stored appearance only). Statusaccepted. Receipt fields:mandate_id,confirmation_level,document_hash. - L1,
l2_signingdisabled, out of policy, orhuman_signature_required→ createrequest_sign. Statusescalated. Principal confirms in the app; then status becomesaccepted.
POST /v1/offers/:id/escalate always takes the L1 path.
The sign attestation is the existing audit row plus webhook document.signed:
{
"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
| DASP | Agents API |
|---|---|
| Invite / accept / revoke contact | POST /contacts, /contacts/accept, /contacts/:id/accept, /contacts/:id/revoke |
| Offer | POST /documents/:id/offers |
| Ack / decline | POST /offers/:id/ack, POST /offers/:id/decline |
| Accept (L2 or escalate) | POST /offers/:id/accept |
| Force L1 | POST /offers/:id/escalate |
| Open mandate | App grant — GET /mandates |
| Closed-mandate apply | POST /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.