eContract Developers

AI agents

How an AI agent sends contracts for e-signature with eContract, end to end

An AI agent can do the whole contract job in eContract: prepare the document, send it, follow signing, give each signer their link, and collect the signed PDF and the audit trail. The one thing it can never do is sign.

Two ways in

MCP serverREST API
Best forChat assistants and IDE agents (Claude, ChatGPT, Cursor, VS Code)Your own agent runtime, backends, workflows
Endpointhttps://api.econtract.online/mcphttps://api.econtract.online/api/v1/…
What you writeNothing: the client lists the toolsHTTP calls (quickstart)
AuthOAuth (the client handles it) or an API key headerAPI key or OAuth access token
DocumentsUp to 10 MB through the connectorUp to 50 MB per file; a request body is limited to 50 MB, so use files[].url for large files (limits)
EventsPoll get_contractPoll or webhooks

Both paths reach the same API with the same rules: scopes, role cap, rate limits, idempotency and the human-signs rule.

Signing is human

API keys and OAuth tokens are never allowed to sign or decline on anyone's behalf, manage saved signatures or signing certificates. Those calls answer 403 HUMAN_ACTION_REQUIRED. Instead:

  • External signers get a personal link (https://econtract.online/sign/<token>). They open it, confirm their identity with a one-time code sent to their email, and sign or decline themselves.
  • Internal signers (members of the workspace) get a link to the contract in the dashboard and sign there while signed in.
  • With sequential signing each signer can sign only after the previous ones.

Rules for agent builders:

  1. Confirm with the user before sending, voiding or reminding: these contact real people.
  2. Give each signer only their own link, privately. A link lets its holder act as that signer once they pass the email check.
  3. Never pretend a contract is signed until status is completed.

Choose a credential

API keyOAuth
Acts asThe admin who created the keyThe person who connected the app
Set upDashboard → API Keys, paste the key into your configThe user clicks Connect and approves in the browser
WorkspaceThe key's workspaceThe workspace the user picked on the consent screen
ScopesTicked when the key is createdRequested by the app, can be reduced by the user
LifetimeUntil revoked or its expiry dateAccess token 1 h, refresh token 30 days (rotating), grant until revoked
RevokeDashboard → API KeysThe user: Dashboard → Connected apps
Pick it whenOne team runs the agent for its own workspace, headlessMany users connect their own workspaces, or you ship a client app

Minimal scopes for the flow below: contracts:read contracts:write. Add templates:read for templates, webhooks:write to register webhooks, ai:use for AI analysis.

The flow

send ──► processing ──► ready (in_signing) ──► signing links ──► people sign ──► completed
            │                                        │                              │
       poll GET /contracts/{id}               POST /signing-links          signed PDF + audit trail
       or webhook contract.processing.*       (or email invitations)       GET /document, /audit-trail

1. Send

POST /api/v1/contracts/send creates the contract, stores the document and queues it for processing, all in one call. Give exactly one document source:

SourceField
Inline filefiles: [{ "filename": "nda.pdf", "contentBase64": "JVBERi0…" }] (PDF or DOCX, optional contentType)
File on the webfiles: [{ "url": "https://example.com/nda.pdf" }] (https, public host, no redirects, 30 s, 50 MB)
TemplatetemplateId + templateValues
Uploaded earlierfileKey (from POST /api/v1/files/upload)
Multipartmultipart/form-data with files parts and a metadata JSON string

Up to 10 files; DOCX files are converted to PDF. Choose how signers hear about it with delivery:

deliveryEffect
email (default)eContract emails each signer an invitation (in order for sequential signing)
linkNo invitation emails at all, also not for the following signers in sequential order: fetch the links and hand each one out yourself
bothInvitations are emailed and you can fetch links too
{
  "title": "Mutual NDA - Acme / Globex",
  "signingOrderType": "parallel",
  "signers": [
    { "signerType": "external", "name": "Alice Johnson", "email": "[email protected]", "signOrder": 1 },
    { "signerType": "external", "name": "Bob Smith", "email": "[email protected]", "signOrder": 2 }
  ],
  "files": [{ "filename": "nda.pdf", "contentBase64": "JVBERi0xLjcK…" }],
  "delivery": "link",
  "expiresInDays": 30
}

The answer is 202 Accepted with the contract in processingStatus: "processing". Send an Idempotency-Key so a retry cannot send the contract twice.

2. Wait for processing

Poll GET /api/v1/contracts/{id} every 2–5 seconds until processingStatus is ready (the contract is then in_signing), or subscribe to contract.processing.completed and contract.processing.failed. On failure processingError says why and the contract stays visible with processingStatus: "failed"; fix the input and send again.

POST /api/v1/contracts/{id}/signing-links (contracts:write) returns a link for every signer. It sends no email.

{
  "data": [
    {
      "signerId": "3a7f…",
      "name": "Alice Johnson",
      "email": "[email protected]",
      "signerType": "external",
      "status": "pending",
      "signingUrl": "https://econtract.online/sign/7Kf2…",
      "expiresAt": "2026-11-09T09:00:00.000Z"
    },
    {
      "signerId": "9b2c…",
      "name": "Bob Smith",
      "email": "[email protected]",
      "signerType": "external",
      "status": "signed",
      "signingUrl": null,
      "expiresAt": null
    }
  ]
}
  • Every call mints a fresh link for each signer who still has to act; older links keep working.
  • Signers who already signed or declined get signingUrl: null.
  • Internal signers get the dashboard link to the contract and expiresAt: null.
  • External links expire with the contract (30 days when the contract has no later expiry).
  • Only while the contract is pending or in_signing; otherwise 409 CONTRACT_NOT_IN_SIGNING (also while it is still processing).

To email an invitation later instead, call POST /api/v1/contracts/{id}/resend-signing-request (optionally { "signerId": "…" }).

4. Follow signing

GET /api/v1/contracts/{id} lists each signer with status (pending, notified, viewed, signed, rejected, expired) and timestamps. Or listen for signer.viewed, signer.completed (one per signature), signer.declined and contract.completed. A decline voids the contract. To cancel it yourself: POST /api/v1/contracts/{id}/void with an optional reason.

5. Collect the signed PDF and the audit trail

When status is completed:

curl -o signed.pdf -D headers.txt \
  "https://api.econtract.online/api/v1/contracts/{id}/document?version=signed" \
  -H "Authorization: Bearer cl_live_your_key_here"
QueryReturns
(none)The signed PDF when completed, otherwise the current file
version=signedThe signed PDF; 409 DOCUMENT_NOT_SIGNED until completed
version=originalThe unsigned upload (the PDF rendition of a DOCX); 404 ORIGINAL_NOT_AVAILABLE if it was not kept
fileId=…A specific file of a multi-file contract (default: the first)

Response headers: Content-Type: application/pdf, Content-Disposition: attachment; filename="…-signed.pdf", X-Document-Version (signed, original or current) and X-Document-Sha256.

GET /api/v1/contracts/{id}/audit-trail returns the evidence in JSON:

{
  "contract": { "id": "8c1e…", "code": "CTR-…", "title": "Mutual NDA - Acme / Globex", "status": "completed", "createdAt": "…", "completedAt": "…" },
  "signers": [
    { "id": "3a7f…", "name": "Alice Johnson", "email": "[email protected]", "signerType": "external", "status": "signed",
      "signOrder": 1, "notifiedAt": null, "viewedAt": "…", "signedAt": "…", "rejectedAt": null, "rejectionReason": null, "ipAddress": "…" }
  ],
  "events": [
    { "type": "created", "source": "activity", "actorName": "…", "actorEmail": "…", "at": "…", "ip": "…", "userAgent": "…", "details": {} },
    { "type": "signer_viewed", "source": "workflow", "actorName": "Alice Johnson", "actorEmail": "[email protected]", "at": "…", "ip": null, "userAgent": null, "details": { "signerId": "3a7f…" } }
  ],
  "chain": { "valid": true, "checkedAt": "…", "totalEvents": 7, "chainedEvents": 7, "verifiedEvents": 7, "unchainedEvents": 0 },
  "documentSha256": "4f1c…",
  "documentVersion": "signed"
}
  • events merges the hash-chained contract activity log (source: "activity") with signing workflow events (source: "workflow"), oldest first.
  • chain.valid says whether the activity hash chain verifies (brokenAt points to the first broken link otherwise).
  • documentSha256 is the hash of the file GET /document returns by default; compare it with X-Document-Sha256 of your download.

With MCP

The same flow with tools: send_contract → get_contract (until ready) → get_signing_links → get_contract / send_reminder → download_signed_document + get_audit_trail. Connect a client in a minute: MCP server.

Checklist

  • Credential with the smallest scope set (contracts:read contracts:write to start)
  • User confirmation before send, void and reminders
  • Idempotency-Key on every write, the same key on retries
  • Links handed to each signer privately, never signed by the agent
  • Webhook signature verification and de-duplication by event id
  • Backoff on 429 (Retry-After) and on 5xx

On this page