<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Wes E</title>
    <description>The latest articles on DEV Community by Wes E (@wes_e_fa9b9483d1243c16761).</description>
    <link>https://dev.to/wes_e_fa9b9483d1243c16761</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3461567%2Fa607dab7-548f-442d-a6eb-96ae72e2f1a3.png</url>
      <title>DEV Community: Wes E</title>
      <link>https://dev.to/wes_e_fa9b9483d1243c16761</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wes_e_fa9b9483d1243c16761"/>
    <language>en</language>
    <item>
      <title>Your contract templates should live in your repo, not in a WYSIWYG editor</title>
      <dc:creator>Wes E</dc:creator>
      <pubDate>Wed, 19 Aug 2026 17:09:19 +0000</pubDate>
      <link>https://dev.to/wes_e_fa9b9483d1243c16761/your-contract-templates-should-live-in-your-repo-not-in-a-wysiwyg-editor-10mh</link>
      <guid>https://dev.to/wes_e_fa9b9483d1243c16761/your-contract-templates-should-live-in-your-repo-not-in-a-wysiwyg-editor-10mh</guid>
      <description>&lt;p&gt;Every time I've needed signatures inside a product I was building, the same thing&lt;br&gt;
happened. The signature part took an afternoon. The &lt;em&gt;contract&lt;/em&gt; part took weeks —&lt;br&gt;
because the legal text lived in someone else's WYSIWYG editor, behind a login,&lt;br&gt;
with no version history anybody on my team could review.&lt;/p&gt;

&lt;p&gt;That's a strange place to keep the one document with legal consequences.&lt;/p&gt;

&lt;p&gt;So the tool I ended up building keeps templates as Markdown files. Here is the&lt;br&gt;
whole format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Freelance&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Agreement"&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Flexible&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;agreement&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;freelance&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;work&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;projects."&lt;/span&gt;
&lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Services&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# FREELANCE AGREEMENT&lt;/span&gt;

&lt;span class="gs"&gt;**Effective Date:**&lt;/span&gt; {{effective_date}}

This Freelance Agreement is entered into by:

&lt;span class="gs"&gt;**Freelancer:**&lt;/span&gt;
{{freelancer_name}}
{{freelancer_address}}
{{freelancer_email}}

&lt;span class="gu"&gt;## 1. PROJECT DESCRIPTION&lt;/span&gt;

{{project_description}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Frontmatter, headings, &lt;code&gt;{{variables}}&lt;/code&gt;. That's it. There is no other syntax to&lt;br&gt;
learn, because there is no other syntax.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why this shape matters more than it sounds like it should
&lt;/h3&gt;

&lt;p&gt;A Markdown template is a file. Files do things that editor content can't:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It diffs.&lt;/strong&gt; When someone changes the indemnity clause, that change shows up in&lt;br&gt;
a pull request as a red line and a green line. A non-engineer can read that diff&lt;br&gt;
and tell you what changed. Try getting that out of a rich-text editor's revision&lt;br&gt;
history.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It reviews.&lt;/strong&gt; The contract your company sends is now subject to the same&lt;br&gt;
approval process as the code your company ships. Same reviewers, same audit&lt;br&gt;
trail, same blame. If your legal counsel wants to own the clause language, they&lt;br&gt;
own a file — and &lt;code&gt;git log&lt;/code&gt; says when they last touched it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It tests.&lt;/strong&gt; You can lint it. You can grep every template for a term you're&lt;br&gt;
retiring. You can write a test that fails if a template loses a required&lt;br&gt;
variable. I have all of these; none of them were possible when the source of&lt;br&gt;
truth was a form field on someone else's server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's yours.&lt;/strong&gt; The template is in your repo whether or not you keep using my&lt;br&gt;
product. That's a deliberate property, not an accident — I've been on the wrong&lt;br&gt;
end of an export button before.&lt;/p&gt;
&lt;h3&gt;
  
  
  The full flow, end to end
&lt;/h3&gt;

&lt;p&gt;Four calls. Auth is an &lt;code&gt;x-api-key&lt;/code&gt; header on all of them; the base is&lt;br&gt;
&lt;code&gt;https://apisign.io/api&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Push a template.&lt;/strong&gt; &lt;code&gt;content&lt;/code&gt; is the Markdown above, verbatim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://apisign.io/api/template/create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-api-key: &lt;/span&gt;&lt;span class="nv"&gt;$APISIGN_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "name": "Freelance Agreement",
    "content": "# FREELANCE AGREEMENT\n\n**Effective Date:** {{effective_date}}\n..."
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returns &lt;code&gt;{ "template": { "id": "...", ... } }&lt;/code&gt;. Every save writes a version row,&lt;br&gt;
so the server keeps its own history alongside your git history.&lt;/p&gt;

&lt;p&gt;The natural next step is a CI job: on merge to &lt;code&gt;main&lt;/code&gt;, POST each changed&lt;br&gt;
&lt;code&gt;.md&lt;/code&gt; file. Your templates are then deployed the same way your code is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Create a contract&lt;/strong&gt; by filling the variables in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://apisign.io/api/contract/create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-api-key: &lt;/span&gt;&lt;span class="nv"&gt;$APISIGN_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "name": "Freelance Agreement — Jane Doe",
    "template_id": "TEMPLATE_ID",
    "variables": {
      "effective_date": "2026-08-10",
      "freelancer_name": "Jane Doe",
      "freelancer_email": "jane@example.com",
      "project_description": "Design system refresh, 6 weeks."
    },
    "expires_in_days": 14,
    "signers": [
      { "email": "jane@example.com", "name": "Jane Doe", "signing_order": 1 }
    ]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get back the contract and its signers. Each signer carries an explicit&lt;br&gt;
&lt;code&gt;signing_order&lt;/code&gt;, so sequential signing is a number rather than a workflow&lt;br&gt;
builder. The signing link itself is generated at send time and goes into the&lt;br&gt;
email — it is not on the create response.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Send it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://apisign.io/api/contract/send &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-api-key: &lt;/span&gt;&lt;span class="nv"&gt;$APISIGN_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{ "contract_id": "CONTRACT_ID" }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Signers get an email with their link. They sign in the browser — no account, no&lt;br&gt;
app, no download.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Find out when they signed.&lt;/strong&gt; Register a webhook endpoint and you'll get&lt;br&gt;
&lt;code&gt;contract_signed&lt;/code&gt; when a signer finishes and &lt;code&gt;contract_completed&lt;/code&gt; when everyone&lt;br&gt;
has. (Yes, underscores. Stripe and GitHub use dots and I'd probably pick dots if&lt;br&gt;
I were starting today, but renaming them is a migration with a live integration&lt;br&gt;
on the other end, not a Tuesday.)&lt;/p&gt;

&lt;p&gt;One thing I'd tell you to do anyway: &lt;strong&gt;reconcile against &lt;code&gt;GET /contract/get&lt;/code&gt;&lt;br&gt;
rather than trusting delivery.&lt;/strong&gt; Webhooks retry with backoff, but the retry is&lt;br&gt;
kicked by your org's next event rather than by a scheduler, so a failed delivery&lt;br&gt;
to a quiet account waits. Every attempt is visible via the webhook API. I'd&lt;br&gt;
rather you know that than discover it.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it costs, since that's the next question
&lt;/h3&gt;

&lt;p&gt;$0.25 per contract sent. No subscription, no per-seat charge, no minimum. A&lt;br&gt;
contract with six signers costs the same as one with one signer, because the&lt;br&gt;
charge is per send, not per signature. A resend goes through the same path, so it&lt;br&gt;
costs another $0.25 — there's no free reminder, and I'd rather say that here than&lt;br&gt;
have you find it on the invoice.&lt;/p&gt;

&lt;p&gt;Sign up, create an organization, and you have $5.00 on it — 20 contracts, no&lt;br&gt;
card.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where it falls short
&lt;/h3&gt;

&lt;p&gt;Nobody outside my own companies has paid for this yet. That's the honest number&lt;br&gt;
and I'd rather lead with it than build a logo wall.&lt;/p&gt;

&lt;p&gt;Some specifics, because "early" is a word people use to avoid a list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API key &lt;code&gt;read&lt;/code&gt; / &lt;code&gt;read_write&lt;/code&gt; permissions aren't enforced yet.&lt;/strong&gt; The setting
is stored and displayed; nothing checks it. Treat every key as read-write and
don't hand one to a third party expecting it to be scoped.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;There's no rate limiting on the API.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No idempotency key on send.&lt;/strong&gt; If your HTTP client retries a timed-out send,
you can be charged twice and your signer gets two emails. Guard the call on
your side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webhook retries are best-effort&lt;/strong&gt;, per above.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are 37 templates in the library to start from, and an MCP server if you&lt;br&gt;
want an agent driving it.&lt;/p&gt;

&lt;p&gt;If you keep contract text in a WYSIWYG editor today and it's working fine for&lt;br&gt;
you, this isn't worth switching for. If you've ever tried to find out who changed&lt;br&gt;
a clause and when, I built this for that.&lt;/p&gt;

&lt;p&gt;Docs: &lt;a href="https://apisign.io/docs" rel="noopener noreferrer"&gt;https://apisign.io/docs&lt;/a&gt; — happy to answer anything here, including the&lt;br&gt;
parts that don't work.&lt;/p&gt;

</description>
      <category>api</category>
      <category>webdev</category>
      <category>showdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
