# HESigns integration guide Target origin: **https://hesigns.homeequal.ai**. This is the intended address, not confirmation of deployment. The database runtime is separate from the local demo on port 4317. ## 1. Create a customer workspace An operator enrolls the company, its monthly allowance, and exact allowed application origins with `production/admin.mjs create-tenant`. The first key goes into a private output file, not a log. Use a separate workspace for sandbox and production. The database runtime now also provides `/account` and `/workspace`, with optional verified signup, private owner/team invitations, MFA, roles and optional hosted billing. See `ACCOUNTS-BILLING.md`. Google Workspace SSO and SCIM user provisioning are implemented; provider setup and acceptance remain required. See [CAPABILITIES-0.7.md](CAPABILITIES-0.7.md). Send `X-API-Key` only from your server. Keys expire after 90 days by default, support up to 365 days, and are stored as hashes. Issue replacement credentials with `POST /v2/credentials`, deploy the replacement in your application, then revoke the old key with `DELETE /v2/credentials/{id}`. A key can issue only scopes it already has. Keep the administrator key out of the application runtime. | Scope | Permission | |---|---| | `envelopes:read` | Read owned agreements, private signer links and artifacts. Treat this as sensitive access. | | `envelopes:write` | Prepare, create and void agreements; preview field placement. | | `envelopes:send` | Queue invitations, due reminders and completed copies. | | `templates:read`, `templates:write` | Read or change your versioned role templates and public signing forms. Preparation is a write-scope operation. `templates:read` also reads the workspace brand. | | `templates:approve` | Approve a template version, withdraw approval, and require approval before use. Owners and admins hold it; senders do not. | | `sessions:write` | Create and revoke embedded sessions for enrolled origins. | | `events:read` | Read agreement events and the durable cursor stream. | | `webhooks:manage` | Configure callbacks, inspect delivery jobs and request replay. | | `credentials:manage` | List metadata, issue scoped keys and revoke keys. | | `usage:read` | Read UTC monthly usage, allowance and delivery backlog. | | `exports:read` | List completed agreements and download batched proof-kit archives (1–10 agreements, up to 32 MiB of artifacts). | | `retention:manage` | Inspect retention, set/release legal holds, schedule/cancel closed-agreement deletion. Grant deliberately. | | `audit:read` | Read the admin audit log (`GET /v2/audit-log`, `/verify`, `/export.json`) for a SIEM. Reads nothing else. Workspace owners and admins hold it. | | `brand:manage` | Set or clear the name, accent colour and logo signers see on signing pages, public forms and emails. Workspace owners and admins hold it. | ## 2. Create an agreement Copy `sdk/server.mjs` into your server project. It has no external dependencies and requires Node 22. Use a harmless sample agreement first. Persist a random idempotency key in your own database **before** making the request. If the response is lost, retry the same body with that same key. A changed body with an existing key returns 409. The Node SDK retries one dropped connection for GET or idempotent creation only. It does not automatically repeat invitations or retry HTTP failures. Inspect status, request ID and Retry-After before retrying other operations. Python 3.10+ customers can download `/sdk/python.py` as `he_signs.py`. It uses only the standard library, verified HTTPS, no redirect forwarding, structured API errors and raw-byte webhook verification: ```python from he_signs import HESigns, verify_webhook signs = HESigns(api_key=server_side_secret) agreement = signs.create_envelope(prepared_agreement, persisted_idempotency_key) signs.invite(agreement["id"], signer=0) ``` JavaScript, Python and .NET SDKs are tested against the local HTTP API. They do not automatically retry non-idempotent actions or silently acknowledge webhook events. ```js import { readFile } from 'node:fs/promises'; import { HESigns } from './server.mjs'; const signs = new HESigns({ apiKey: process.env.HE_SIGNS_API_KEY }); const agreement = await signs.createEnvelope({ client_reference: 'your-order-identifier', document: { name: 'Service agreement.pdf', base64: (await readFile('./agreement.pdf')).toString('base64') }, routing: 'sequential', participants: [{ name: 'Alex Example', email: 'alex@example.com' }], fields: [{ signer: 0, type: 'signature', page: 1, x: 60, y: 100, width: 240, height: 55 }] }, persistedIdempotencyKey); ``` Coordinates use PDF points from the bottom-left, not screen pixels. Pages are one-based; signer indexes are zero-based. Every signer requires a signature field. Available types: `signature`, `initials`, `full_name`, `email`, `signed_date`, `date`, `text`, `checkbox`. Only date, text and checkbox accept signer-supplied values. `signed_date` records the UTC signing date. The sender must review all auto-placement suggestions before creating the agreement. Current bounds: up to 10 PDFs with 8 MiB combined source bytes and 100 pages, 1–25 signers, 200 fields and unrotated pages. Text detection supports existing text, literal anchors and explicit tags; optional local English OCR handles up to 10 scanned pages per pass. All suggestions require sender review. Reusable templates support 100 pages, 100 templates per workspace and 50 immutable versions. Choose an exact version and assign every role through `/v2/templates/{id}/prepare`, then create the returned envelope payload with a new idempotency key. For customer-controlled authorization, enroll only your **public** Ed25519 key with the service operator. Ask `/v2/envelopes/manifest` for the exact manifest, canonicalize and sign it on your own server, then supply `sender_authorization.signature` during creation. `proof.js` defines the canonical serialization. Never send a customer private key to HESigns. Key enrollment changes require controlled operator configuration and a restart in this release; self-service customer signing-key rotation is not implemented. ## 3. Email or embed `await signs.invite(agreement.id, 0)` returns **202**, meaning durably queued. It does not mean emailed. A separate worker sends the invitation. Recipient delivery state `sent` means SMTP accepted the message, not that an inbox received it. Reminders are limited to three successful reminders, at least 24 hours apart. Ordered signing queues the next person's invitation after the previous signature commits. Completion queues a signed-copy message for each recipient. For an embedded experience, your server creates a session only after it authenticates its own user and confirms that the user may act as the selected signer: ```js const session = await signs.embeddedSession(agreement.id, { signer: 0, parent_origin: 'https://app.example.com' }); // Return only session.session_url to your authenticated browser user. ``` Then, in that browser: ```js import { mountSigning } from './browser.mjs'; const embedded = mountSigning({ container: document.getElementById('signing'), sessionUrl: session.session_url, onEvent(event) { if (event.type === 'signed') refreshStatusFromOurServer(); } }); // On navigation: embedded.destroy(); ``` Sessions expire after 15 minutes or at the agreement's expiry, whichever comes first. Renew from your authenticated server when needed. Revoke independently through `DELETE /v2/embedded-sessions/{session_id}`. Each session restricts framing to the exact enrolled origin through CSP. The browser SDK checks the sender origin **and** iframe window. `ready`, `signed`, `declined`, and `expired` events help the interface; they are not authoritative business confirmation. An expired frame explains how to reopen the session. Confirm status from your server or a verified webhook before releasing goods, changing a contract, or performing another business action. An embedded session remains a bearer capability; origin restrictions prevent unauthorized framing, not identity impersonation by someone who steals the URL. Do not log, forward, index, or place session URLs in analytics. ## 4. Receive and recover events Enroll `PUT /v2/webhooks` with `{ "url": "https://your-server.example/events/he-signs" }`. The generated signing secret is returned once. Store it in your secret manager. Existing queued events keep a snapshot of the old URL and secret when you rotate; retain the old verification secret until that backlog is drained. Disabling callbacks cancels queued jobs, though an in-flight request may finish. Callbacks contain a stable `id`, `type`, `created_at`, and `data.envelope_id`; participant events also carry `data.signer`. No signer email, document content, or private URL is included. Verify the exact raw body **before** JSON parsing: ```js import express from 'express'; import { verifyWebhook } from './server.mjs'; app.post('/events/he-signs', express.raw({type:'application/json'}), async (req,res) => { let event; try { event = verifyWebhook({ secret: process.env.HE_SIGNS_WEBHOOK_SECRET, rawBody: req.body, timestamp: req.get('HESigns-Timestamp'), signature: req.get('HE-Signs-Signature') }); } catch { return res.sendStatus(401); } await saveEventAndApplyBusinessUpdateOnce(event); // your durable transaction, unique event.id res.sendStatus(204); }); ``` Place this handler before a global JSON body parser. The SDK rejects timestamps outside five minutes. Synchronize server clocks. Deduplicate by event ID and acknowledge only after durable acceptance. Delivery is **at least once**: a network failure or process crash after receiver/SMTP acceptance can cause a duplicate. Webhook order is not guaranteed; query agreement status if an event arrives out of order. The worker retries up to eight attempts with increasing delays, then retains a dead job. Inspect `/v2/webhooks/deliveries` and deliberately replay a job when the underlying issue is fixed. Replay preserves the event ID and original destination snapshot. For backfill and recovery, poll `/v2/events?after=CURSOR`; save `next_cursor` only after processing that page. The stream includes pre-enrollment events. Page size is 100; follow `has_more`. `/v2/envelopes` and delivery inspection currently return the newest 100; use the event stream and known IDs for full synchronization. ## 5. Download and independently check Wait for `completed`, then call `await signs.proofKit(agreement.id)`. Store the returned ZIP in your own durable storage. It contains both PDFs, signed evidence and the offline verifier. Pin the HESigns public key through an independently trusted channel. Customer-authorized agreements also need your independently trusted customer public key. The key bundled with the ZIP alone is not an independent trust anchor. Evidence integrity is separate from identity, legal acceptance, and trusted time. This release records private-link possession and the signing ceremony. Optional email codes add mailbox verification; configured service certificates and RFC3161 authorities add PDF seals and timestamp evidence. Provider activation and certificate trust must be verified separately. Government identity verification, QES, long-term validation, notarization and universal jurisdictional acceptance are not established. **Signer certificate signatures (eIDAS).** Connect a qualified trust service provider's remote signing service (CSC API v2) under workspace settings, then set `signature_level: "qualified"` or `"advanced"` on a signer. That signer completes the HESigns ceremony, signs in at the provider, checks the certificate HESigns found, and approves exactly one signature over the prepared PDF revision. HESigns embeds it as a PAdES signature in an incremental update (with an RFC 3161 signature timestamp when a timestamp authority is configured, and the provider's revocation data in the document security store), so later signers, the signing record and the service seal never invalidate it. The proof lists every embedded signature. HESigns claims a qualified electronic signature only when the certificate says it is qualified and its issuer is granted on the trusted list file you configure. - **Settings.** `GET /v2/signing-provider` shows the provider (never its secret) and the `return_url` to register with it; `PUT` and `DELETE` change it and `POST /v2/signing-provider/test` checks it (owners and admins, `brand:manage`). A level the provider does not offer is refused when the agreement is created (`SIGNATURE_LEVEL_UNAVAILABLE`). - **Trusted list.** `HE_SIGNS_TRUST_LIST_FILE` names a trusted-list XML file (ETSI TS 119 612), or a directory of them, that the operator downloads; HESigns never fetches one. `HE_SIGNS_TRUST_LIST_SHA256` optionally pins it. Without a list, the proof says the issuer was not confirmed on a trusted list. - **Where they sign.** Providers do not open inside another application, so such a signer always signs on their own signing page. An embedded session shows them a "Sign on your signing page" link that opens it in a new tab (and posts `certificate_required` to your page); `POST /embed/{session}/complete` answers `409 CERTIFICATE_SIGNATURE_REQUIRED` with `signing_url`. In HEMeets their signing page opens in a new tab, and the meeting shows their signature when they finish. ## 6. Review, revisions and AI tools **Who does what.** Each participant has an `action`. `sign` (the default) places fields and signs. `approve` approves the exact version without signing it; approvers get no fields and their approval names the version. `interpret` signs an interpreter statement for the signers listed in `interprets_for`, naming the two `languages`. Every agreement needs at least one signer. ```json "participants": [ { "name": "Sofia Marin", "email": "sofia@example.com", "locale": "es" }, { "name": "Morgan Lee", "email": "morgan@example.com", "action": "approve" }, { "name": "Ana Duarte", "email": "ana@example.com", "action": "interpret", "interprets_for": [0], "languages": ["en", "es"] } ] ``` **Revising a document that is out for signature.** `POST /v2/envelopes/{id}/revisions` with an `Idempotency-Key`, a `summary` and the new `document` (or `documents`; `fields` default to the current ones). The agreement keeps its ID and history. Everyone who had not finished starts again. Anyone who opened version N, or approved it, sees each change since then word by word and initials it before they finish. They submit `acknowledged_changes` with the exact `changes_sha256`, and the proof records it as `reviewed_changes`. A closed agreement cannot be revised. Limits: 20 versions per agreement. **Comparing.** `GET /v2/envelopes/{id}/compare?from=1&to=2` returns every insertion, deletion and replacement with its page, nearest section heading and flags for amounts, dates, percentages and obligations. `compare.pdf` is the redline: a change summary, then the newer document with deletions struck through and insertions underlined. Add `?proposal=ID` to compare a pending proposal with the version it was made against. The comparison reads the text in each PDF. It does not compare layout, spacing or images. Scanned pages with no text layer are listed in `pages_without_text` because changes there cannot be detected. **Comments and proposals.** `POST /v2/envelopes/{id}/comments` anchors a comment to a quoted passage (`QUOTE_NOT_FOUND` if the words are not in that version) and may carry `suggested_text`. `POST /v2/envelopes/{id}/proposals` uploads a proposed revision; nothing changes for signers until a person accepts it. `accept` turns the proposal into the next version, exactly like a revision. It returns `PROPOSAL_OUTDATED` if the agreement changed after the proposal was made. `reject` records a reason. Limits: 20 proposals and 200 comments per agreement. **Scopes.** `review:read` reads agreements, versions, text and comparisons. `review:write` adds comments and proposals. Revising, accepting and rejecting need `envelopes:write`. In the workspace, senders have both review scopes; viewers have `review:read`. `GET /v2/review/envelopes` lists agreements without signing links, email addresses or delivery details. **AI agents.** Send `HE-Signs-Agent: Harvey` (and optionally `HE-Signs-On-Behalf-Of: morgan@firm.example`) and the comment or proposal is credited to the agent, for the person it works for. A key limited to review scopes is credited as an agent automatically. An agent can read, compare, comment and propose. It cannot sign, send, see signing links, or accept, reject or revise (`PERSON_REQUIRED`). ### Connect Harvey, Claude, Copilot or Gemini (MCP) The MCP server URL is **https://hesigns.homeequal.ai/mcp** (the intended address; the workspace shows the live one under Security → AI tools). It speaks Streamable HTTP with JSON responses, protocol `2026-07-28` and `2025-03-26` through `2025-11-25`. Tools: `list_agreements`, `get_agreement`, `read_document` (page text, paged at about 60,000 characters), `compare_versions`, `get_redline_pdf`, `add_comment`, `suggest_edits` (several quoted suggestions at once; all or none are saved), `propose_revision`. Drafts (section 6B) have their own tools: `list_drafts`, `read_draft` (every passage with its block id and section, open suggestions, open comments including internal ones, and the checks), `suggest_draft_changes` (quoted wording, replacement, reason and source; each becomes a tracked suggestion from the AI tool, shown with its reason and source, that a person accepts or rejects; all or none are saved) and `comment_on_draft` (internal to the firm). This is how a firm's own AI assistant reviews a draft against the firm's playbook: it proposes, a lawyer decides. Sign-in is OAuth 2.1. The server publishes protected-resource metadata (RFC 9728) and authorization-server metadata (RFC 8414), accepts dynamic client registration (RFC 7591), requires PKCE S256 and returns `iss` with the code. Each person approves a tool for themselves on the HESigns consent page. If the workspace requires an authenticator, they set it up first. Tokens last one hour; refresh tokens rotate and last 30 days. Anyone can disconnect their own tools under Security → AI tools. Owners and admins can disconnect anyone's. - **Harvey:** a Harvey administrator adds HESigns as a custom MCP connector with the URL above. Each lawyer approves access the first time Harvey uses it. - **Claude:** Settings → Connectors → Add custom connector, then paste the URL. - **Microsoft Copilot Studio, Google Gemini and other MCP clients:** add a remote MCP server with the URL. Clients that support dynamic registration configure themselves. Others register once with `POST /oauth/register`. - **Review services that cannot run OAuth:** issue an expiring key with only `review:read` and `review:write` and send it as `Authorization: Bearer`. Verified locally against the protocol, the OAuth flows and the consent page. It has not been exercised against Harvey, Claude, Copilot or Gemini, because nothing is deployed yet. ### Reading in another language Recipients can read the document in 20 languages while the signed document stays the original. `machine_translation: "device"` (the default) uses the translator built into Chrome and Edge, so the text never leaves the recipient's device. Other browsers show the original with an explanation. `"service"` also offers the configured provider: Microsoft Translator, REST v3.0. Configure it with `HE_SIGNS_TRANSLATOR_KEY_FILE`, `HE_SIGNS_TRANSLATOR_REGION` and optionally `HE_SIGNS_TRANSLATOR_ENDPOINT`. The provider receives page text, up to 300,000 characters per agreement. Creating a `"service"` agreement without a configured provider returns `TRANSLATION_SERVICE_UNAVAILABLE`. The proof and signing record state which pages each person read with machine translation, into which language, and whether their browser or the service did it. Machine translation is a reading aid, not a certified translation. For a person who needs one, add an interpreter participant or send a translated document. ## 6A. Template library: questionnaire templates, the attorney library and approvals A template is either a **fixed PDF** (a document plus placed fields) or a **questionnaire**: a composition of sections, questions, conditional text, clause alternatives and signature blocks that HESigns drafts into a PDF. Signature, name, date, initials, checkbox and fill-in fields are placed exactly where the drafted document prints them. The same composition and answers always draft the same bytes. - **Attorney library.** `GET /v2/templates/catalog` lists fifteen drafted agreements across engagement and ethics, litigation, transactions, employment and health records. Each has questions, clause alternatives and compliance notes that cite the rule or statute they rest on. `GET /v2/templates/catalog/{key}/preview.pdf` drafts a sample; `POST /v2/templates/catalog/{key}/install` adds it to the workspace as version 1. These are starting drafts for licensed lawyers to review; they are not legal advice. - **Drafting.** `GET /v2/templates/{id}/composition?version=` returns the interview. `POST /v2/templates/{id}/assemble` with `{version, values, choices, assignments}` drafts a preview that marks gaps in [brackets] and reports `missing` and `errors`. With `mode: "final"` every required answer and every role's name and email are needed, and the response carries an `assembly_token`. Create the agreement with `template: {id, version, assembly_token}` and the unchanged document; the agreement records `origin.assembled.verified: true`. `POST /v2/templates/{id}/prepare` also accepts `values` and `choices`, so bulk and API sends draft the same way. - **Writing and editing.** Save a questionnaire with `composition` instead of `document` and `fields` (see the `TemplateComposition` schema). `POST /v2/templates/preview` drafts an unsaved composition and answers `valid: false` with the reason when it cannot be saved yet. A drafted copy cannot be saved over a questionnaire as a fixed PDF (`ASSEMBLED_TEMPLATE`). - **Approval.** `PUT /v2/templates/{id}/approval` with `{approved: true, version, note}`, `{approved: false}` or `{require: true}`. When approval is required, preparing, final drafting, public forms and agreement creation accept only the approved version (`VERSION_NOT_APPROVED`). Every change is kept in `approval_history`. - **Versions.** Each save is a new immutable version with an optional `change_note`. `GET /v2/templates/{id}/compare?from=&to=` lists word-by-word and structural changes (roles, fields, questions, clause alternatives, compliance notes); `compare.pdf` is the redline. - **Library tools.** `category`, `tags`, `jurisdiction` and `usage_notes` on each version; `PUT /v2/templates/{id}/pin`; `POST /v2/templates/{id}/duplicate`; `GET /v2/templates/{id}/export.json` and `POST /v2/templates/import` move a template between workspaces; `GET /v2/templates/usage?days=` counts agreements, completions, verified drafts and versions used per template. - **Public forms** from a questionnaire take the sender's `values` and `choices` once, at creation; each visitor's copy is drafted with their own name. - **Counsel review.** `POST /v2/templates/{id}/versions/{version}/counsel-review` (needs `templates:approve`) records that licensed counsel reviewed that exact version: the reviewer, bar admissions, jurisdictions, date and the version's `content_sha256`, which must match. `GET` returns the status (`reviewed`, `stale` or `not_reviewed`), the label shown on the template ("Reviewed by counsel for Florida on October 5, 2026" or "Not yet reviewed by counsel") and the history. A new version shows "Not yet reviewed by counsel" until it is reviewed. `.../counsel-review/withdraw` withdraws a review with a reason; nothing is edited or deleted. `.../counsel-review/packet.docx` is a Word packet for counsel to mark up with Track Changes. - **Import from the firm's own software.** Export a form as a Word document (.docx or .dotx) or text, then `POST /v2/templates/convert` with `{file: {name, base64}}` (up to 8 MB). Merge fields, content controls, `[BRACKETED]` and `______` blanks and `«field»`, `<>` or `{{field}}` markers become questions; whole bracketed sentences become optional clauses; paragraphs separated by `[OR]` become clause choices; `«IF x»…«ELSE»…«END IF»` become conditions; drafting notes move to `usage_notes`; signature blocks become recipients. Nothing is saved until you `POST /v2/templates` with the result and its `import_source`, which is recorded as the template's source. Legacy .doc files are refused with save-as-.docx guidance, and PDFs belong in the agreement composer. ## 6B. Drafts: negotiate the text before anyone signs A draft is an agreement still being written. Your team edits it directly or suggests changes, comments with a chosen audience, shares it with the client and the other side, compares versions, and then sends exactly the agreed text for signature. All routes take `envelopes:read` to read and `envelopes:write` to change. In the signed-in workspace they are under `/workspace/api/agreement-drafts`, because `/workspace/api/drafts` holds the composer's saved preparations. - **Start.** `POST /v2/drafts` with `{template: {id, version}, values, choices, people}` resolves a questionnaire's answers and clause choices into concrete text; unanswered questions stay as `[bracketed]` placeholders. `{import: {name, base64}}` starts from a Word or text file, and `{title}` alone starts blank. Signers' names stay live as `{{role.name}}` until the draft is agreed. - **Edit.** `POST /v2/drafts/{id}/edits` with up to 50 changes: `text` (send `from`, the text you read; a changed passage returns `409 TEXT_CHANGED` and nothing is saved), `insert`, `delete`, `move`, `list_item` and `restore` (puts back a deleted block `after`, `before` or `into` a section at `index`, keeping its block ids where they are free; the editor's undo uses it). Every signer must keep a signature block. Text may contain `{{ref:}}`, a live cross-reference to a section: it shows and prints as "Section N" and renumbers when sections move. Draft checks flag a reference to a section that no longer exists (`BROKEN_REFERENCE`), a section number typed as plain text (`TYPED_REFERENCE`, with the section it most likely means) and a term defined twice (`TERM_REDEFINED`). `GET /v2/drafts/{id}?since=` returns `{unchanged: true}` when nothing changed, for cheap polling. - **Suggest and decide.** `POST /v2/drafts/{id}/suggestions` records a tracked change (`replace`, `insert` or `delete`) without making it. `POST /v2/drafts/{id}/decisions` accepts or rejects by `ids`, or every open suggestion from a `party`. A suggestion whose passage changed after it was made is `outdated`: accepting it by id needs `force: true`, and accepting by party leaves it for you. `POST /v2/drafts/{id}/suggestions/{suggestion}/withdraw` withdraws one of your own team's open suggestions; a reviewer's suggestion is accepted or rejected, never withdrawn (`403`). - **Comment.** `POST /v2/drafts/{id}/comments` anchors to a passage or a quoted phrase, with `visibility` `internal` (default; never reaches a reviewer and never prints), `client`, `counterparty` or `everyone`. Replies, resolve and delete have their own routes. - **Share for review.** `POST /v2/drafts/{id}/shares` with `{name, email, party: client | counterparty, permission: suggest | comment}` returns a private `/d/{token}` link. The reviewer sees the draft, comments meant for their side, their own suggestions and what changed since the link was created or resent. The client and the other side never see each other's comments or suggestions, or anyone's email address. Links can be turned off (`revoke`), replaced (`rotate`) or marked as sent again (`reshare`). Reviewer posts from another origin are refused. - **Versions.** `POST /v2/drafts/{id}/versions` saves a named version; versions are also saved when a draft is created, shared and agreed. `GET /v2/drafts/{id}/compare?from=&to=` compares passage by passage and word by word; `compare.pdf` is the redline and `document.pdf?version=` any version as it would print. - **Review queue.** The editor's Review panel lists, most urgent first, what blocks agreement (open suggestions by party, missing people, approval), what waits on the firm (comments assigned to someone, comments where the client or the other side spoke last) and what is unfinished (placeholders, unused defined terms, sections not yet reviewed). Each item goes to its passage and offers the next step. The primary button names the real next step: share for negotiation, resolve items, request or give approval, or prepare for signature. - **Section reviews.** `PUT /v2/drafts/{id}/reviews/{block}` with `{reviewed: true}` marks a section reviewed. The mark covers the exact text; `reviews[block].current` turns false when the section changes. - **Approval.** `PUT /v2/drafts/{id}/approval` with `{required: true}` (needs `templates:approve`) makes approval a condition of agreement. `POST .../approval/request` asks for it; `POST .../approval/decide` with `{approved: true}` (needs `templates:approve`) approves the current text and saves a version, or `{approved: false, note}` returns it. Approval records a hash of the text: any later change makes it out of date (`approval.stale`), and `finalize` refuses until it is approved again. - **Comment owners.** `PUT /v2/drafts/{id}/comments/{comment}/assign` with `{assignee: {name}, due}` or `{assignee: null}`. Reviewers never see owners or due dates. - **Our standard and our alternatives.** `GET /v2/drafts/{id}/standard?block=&field=` compares a passage with the template wording it was drafted from (or the first draft), with what the difference does in practice. For passages drafted from a template clause choice, `GET /v2/drafts/{id}/alternatives?block=` drafts each alternative with this matter's answers; apply one with an edit or a replace suggestion carrying `clause_option`. - **What changed in practice.** Comparisons, suggestions and the reviewer's view include `insights`: amounts, rates, time periods and dates that changed, "shall" becoming "may", a "not" added or removed, a definition that changed (with how often the term is used), protective wording removed (indemnity, liability limits, notice, confidentiality and similar), and passages moved unchanged. They are worked out from the text, not by a model. - **Word exchange.** `POST /v2/drafts/{id}/document.docx` with `{audience: firm | client | counterparty, suggestions: true}` returns a Word document with the numbering, lists, tables, notices and signature blocks, the comments that reader may see as Word comments, and open suggestions as tracked changes. Every passage carries a bookmark, and the text that went out is kept as a version. `POST /v2/drafts/{id}/word-return` with `{name, base64, party, reviewer}` reads the copy that comes back against that version: changed passages become replace suggestions from that party (flagged when the passage also changed here meanwhile), new paragraphs become suggested insertions, removed passages become suggested removals, and Word comments become comments; their tracked changes are read as accepted. Nothing changes until it is accepted. A file from a different draft is refused. - **Playbooks.** `POST /v2/playbooks` (needs `templates:approve`) saves positions on recurring clauses: the words that find the clause, the preferred wording, up to five fallbacks with when each is acceptable, red lines, guidance and who approves an exception. `POST /v2/playbooks/example` adds a starting playbook. Firm playbooks apply to every draft; `PUT /v2/drafts/{id}/playbooks` chooses client playbooks for a draft. `GET /v2/drafts/{id}/playbook` returns findings: `preferred`, `fallback`, `deviates` (with how much of the wording is the same), `missing` (for required clauses) and `red_line`, each with the options to use instead. `POST /v2/drafts/{id}/playbook/{position}/waive` with a reason (needs `templates:approve`) accepts an exception; it lapses when the passage changes. The check is worked out from the text. - **Obligations after signing.** `GET /v2/drafts/{id}/obligations/candidates` finds duties, deadlines, renewals and notice periods in the agreed text, with due dates worked out from the effective date (set on the draft, stated in the text, or the day signing completed). `POST /v2/drafts/{id}/obligations` tracks them with an owner and a reminder; `PUT .../obligations/{obligation}` changes the date, owner, reminder or marks it done (a repeating duty moves to its next date instead of closing); `PUT /v2/drafts/{id}/effective-date` moves computed dates. `GET /v2/obligations` lists them across every agreement, and `GET /v2/obligations.ics` (or the per-draft `.ics`) gives a calendar with reminders and repeating schedules. When the owner has an email address, the production runtime emails them once when the reminder window opens and once when it falls due, through the same delivery worker as invitations. Parties are read from the text, including "you" and "we" when the opening paragraph defines them. - **Amendments.** `POST /v2/drafts/{id}/amend` starts an amendment from the signed text of an agreed or sent draft. Its first version is the signed text, so the amendment's changes compare and sign like any draft, and the two drafts link to each other. - **Working together.** Each open editor sends `PUT /v2/drafts/{id}/presence` with a session id and the passage it is editing, every few seconds, and `DELETE .../presence/{session}` on close. `GET /v2/drafts/{id}/live` is a server-sent event stream of `{revision, people}`, sent whenever either changes, so every open editor updates at once and shows who is editing which passage. Edits still carry `from`, so two people can never overwrite each other. The production runtime keeps presence in the database so every instance sees it. - **Drafting assistant.** Off unless the server operator configures a provider and an owner or admin turns it on for the workspace with `PUT /v2/assistant` `{enabled: true, acknowledged: true}`. `POST /v2/drafts/{id}/assist` with `action` `explain`, `explain_client`, `mutual`, `plain`, `position` (with a playbook `position`) or `custom` (with an `instruction`) returns an explanation, or proposed wording with a rationale and any missing facts as bracketed placeholders; nothing changes until the wording is applied as an edit or a suggestion. The editor can also fit every passage that differs from a playbook in one pass (one `position` call per finding, for every finding), for the lawyer to use or suggest each result. The provider is sent the passage, its section title, the agreement title and the defined terms, with people's names replaced by their roles; comments, other passages and other drafts are never sent. Each use is recorded in the draft's activity, and a workspace hourly limit applies. Operator settings: `HE_SIGNS_AI_API_KEY_FILE` (or `HE_SIGNS_AI_API_KEY` locally), `HE_SIGNS_AI_MODEL` (default `claude-fable-5-1`) and `HE_SIGNS_AI_LIMIT_PER_HOUR` (default 200). The provider is Anthropic's Messages API; check its data terms against the firm's confidentiality duties before turning it on. - **Formatting and structure.** Passage text carries `**bold**`, `italic`, `underline` and footnotes as `…`, which number themselves and print at the foot of the page. A section can hold subsections (the `nest` edit moves a passage in or out a level), numbered 1, 1.1, 1.1.1; list items take a `level` from 0 to 3 and keep a stable `key`, numbered (a), (i), (A). `{{ref:}}` points at a section or subsection and `{{ref:.}}` at a list item, shown and printed as "Section 2.1(a)" and renumbered when things move. An `exhibit` block (`kind` exhibit, schedule, annex or appendix) starts a new page and is lettered or numbered in order. `POST /v2/drafts/{id}/images` with `{name, base64}` stores a PNG or JPEG (up to 2 MB each; 30 pictures and 8 MB per draft) for an `image` block or the header logo; `GET .../images/{image}` returns it. `PUT /v2/drafts/{id}/layout` sets `header_text` and `footer_text` (one line each, up to 120 characters), `header_image` and `font` (`times` or `helvetica`). Until a draft is agreed, its PDFs carry a "DRAFT FOR DISCUSSION" mark naming the version. Approval covers the layout as well as the text. Word export and import carry all of it: real Word numbering, footnotes, headers and footers with page numbers, pictures and exhibits. - **Editing as one document.** `move` also takes `after`, `before` or `into` with `index`, so a passage or section goes anywhere in one change (exhibits stay at the top level); references to it follow. An insert can be a `table` (`{cols, rows}` or `{columns, rows}`, up to 6 columns and 100 rows, the first row a header) or a `contents` block (`{title, depth: 1 | 2}`), a table of contents whose page numbers are worked out in the PDF and which Word refreshes. Paragraphs and headings take `align`: `center`, `right` or `justify`. Text also takes `strikethrough` and `highlight`. Highlights are drafting marks: previews and Word copies show them, draft checks list each one as `HIGHLIGHTED`, and the agreed document (and a template's final drafting) prints without them. A request carries up to 200 changes. The editor plans its structural edits from these operations: a selection across passages and sections deleted in one step (a heading inside it goes, and what followed it joins the section before), Backspace and Delete joining passages, paste from Word, Google Docs or the web that keeps paragraphs, numbering, lists, tables, footnotes and formatting, copy and cut that keep a passage's identity within a draft so its references follow, turning a passage into another kind, and dragging sections in the outline. Each is one undo step. - **Suggesting changes to the structure.** `POST /v2/drafts/{id}/suggestions` (and a reviewer's `/d/{token}/suggestions`) also takes `{kind: "structure", changes, summary}`: edit operations, as for edits, tried on a copy when suggested, and applied together, or not at all, when accepted. Moves, joins, a passage turned into another kind, alignment and column widths are suggested this way. The firm's editor may name the suggestion; a reviewer's is always summarized by the server ("Move Section 9 “Your File” to before Section 1 “General Terms”"), so the other side cannot mislabel a change. The suggestion goes out of date (`outdated: true`) once a passage it changes is edited; accepting then needs `force` and is refused if the change no longer fits. The document shows each as a "Suggested: …" mark on its passage. - **Table column widths.** A table may carry `widths`: whole percentages, one per column, adding up to 100 (each 5 to 95). The PDF, the editor, the review page and Word use them, and a Word file's column grid comes back as widths. Without them columns are equal, exactly as before. - **Merged table cells.** A table may carry `merges`: `[{row, col, rows, cols}]`, where `row` -1 is the header row (which merges only across). Merged cells never overlap and stay inside the table; a list that breaks either rule is dropped rather than stored. A cell hidden inside a merged cell keeps empty text and is not editable (a `text` edit to it is refused with 400), and drafts never list it as a passage. The PDF, the editor, the review page, Word export and Word import all keep merged cells. Without `merges`, tables print exactly as before. - **Spelling.** `GET /v2/drafts/{id}/spelling` returns `{revision, items}`, each item `{block, field, word, offset, length, context}` (and `note` for words inside a footnote), with offsets into the passage as a reader sees it. Defined terms, the people and organizations in the draft, legal vocabulary, and names used more than once all count as known, unless a name is one slip from a real word. `GET .../spelling/suggestions?words=a,b` returns up to five suggestions for each of up to 40 words. `POST .../spelling/ignore` `{word}` ignores a word in that draft. The firm's own dictionary, accepted in every draft, is `GET /v2/dictionary`, `POST /v2/dictionary` `{word}` (one word of up to 60 letters; up to 5,000 words) and `DELETE /v2/dictionary/{word}`. The check runs on the server with an English dictionary (`nspell` 2.1.5, MIT; `dictionary-en` 4.0.0, MIT and BSD); nothing is sent elsewhere. - **Word's table of contents.** A Word file with a table of contents carries the page numbers the PDF prints, and Word opens it without asking to update fields. Both the firm's download and the agreed and draft Word files sent to a document system do this. - **Numbering, paper and line spacing.** `PUT /v2/drafts/{id}/layout` also takes `numbering`: `decimal` (1., 1.1, 1.1.1, the default) or `article` (ARTICLE I over its title, then Section 1.01, (a), (i), (A)). It also takes `paper` (`letter`, the default, or `a4`) and `spacing` (1, 1.15, 1.5 or 2, for the body text; tables, footnotes and signature blocks stay single). Cross-references follow the numbering on their own: "Section 4.2" becomes "Article IV" or "Section 4.02(a)". The draft checks then recognise typed "Article IV" and "Section 4.02" rather than "Section 4.2". A default value removes the setting. A numbering, paper or spacing other than the default is covered by approval, like the header and footer. The PDF, the editor, the review page and the Word file all use these settings: real Word multilevel numbering, page size and line spacing. A Word file that uses them comes back with them on import. Word export also writes Arial when the draft's font is Helvetica, where it used to write Times New Roman whatever the font. An A4 agreement signs on A4, and the signing record appended to the completed PDF is A4 too (it stays Letter after Letter or landscape pages). - **Revisions in responses.** Every write that returns the draft (edits, suggestions and decisions, comments, pictures, shares, and the rest) reports the revision it saved. `GET /v2/drafts/{id}?since=` answers `unchanged` until someone else changes the draft. - **Long agreements.** Drafts take up to 8,000 passages and print up to 1,000 pages. Saved versions keep their text once per distinct version (`hes_draft_versions` in production), so a long negotiation stays small; a 140-page draft with 31 versions measured 538 KB. Rendered PDFs are cached by content. Locally, a 140-page draft opened in about 0.2 s, an edit redrew in about 0.16 s, the PDF took 1.3 s and a comparison 4.4 s the first time and 2.3 s after. - **Two people in one passage.** A `text` edit whose `from` is out of date is merged word by word with what changed meanwhile: when the two changes do not touch the same words the edit is saved and the result says `merged`; only overlapping changes return `409 TEXT_CHANGED`. The editor saves as you type, shows others' words as they arrive, and undo takes back only your own words. Undo history survives a reload in the same tab. - **Review emails.** On `POST /v2/drafts/{id}/shares`, `send: true` emails the reviewer their link with an optional `message` and `respond_by` date; the reviewer's page shows both. `rotate` and `reshare` accept the same fields. When a reviewer comments, suggests or replies, the firm is told once per 10-minute window per draft (`draft_digest` jobs, one email per watcher, idempotent per window), and reviewers are told when the firm answers them. `PUT /v2/drafts/{id}/watchers` with `{watchers: [{name, email}]}` chooses who at the firm is told; `POST /v2/drafts/{id}/seen` marks the latest response read. The draft list shows a "Responded" mark until then. These emails go through the delivery worker like invitations; with no mailer configured nothing is sent and nothing fails. - **Matters and ethical walls.** `GET/POST /v2/matters`, `PUT/DELETE /v2/matters/{matter}` (changes need `matters:manage`, held by owners and admins). A matter has a `name`, `client`, `reference` and, when `restricted: true`, a `team` of sign-in emails. `PUT /v2/drafts/{id}/matter` files a draft under a matter; only its team may file into a restricted one. A restricted matter's drafts, the agreements sent from them, their precedents, obligations, digests and search results are seen only by its team; everyone else gets `404`, as if they did not exist. An integration key and the local studio act for the whole workspace and are not walled. Every change to a matter is kept in its history. - **Precedents and what negotiations show.** `GET /v2/precedents?q=` (2 to 200 characters) searches the wording of agreed and signed drafts the viewer may see, returning each passage with its agreement, matter, section, the first-drafted wording and whether it changed in negotiation and by whose suggestions. `GET /v2/negotiations` sums up the workspace: agreements agreed of drafts, the median days to agree, suggestions made and accepted by the client and the other side, the sections changed most, and how each playbook position held (preferred, fallback, other wording, missing, red line, waived). It is worked out from the records, not by a model. - **More from the assistant.** Two further uses each need their own consent from an owner or admin in `PUT /v2/assistant`: `whole_document` (with `whole_document_acknowledged: true`) allows `action: "issues"`, an issue list over the whole agreement split into parts when long, each issue tied to its passage and severity; and `POST /v2/drafts` with `{template, from_terms}`, which fills a questionnaire from a pasted term sheet and reports what it answered and what it left open (`filled_from_terms`). `precedents` (with `precedents_acknowledged: true`) lets a passage request include up to three similar clauses from the firm's own agreed agreements, with names replaced by roles, and the result names them. `GET /v2/assistant` states what each use sends. Without the consent the request returns `409 ASSISTANT_SCOPE`. - **Agree and send.** `POST /v2/drafts/{id}/finalize` refuses with `NOT_READY` while suggestions are open or a signer lacks a name or email, and with `PLACEHOLDERS` while placeholders remain unless `allow_placeholders: true`. It also refuses with `NOT_READY` while required approval is missing or out of date. It locks the text, saves the agreed version and returns the signing package: `document`, `fields`, `roles`, `routing`, `people` and a `draft_token`. Create the agreement with that document unchanged and `template: {draft_id, version, draft_token}`; it records `origin.kind: "draft"` with `verified: true`, and the draft becomes `sent`. A changed document is recorded without `verified`. `GET /v2/drafts/{id}/agreed` returns the package again; `POST /v2/drafts/{id}/reopen` unlocks it. ## 6C. HESigns for Word HESigns for Word is an add-in for Word on Windows, Mac and the web, served by HESigns at `/word/manifest.xml`. Lawyers sign in once (OAuth with PKCE in Word's own dialog; the token reaches drafts only, within their role, and never the MCP endpoint), open a draft into a document with comments and open suggestions as tracked changes, edit it in Word and send their changes back as suggestions, refresh to bring in others' changes, work the review queue, accept or reject suggestions, comment on selected words, reply and resolve, see who else is in the draft, and apply playbook wording as a suggestion. Changes sent back with `party: "firm"` become your own team's suggestions, and the Word comments in that copy become internal comments. See word-addin/README.md for sideloading and firm-wide deployment. - **Hosting.** Every URL in the manifest is the server's public origin; Office requires HTTPS and a certificate Word trusts. The pane's pages may be framed only by Word on the web and Teams, and load scripts only from HESigns and Microsoft's Office.js address. Do not cache `/word/*.html`, `/word/manifest.xml` or `/word/config.json` at the edge. ## 6D. Saving to the firm's document system Owners and admins choose where agreed drafts and signed agreements are filed: Microsoft SharePoint or OneDrive through Microsoft Graph (an app registration with a client secret and admin consent; Sites.Selected is recommended over Files.ReadWrite.All), or a WebDAV server over HTTPS. Files are filed by a folder template such as `/Clients/{client}/{matter}/{agreement}`. - **Settings.** `GET/PUT/DELETE /v2/destinations` need `destinations:manage`, which owners and admins hold. The secret is encrypted and never returned; turning saving off deletes it. `POST /v2/destinations/test` signs in, finds the place and saves and removes a small test file. - **What is saved.** When a draft is agreed, the agreed PDF and its Word copy; when an agreement completes, the signed PDF and the proof kit exactly as the sender downloads it. Each file is saved once by the delivery worker, and nothing is ever overwritten: a second copy gets a new name. - **On demand.** `GET /v2/drafts/{id}/save-to` and `GET /v2/envelopes/{id}/save-to` show where the files would go; `POST` saves them now (`envelopes:write`). `GET /v2/destinations/uploads` lists recent saves, and `POST /v2/destinations/uploads/{id}/retry` saves a failed one again. - **Failures and records.** A failed save is shown to the firm's admins under recent saves and does not turn `/health/delivery` red. The admin audit log records each save's id, not its path. Files in the firm's document system follow the firm's own retention. ## 7. HEMeets: encrypted meetings where people sign together HEMeets is a video meeting attached to your agreements. People see each other, compare a security code, talk privately with their own lawyer, move into caucus rooms, draft terms live and sign before anyone leaves. It needs a media server (section 7.4). Without one, the meeting routes are off. ### 7.1 What is encrypted, and what HESigns sees Each browser makes a key pair when it joins. A participant who already holds the room key wraps it for the newcomer with a key derived from their two public keys (ECDH P-256, AES-GCM). The media server relays only public keys and ciphertext. - **Audio, video and screen share** are encrypted frame by frame in the browser (LiveKit encoded transforms) with the room key. - **Chat, captions, live terms, counsel-only PDFs and wire instructions** are encrypted with keys derived from the room key. - **The private line** between a lawyer and a client uses a key only those two browsers can derive. - **When someone leaves**, the remaining browsers switch to a new key that the leaver never receives. - **Caucus rooms** each have their own key. The **security code** (twelve digits) is computed in each browser from the room key and every participant's public key, as that browser received them. If a server substituted keys to sit in the middle, the codes would differ. Each person can record that their code matched. The code is evidence that the keys match, not proof of anyone's identity. HESigns does see: - who joined which room and when; - how much data was sent; - the consent and confirmation ledger; - any agreement signed, which it keeps so each signer receives the sealed copy and proof. It never holds a meeting key. A browser that cannot encrypt end to end cannot join; there is no unencrypted fallback. ### 7.2 Scheduling `POST /v2/meetings` (scopes `meetings:write`; reads need `meetings:read`): ```json { "title": "Marin v. Coastline mediation", "purpose": "mediation", "matter_number": "2026-CA-0142", "scheduled_at": "2026-10-02T14:00:00Z", "duration_minutes": 90, "time_zone": "America/New_York", "options": { "lobby": true, "recording": "with_consent", "captions": true, "caucus": true }, "participants": [ { "name": "Dana Whitfield", "email": "dana@mediate.example", "role": "mediator" }, { "name": "Sofia Marín", "email": "sofia@example.com", "role": "client", "locale": "pt", "phone": "+55 11 91234-5678" }, { "name": "Morgan Lee", "email": "morgan@firm.example", "role": "attorney", "represents": "Sofia Marín", "bar_number": "FL 123456" } ] } ``` - **Participants and links.** Each participant gets a private `join_path`, signed and tied to that person. - `POST /v2/meetings/{id}/participants/{participant}/new-link` replaces a leaked link and removes that person from the call. - `POST /v2/meetings/{id}/invitations` emails each person their link and a calendar link. - The workspace also prepares a WhatsApp message in the person's language. - **Roles.** Roles are `host`, `mediator`, `attorney`, `client`, `party`, `interpreter` and `observer`. A host or mediator admits people from the waiting room, moves them between rooms, asks to record, drafts the agreement and ends the meeting. - **Stated details.** Parties represented and bar numbers are recorded as stated; they are not verified. - **Languages.** The meeting page is in English, Spanish, Brazilian Portuguese, French and Arabic, with Arabic right to left. - **Signing in the meeting.** Pass `agreement_id` and match recipients with `signer_index` to sign an existing agreement inside the meeting. During a mediation, the mediator can instead turn the live terms into a PDF with a signature block for each signer. Every signer then signs in the meeting through the normal HESigns ceremony, including the changes check, approvals and interpreter statements. ### 7.3 During and after - **Recording.** `recording: "with_consent"` lets the host ask. Recording starts only after everyone present agrees. It stops if someone withdraws consent or a newcomer has not agreed. The file is made on the host's device and never sent to HESigns; the record keeps who agreed and the file's SHA-256. - **Captions.** Captions are recognised on each speaker's device and encrypted to the room. Viewers can translate them on their own device. A browser without on-device speech recognition shows others' captions but does not caption its user. HESigns never sends voice to a captioning service. - **Ending.** `POST /v2/meetings/{id}/end` ends the meeting for everyone, closes its rooms at the media server and seals the record. The record (`he-meets-record-v1`) is signed with the same Ed25519 key as agreement proofs and includes: - attendance as the media server reported it, through its signed webhooks; - security-code confirmations; - recording consents and file fingerprints; - wire confirmations, which keep only the fingerprint of the instructions; - the agreements signed. - **Downloads.** - `GET .../record.json` returns the sealed record. - `GET .../binder.zip` returns the closing binder: the record, each signed agreement with its proof kit, appearances, time entries, a draft FRE 902(11)/(13)/(14) certification for a qualified person to complete, and the key. - `GET .../time.csv` returns time per person and room, in minutes and tenths of an hour. - **Verifying.** The offline verifier checks `meeting-record.json` directly. ### 7.4 Running the media server HEMeets uses LiveKit's open-source server (Apache-2.0). Version 1.13.7 is tested. - **Cheapest setup:** one small instance next to HESigns, with no per-minute service fees. Bandwidth is the main cost, because the server forwards each person's encrypted streams to the others. - **Network:** - signalling over TLS (`wss://`) behind your load balancer; - TCP 7881 and a UDP range, or the single UDP port 7882; - LiveKit's built-in TURN on TLS 443 or 5349 for networks that block UDP. - **Webhook:** point it at `https:///meet/livekit/webhook` with the same API key. - **HESigns configuration:** `HE_MEETS_LIVEKIT_URL` (`wss://…`), `HE_MEETS_LIVEKIT_API_KEY` and `HE_MEETS_LIVEKIT_API_SECRET_FILE` (a file holding a secret of at least 32 characters). - **Local development:** `node livekitDev.mjs` runs a loopback-only server and checks the binary's SHA-256. **Browsers:** current Chrome, Edge, Safari and Firefox encrypt frames in the browser. **Limits:** 25 people per meeting and seven caucus rooms. Online notarization is described in section 7.5. ### 7.5 Online notarization A meeting with `purpose: "notarization"` notarizes the agreement attached to it (`agreement_id` is required). The notary runs the meeting, and the waiting room, recording with everyone's consent and no caucus rooms are always on. Roles are `notary`, `principal` and `witness`. - **Notary profile.** Each notary keeps their own profile (`GET/PUT /v2/meetings/notary/profile`): commission, seal image and a PKCS#12 signing certificate (RSA, 2048 bits or more). The key and its password are encrypted at rest and never returned. Profiles and journals belong to a signed-in member; an API key gets `403 NOTARY_MEMBER_REQUIRED`. - **Identity.** The principal shows their ID in the meeting. HESigns reads it on the server (passport and ID card check digits; the front of a driving licence, whose barcode is not read) to assist the notary, who decides. It is not a certified credential-analysis product. No knowledge-based authentication provider is bundled; without one, principals are identified by personal knowledge or a credible witness, and the meeting says so. - **The act.** The notary completes the certificate (acknowledgment, acknowledgment in a representative capacity, jurat or copy certification, with remote-appearance wording) and seals. The certificate page, the seal and the notary's PAdES signature are added to the signed PDF as an incremental update, so earlier signatures stay valid. When the certificate is ready before the last signature, completion notarizes the agreement and the proof covers the notarized bytes; otherwise the notary seals from the meeting afterwards. `GET /v2/meetings/{id}/notarization/notarized.pdf` downloads it. - **Journal.** Each notary's journal is append-only and hash-chained: `GET /v2/meetings/notary/journal`, with `.csv`, `.pdf` and a signed `.json` export. A correction is a new entry (`POST /v2/meetings/notary/journal/corrections`). - **Recording.** Live media stays end-to-end encrypted. The notary's browser encrypts the recording of the act with a fresh key before upload and sends that key to HESigns, which keeps it encrypted under the deployment key. HESigns can therefore decrypt the recording, so it can produce it during the retention period. - **Retention.** Recordings, notarized PDFs and journal signatures are kept for 10 years by default and ID images for 30 days. The worker deletes them when their time ends. Each notary's commission, any state registration or approval to notarize online, any platform approval a state requires and a contract with a knowledge-based authentication provider are legal steps outside the software. Have counsel review the certificate wording, identification phrases, retention periods and journal fields for each state before notaries use it. ## Errors and operations Every request returns `X-Request-ID`. Keep that ID for support, not private signing URLs or document bodies. A credential defaults to 120 API requests per minute across API processes. Honor `Retry-After` on rate-limit responses. A UTC monthly agreement limit is enforced transactionally and counts each new agreement once. These counts are metering, not a payment collection or invoicing system. Retry failed reads safely. Retry creation with the original idempotency key. Inspect queued delivery state before requesting another delivery. 401 means invalid/expired/revoked credentials; 403 means missing scope or an unenrolled origin; 409 means a state or idempotency conflict. No permissive browser CORS endpoint exposes customer keys. The signing page is available in English, Spanish, French and Arabic (right to left), and recipients can read the document in 20 languages (section 6). A deployment can provide a licensed TrueType/OpenType font via `HE_SIGNS_PDF_FONT_FILE`; unsupported glyphs fail explicitly. Cyrillic names were tested using a local licensed system font. This is not a claim of complete Arabic shaping, RTL, CJK, accessibility, or international language coverage. ## Release 0.7 workflows See [CAPABILITIES-0.7.md](CAPABILITIES-0.7.md) for packet inputs, role policies, OCR, Google/SCIM setup, bulk manifest preparation, resumable exports, privacy workflows, regions and certificate/timestamp configuration. Download the .NET client at `/sdk/dotnet.zip` and the Node resumable export helper at `/sdk/download-export.mjs`. New scopes are `provisioning:manage` and `privacy:manage`; background exports use `exports:read`. ## Admin audit log Every administrative change is recorded with who made it: a workspace member, an API key, or the system for background work. Entries also record the IP address, browser and time. The database chains each workspace's entries as they're written, and they can't be changed or deleted. The only permitted change is redacting a subject during a privacy erasure, and that stays verifiable because the chain covers the subject's digest. - **`GET /v2/audit-log?limit=50&before=`**: entries, newest first. - **`GET /v2/audit-log/verify`**: recomputes the chain and reports the first entry that fails, with the reason. - **`GET /v2/audit-log/export.json`**: the whole chain, signed with the same Ed25519 key as proofs (see `/verification-key.pem`). - Keep exports. A later export must extend the head signed in an earlier one. Editing, removing or reordering entries breaks the hashes, and dropping the newest entries contradicts the signed head. - Each `entry_hash` is the SHA-256 hex digest of the UTF-8 newline-joined fields: `prev_hash` (empty for the first entry), `tenant_id`, `seq`, `created_at_us`, `action`, `subject_sha256`, `actor_kind`, `actor_id`, `sha256(ip)`, `sha256(user_agent)`. Missing values are empty strings. - `production/auditLog.js` exports `verifyAuditExport(exported, publicKeyPem)`, which checks all of this. Owners and admins can see the log in the workspace under *API & events*. From there they can download it as CSV or as a signed export.